├── .gitignore ├── firstimages ├── aapoly.png ├── arcto.png ├── richtext.png ├── firstletter.png ├── firstpoly.png ├── firsttext_bad.png ├── text_comparison.png ├── firsttext_lessbad.png └── quad_before_after.png ├── tests ├── images │ ├── bird.jpg │ ├── bird.png │ ├── cat.jpg │ ├── rock.jpg │ └── test.jpg ├── fonts │ ├── SourceSansPro-Italic.ttf │ └── SourceSansPro-Regular.ttf ├── quadtest.js ├── checkerboard.js ├── image_scale.js ├── compositing.js ├── invert_jpg.js ├── geometry.html ├── transforms.js ├── text.js ├── shapes.js ├── pixel_poly.js └── runtests.js ├── .idea ├── watcherTasks.xml ├── encodings.xml ├── misc.xml ├── vcs.xml ├── modules.xml ├── node-pureimage.iml └── jsLibraryMappings.xml ├── src ├── named_colors.js ├── bitmap.js ├── text.js ├── transform.js ├── pureimage.js └── uint32.js ├── vendor ├── draw.js ├── check.js ├── tables │ ├── kern.js │ ├── loca.js │ ├── maxp.js │ ├── hmtx.js │ ├── hhea.js │ ├── head.js │ ├── post.js │ ├── os2.js │ ├── name.js │ ├── cmap.js │ ├── gpos.js │ ├── sfnt.js │ └── glyf.js ├── table.js ├── path.js ├── parse.js ├── opentype.js ├── glyph.js ├── types.js ├── font.js └── encoding.js ├── package.json ├── measure.js ├── LICENSE ├── polytest1.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /firstimages/aapoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/aapoly.png -------------------------------------------------------------------------------- /firstimages/arcto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/arcto.png -------------------------------------------------------------------------------- /tests/images/bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/images/bird.jpg -------------------------------------------------------------------------------- /tests/images/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/images/bird.png -------------------------------------------------------------------------------- /tests/images/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/images/cat.jpg -------------------------------------------------------------------------------- /tests/images/rock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/images/rock.jpg -------------------------------------------------------------------------------- /tests/images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/images/test.jpg -------------------------------------------------------------------------------- /firstimages/richtext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/richtext.png -------------------------------------------------------------------------------- /firstimages/firstletter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/firstletter.png -------------------------------------------------------------------------------- /firstimages/firstpoly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/firstpoly.png -------------------------------------------------------------------------------- /firstimages/firsttext_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/firsttext_bad.png -------------------------------------------------------------------------------- /firstimages/text_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/text_comparison.png -------------------------------------------------------------------------------- /firstimages/firsttext_lessbad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/firsttext_lessbad.png -------------------------------------------------------------------------------- /firstimages/quad_before_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/firstimages/quad_before_after.png -------------------------------------------------------------------------------- /tests/fonts/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/fonts/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /tests/fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quartz/node-pureimage/master/tests/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/named_colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by josh on 8/21/16. 3 | */ 4 | var NAMED_COLORS = { 5 | 'white':0xFFFFFFff, 'black':0x000000ff, 'red':0xFF0000ff, 'green':0x00FF00ff, 'blue':0x0000FFff, 6 | } 7 | 8 | module.exports = NAMED_COLORS; 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/node-pureimage.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vendor/draw.js: -------------------------------------------------------------------------------- 1 | // Drawing utility functions. 2 | 3 | 'use strict'; 4 | 5 | // Draw a line on the given context from point `x1,y1` to point `x2,y2`. 6 | function line(ctx, x1, y1, x2, y2) { 7 | ctx.beginPath(); 8 | ctx.moveTo(x1, y1); 9 | ctx.lineTo(x2, y2); 10 | ctx.stroke(); 11 | } 12 | 13 | exports.line = line; 14 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/quadtest.js: -------------------------------------------------------------------------------- 1 | 2 | var p = [ 3 | {x:0,y:0}, 4 | {x:30,y:30}, 5 | {x:30,y:30}, 6 | ]; 7 | 8 | function calcQuadraticAtT(points, t) { 9 | 10 | // var t = 0.5; // given example value 11 | var x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x; 12 | var y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y; 13 | return {x:x,y:y}; 14 | } 15 | console.log("output = ", calcQuadraticAtT(p,0.5)); 16 | -------------------------------------------------------------------------------- /vendor/check.js: -------------------------------------------------------------------------------- 1 | // Run-time checking of preconditions. 2 | 3 | 'use strict'; 4 | 5 | // Precondition function that checks if the given predicate is true. 6 | // If not, it will throw an error. 7 | exports.argument = function (predicate, message) { 8 | if (!predicate) { 9 | throw new Error(message); 10 | } 11 | }; 12 | 13 | // Precondition function that checks if the given assertion is true. 14 | // If not, it will throw an error. 15 | exports.assert = exports.argument; 16 | -------------------------------------------------------------------------------- /tests/checkerboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by josh on 8/29/16. 3 | */ 4 | 5 | var PImage = require('../src/pureimage'); 6 | var fs = require('fs'); 7 | 8 | 9 | var img = PImage.make(100,100); 10 | var c = img.getContext('2d'); 11 | //draw red /blue checkerboard 12 | for(var i=0; i<10; i+=1) { 13 | for(var j=0; j<10; j+=1) { 14 | if((i+j) %2 == 0) c.fillStyle = 'red'; 15 | else c.fillStyle = 'blue'; 16 | c.fillRect(i*10,j*10,10,10); 17 | } 18 | } 19 | 20 | 21 | PImage.encodePNG(img, fs.createWriteStream('out.png'), (err) => { 22 | console.log("done! err = ",err); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/image_scale.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | var fs = require('fs'); 3 | 4 | var sc = 2; 5 | var img1 = PImage.decodeJPEG(fs.readFileSync("tests/images/rock.jpg")); 6 | var img2 = PImage.make(img1.width/sc,img1.height/sc); 7 | var ctx = img2.getContext('2d'); 8 | 9 | ctx.drawImage(img1, 10 | //src 11 | 0, 0, Math.floor(img1.width/sc), Math.floor(img1.height/sc), 12 | //dst 13 | 0, 0, Math.floor(img1.width/sc), Math.floor(img1.height/sc) 14 | ); 15 | 16 | //write out to a PNG file 17 | PImage.encodePNG(img2, fs.createWriteStream('out.png'), (err) => { 18 | console.log("wrote to out.png. err = ",err); 19 | }); 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pureimage", 3 | "version": "0.1.3", 4 | "description": "Pure JS image drawing API based on Canvas. Export to PNG streams.", 5 | "author": { 6 | "name": "Josh Marinacci", 7 | "email": "joshua@marinacci.org", 8 | "url": "http://joshondesign.com/" 9 | }, 10 | "main": "./src/pureimage.js", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/joshmarinacci/node-pureimage.git" 14 | }, 15 | "dependencies": { 16 | "jpeg-js": "0.0.4", 17 | "opentype.js": "^0.4.3", 18 | "pngjs": "^0.4.0" 19 | }, 20 | "devDependencies": { 21 | "tape":"^4.6.3" 22 | }, 23 | "scripts": { 24 | "test": "node tests/runtests.js" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/compositing.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | 3 | { 4 | 5 | // compositing tests 6 | // red onto white 7 | eq(PImage.compositePixel(0xFF0000FF, 0xFFFFFFFF), 0xFF0000FF); 8 | // blue onto white 9 | eq(PImage.compositePixel(0x00FF00FF, 0xFFFFFFFF), 0x00FF00FF); 10 | // red onto black 11 | eq(PImage.compositePixel(0xFF0000FF, 0x000000FF), 0xFF0000FF); 12 | // red 50% onto black 13 | eq(PImage.compositePixel(0xFF00007F, 0x000000FF), 0x7F0000FF); 14 | } 15 | 16 | function eq(a,b) { 17 | if(a != b) throw new Error(a.toString(16) + " is not equal to " + b.toString(16)); 18 | } 19 | function dumpBytes(img) { 20 | for(var i=0; i<10; i++) { 21 | console.log(i,img._buffer[i].toString(16)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/invert_jpg.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | var fs = require('fs'); 3 | 4 | var img = PImage.decodeJPEG(fs.readFileSync("tests/images/cat.jpg")); 5 | var ctx = img.getContext('2d'); 6 | var imageData = ctx.getImageData(0,0,100,100); 7 | 8 | 9 | var data = imageData.data; 10 | //invert each channel 11 | for (var i = 0; i < data.length; i += 4) { 12 | data[i] = 255 - data[i]; // red 13 | data[i + 1] = 255 - data[i + 1]; // green 14 | data[i + 2] = 255 - data[i + 2]; // blue 15 | //skip alpha 16 | } 17 | //put the data back 18 | ctx.putImageData(imageData, 0, 0); 19 | 20 | //write out to a PNG file 21 | PImage.encodePNG(img, fs.createWriteStream('out.png'), (err) => { 22 | console.log("wrote to out.png. err = ",err); 23 | }); 24 | 25 | 26 | -------------------------------------------------------------------------------- /measure.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var file = fs.readFileSync(process.argv[2]).toString(); 4 | console.log('chars = ',file.length); 5 | var lines = file.split('\n'); 6 | console.log('lines = ',lines.length); 7 | 8 | var code = lines.filter(function(line){ 9 | //comments 10 | if(line.match(/^\s*\/\//)) { 11 | return false; 12 | } 13 | if(line.match(/^\s*$/)) { 14 | return false; 15 | } 16 | return true; 17 | }); 18 | 19 | console.log('code lines = ',code.length); 20 | 21 | /* 22 | 23 | added selection support 24 | 2014-08-20: 416, 414, 25 | 403 (remove tree dump) 26 | 398 (inlined calcSize) 27 | 411 (added unified bg and text block) 28 | 406 (automate setting default text properties) 29 | 445 (add clipboard, fix selection markers, condense line breaking code) 30 | 390 (removed block system) 31 | 376 (switch to pair system, more cleanup) 32 | 340 (more cleanup, remove some chaining returns) 33 | 2014-08-27: 34 | 391: after adding bunch of new stuff, did a bunch of cleanup 35 | 397: moved selection, clipboard, and cursor into their own classes 36 | 37 | */ 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Josh Marinacci 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/tables/kern.js: -------------------------------------------------------------------------------- 1 | // The `kern` table contains kerning pairs. 2 | // Note that some fonts use the GPOS OpenType layout table to specify kerning. 3 | // https://www.microsoft.com/typography/OTSPEC/kern.htm 4 | 5 | 'use strict'; 6 | 7 | var check = require('../check'); 8 | var parse = require('../parse'); 9 | 10 | // Parse the `kern` table which contains kerning pairs. 11 | function parseKernTable(data, start) { 12 | var pairs, p, tableVersion, subTableVersion, nPairs, 13 | i, leftIndex, rightIndex, value; 14 | pairs = {}; 15 | p = new parse.Parser(data, start); 16 | tableVersion = p.parseUShort(); 17 | check.argument(tableVersion === 0, 'Unsupported kern table version.'); 18 | // Skip nTables. 19 | p.skip('uShort', 1); 20 | subTableVersion = p.parseUShort(); 21 | check.argument(subTableVersion === 0, 'Unsupported kern sub-table version.'); 22 | // Skip subTableLength, subTableCoverage 23 | p.skip('uShort', 2); 24 | nPairs = p.parseUShort(); 25 | // Skip searchRange, entrySelector, rangeShift. 26 | p.skip('uShort', 3); 27 | for (i = 0; i < nPairs; i += 1) { 28 | leftIndex = p.parseUShort(); 29 | rightIndex = p.parseUShort(); 30 | value = p.parseShort(); 31 | pairs[leftIndex + ',' + rightIndex] = value; 32 | } 33 | return pairs; 34 | } 35 | 36 | exports.parse = parseKernTable; 37 | -------------------------------------------------------------------------------- /vendor/tables/loca.js: -------------------------------------------------------------------------------- 1 | // The `loca` table stores the offsets to the locations of the glyphs in the font. 2 | // https://www.microsoft.com/typography/OTSPEC/loca.htm 3 | 4 | 'use strict'; 5 | 6 | var parse = require('../parse'); 7 | 8 | // Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, 9 | // relative to the beginning of the glyphData table. 10 | // The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) 11 | // The loca table has two versions: a short version where offsets are stored as uShorts, and a long 12 | // version where offsets are stored as uLongs. The `head` table specifies which version to use 13 | // (under indexToLocFormat). 14 | function parseLocaTable(data, start, numGlyphs, shortVersion) { 15 | var p, parseFn, glyphOffsets, glyphOffset, i; 16 | p = new parse.Parser(data, start); 17 | parseFn = shortVersion ? p.parseUShort : p.parseULong; 18 | // There is an extra entry after the last index element to compute the length of the last glyph. 19 | // That's why we use numGlyphs + 1. 20 | glyphOffsets = []; 21 | for (i = 0; i < numGlyphs + 1; i += 1) { 22 | glyphOffset = parseFn.call(p); 23 | if (shortVersion) { 24 | // The short table version stores the actual offset divided by 2. 25 | glyphOffset *= 2; 26 | } 27 | glyphOffsets.push(glyphOffset); 28 | } 29 | return glyphOffsets; 30 | } 31 | 32 | exports.parse = parseLocaTable; 33 | -------------------------------------------------------------------------------- /tests/geometry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /vendor/table.js: -------------------------------------------------------------------------------- 1 | // Table metadata 2 | 3 | 'use strict'; 4 | 5 | var check = require('./check'); 6 | var encode = require('./types').encode; 7 | var sizeOf = require('./types').sizeOf; 8 | 9 | function Table(tableName, fields, options) { 10 | var i; 11 | for (i = 0; i < fields.length; i += 1) { 12 | var field = fields[i]; 13 | this[field.name] = field.value; 14 | } 15 | this.tableName = tableName; 16 | this.fields = fields; 17 | if (options) { 18 | var optionKeys = Object.keys(options); 19 | for (i = 0; i < optionKeys.length; i += 1) { 20 | var k = optionKeys[i]; 21 | var v = options[k]; 22 | if (this[k] !== undefined) { 23 | this[k] = v; 24 | } 25 | } 26 | } 27 | } 28 | 29 | Table.prototype.sizeOf = function () { 30 | var v = 0; 31 | for (var i = 0; i < this.fields.length; i += 1) { 32 | var field = this.fields[i]; 33 | var value = this[field.name]; 34 | if (value === undefined) { 35 | value = field.value; 36 | } 37 | if (typeof value.sizeOf === 'function') { 38 | v += value.sizeOf(); 39 | } else { 40 | var sizeOfFunction = sizeOf[field.type]; 41 | check.assert(typeof sizeOfFunction === 'function', 'Could not find sizeOf function for field' + field.name); 42 | v += sizeOfFunction(value); 43 | } 44 | } 45 | return v; 46 | }; 47 | 48 | Table.prototype.encode = function () { 49 | return encode.TABLE(this); 50 | }; 51 | 52 | exports.Table = Table; 53 | -------------------------------------------------------------------------------- /vendor/tables/maxp.js: -------------------------------------------------------------------------------- 1 | // The `maxp` table establishes the memory requirements for the font. 2 | // We need it just to get the number of glyphs in the font. 3 | // https://www.microsoft.com/typography/OTSPEC/maxp.htm 4 | 5 | 'use strict'; 6 | 7 | var parse = require('../parse'); 8 | var table = require('../table'); 9 | 10 | // Parse the maximum profile `maxp` table. 11 | function parseMaxpTable(data, start) { 12 | var maxp = {}, 13 | p = new parse.Parser(data, start); 14 | maxp.version = p.parseVersion(); 15 | maxp.numGlyphs = p.parseUShort(); 16 | if (maxp.version === 1.0) { 17 | maxp.maxPoints = p.parseUShort(); 18 | maxp.maxContours = p.parseUShort(); 19 | maxp.maxCompositePoints = p.parseUShort(); 20 | maxp.maxCompositeContours = p.parseUShort(); 21 | maxp.maxZones = p.parseUShort(); 22 | maxp.maxTwilightPoints = p.parseUShort(); 23 | maxp.maxStorage = p.parseUShort(); 24 | maxp.maxFunctionDefs = p.parseUShort(); 25 | maxp.maxInstructionDefs = p.parseUShort(); 26 | maxp.maxStackElements = p.parseUShort(); 27 | maxp.maxSizeOfInstructions = p.parseUShort(); 28 | maxp.maxComponentElements = p.parseUShort(); 29 | maxp.maxComponentDepth = p.parseUShort(); 30 | } 31 | return maxp; 32 | } 33 | 34 | function makeMaxpTable(numGlyphs) { 35 | return new table.Table('maxp', [ 36 | {name: 'version', type: 'FIXED', value: 0x00005000}, 37 | {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} 38 | ]); 39 | } 40 | 41 | exports.parse = parseMaxpTable; 42 | exports.make = makeMaxpTable; 43 | -------------------------------------------------------------------------------- /vendor/tables/hmtx.js: -------------------------------------------------------------------------------- 1 | // The `hmtx` table contains the horizontal metrics for all glyphs. 2 | // https://www.microsoft.com/typography/OTSPEC/hmtx.htm 3 | 4 | 'use strict'; 5 | 6 | var parse = require('../parse'); 7 | var table = require('../table'); 8 | 9 | // Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. 10 | // This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. 11 | function parseHmtxTable(data, start, numMetrics, numGlyphs, glyphs) { 12 | var p, i, glyph, advanceWidth, leftSideBearing; 13 | p = new parse.Parser(data, start); 14 | for (i = 0; i < numGlyphs; i += 1) { 15 | // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. 16 | if (i < numMetrics) { 17 | advanceWidth = p.parseUShort(); 18 | leftSideBearing = p.parseShort(); 19 | } 20 | glyph = glyphs[i]; 21 | glyph.advanceWidth = advanceWidth; 22 | glyph.leftSideBearing = leftSideBearing; 23 | } 24 | } 25 | 26 | function makeHmtxTable(glyphs) { 27 | var t = new table.Table('hmtx', []); 28 | for (var i = 0; i < glyphs.length; i += 1) { 29 | var glyph = glyphs[i]; 30 | var advanceWidth = glyph.advanceWidth || 0; 31 | var leftSideBearing = glyph.leftSideBearing || 0; 32 | t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); 33 | t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); 34 | } 35 | return t; 36 | } 37 | 38 | exports.parse = parseHmtxTable; 39 | exports.make = makeHmtxTable; 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /tests/transforms.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | 3 | var fs = require('fs'); 4 | var assert = require('assert'); 5 | 6 | function simpleTransforms() { 7 | var img = PImage.make(10,10); 8 | var ctx = img.getContext('2d'); 9 | 10 | function drawLine() { 11 | ctx.beginPath(); 12 | ctx.moveTo(5, 5); 13 | ctx.lineTo(10, 10); 14 | ctx.lineTo(5, 10); 15 | ctx.closePath(); 16 | } 17 | drawLine(); 18 | 19 | assert.equal(ctx.path[0][0],'m'); 20 | assert.equal(ctx.path[0][1].x,5); 21 | 22 | ctx.save(); 23 | ctx.translate(5,0); 24 | drawLine(); 25 | ctx.restore(); 26 | assert.equal(ctx.path[0][0],'m'); 27 | assert.equal(ctx.path[0][1].x,10); 28 | 29 | drawLine(); 30 | assert.equal(ctx.path[0][0],'m'); 31 | assert.equal(ctx.path[0][1].x,5); 32 | 33 | 34 | ctx.save(); 35 | ctx.rotate(Math.PI/180.0*90); 36 | drawLine(); 37 | ctx.restore(); 38 | assert.equal(ctx.path[0][0],'m'); 39 | assert.equal(ctx.path[0][1].x,-5); 40 | assert.equal(ctx.path[0][1].y,5); 41 | 42 | 43 | ctx.save(); 44 | ctx.scale(2,2); 45 | drawLine(); 46 | ctx.restore(); 47 | assert.equal(ctx.path[0][0],'m'); 48 | assert.equal(ctx.path[0][1].x,10); 49 | assert.equal(ctx.path[0][1].y,10); 50 | 51 | assert.equal(ctx.path[1][0],'l'); 52 | assert.equal(ctx.path[1][1].x,20); 53 | assert.equal(ctx.path[1][1].y,20); 54 | 55 | assert.equal(ctx.path[2][0],'l'); 56 | assert.equal(ctx.path[2][1].x,10); 57 | assert.equal(ctx.path[2][1].y,20); 58 | 59 | assert.equal(ctx.path[3][0],'l'); 60 | assert.equal(ctx.path[3][1].x,10); 61 | assert.equal(ctx.path[3][1].y,10); 62 | } 63 | 64 | simpleTransforms(); 65 | 66 | -------------------------------------------------------------------------------- /src/bitmap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var uint32 = require('./uint32'); 3 | var Context = require('./context'); 4 | 5 | class Bitmap { 6 | constructor(w,h,options) { 7 | this.width = Math.floor(w); 8 | this.height = Math.floor(h); 9 | this.data = Buffer.alloc(w*h*4); 10 | var fillval = 0x000000FF; 11 | for(var j=0; j= this.width || y >= this.height) return 0; 22 | return (this.width*y+x)*4; 23 | } 24 | setPixelRGBA(x,y,rgba) { 25 | let i = this.calculateIndex(x, y); 26 | const bytes = uint32.getBytesBigEndian(rgba); 27 | this.data[i+0] = bytes[0]; 28 | this.data[i+1] = bytes[1]; 29 | this.data[i+2] = bytes[2]; 30 | this.data[i+3] = bytes[3]; 31 | } 32 | setPixelRGBA_i(x,y,r,g,b,a) { 33 | let i = this.calculateIndex(x, y); 34 | this.data[i+0] = r; 35 | this.data[i+1] = g; 36 | this.data[i+2] = b; 37 | this.data[i+3] = a; 38 | } 39 | getPixelRGBA(x,y) { 40 | let i = this.calculateIndex(x, y); 41 | return uint32.fromBytesBigEndian( 42 | this.data[i+0], 43 | this.data[i+1], 44 | this.data[i+2], 45 | this.data[i+3]); 46 | } 47 | getPixelRGBA_separate(x,y) { 48 | var i = this.calculateIndex(x,y); 49 | return this.data.slice(i,i+4); 50 | } 51 | getContext(type) { 52 | return new Context(this); 53 | } 54 | } 55 | module.exports = Bitmap; 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/text.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | var fs = require('fs'); 3 | 4 | var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro'); 5 | 6 | // First test: render synchronously loading text 7 | fnt.loadSync(); 8 | renderText('textSync'); 9 | 10 | // Second test: render asynchronously (font is loaded at this point so it's going to be faster) 11 | fnt.load(function() { renderText('textAsync') }); 12 | 13 | function renderText(fileName) { 14 | var img = PImage.make(200,200); 15 | var ctx = img.getContext('2d'); 16 | ctx.fillStyle = "#ffff00"; 17 | ctx.fillRect(0,0,200,200); 18 | ctx.fillStyle = '#00ff00'; 19 | ctx.font = "48pt 'Source Sans Pro'"; 20 | ctx.fillText("Hello world", 10, 60); 21 | 22 | //ctx.USE_FONT_GLYPH_CACHING = true; 23 | //var before = process.hrtime(); 24 | //var count = 20*1000; 25 | //for(var i=0; i{ 50 | // console.log("wrote out the png file to build/text.png"); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /vendor/tables/hhea.js: -------------------------------------------------------------------------------- 1 | // The `hhea` table contains information for horizontal layout. 2 | // https://www.microsoft.com/typography/OTSPEC/hhea.htm 3 | 4 | 'use strict'; 5 | 6 | var parse = require('../parse'); 7 | var table = require('../table'); 8 | 9 | // Parse the horizontal header `hhea` table 10 | function parseHheaTable(data, start) { 11 | var hhea = {}, 12 | p = new parse.Parser(data, start); 13 | hhea.version = p.parseVersion(); 14 | hhea.ascender = p.parseShort(); 15 | hhea.descender = p.parseShort(); 16 | hhea.lineGap = p.parseShort(); 17 | hhea.advanceWidthMax = p.parseUShort(); 18 | hhea.minLeftSideBearing = p.parseShort(); 19 | hhea.minRightSideBearing = p.parseShort(); 20 | hhea.xMaxExtent = p.parseShort(); 21 | hhea.caretSlopeRise = p.parseShort(); 22 | hhea.caretSlopeRun = p.parseShort(); 23 | hhea.caretOffset = p.parseShort(); 24 | p.relativeOffset += 8; 25 | hhea.metricDataFormat = p.parseShort(); 26 | hhea.numberOfHMetrics = p.parseUShort(); 27 | return hhea; 28 | } 29 | 30 | function makeHheaTable(options) { 31 | return new table.Table('hhea', [ 32 | {name: 'version', type: 'FIXED', value: 0x00010000}, 33 | {name: 'ascender', type: 'FWORD', value: 0}, 34 | {name: 'descender', type: 'FWORD', value: 0}, 35 | {name: 'lineGap', type: 'FWORD', value: 0}, 36 | {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, 37 | {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, 38 | {name: 'minRightSideBearing', type: 'FWORD', value: 0}, 39 | {name: 'xMaxExtent', type: 'FWORD', value: 0}, 40 | {name: 'caretSlopeRise', type: 'SHORT', value: 1}, 41 | {name: 'caretSlopeRun', type: 'SHORT', value: 0}, 42 | {name: 'caretOffset', type: 'SHORT', value: 0}, 43 | {name: 'reserved1', type: 'SHORT', value: 0}, 44 | {name: 'reserved2', type: 'SHORT', value: 0}, 45 | {name: 'reserved3', type: 'SHORT', value: 0}, 46 | {name: 'reserved4', type: 'SHORT', value: 0}, 47 | {name: 'metricDataFormat', type: 'SHORT', value: 0}, 48 | {name: 'numberOfHMetrics', type: 'USHORT', value: 0} 49 | ], options); 50 | } 51 | 52 | exports.parse = parseHheaTable; 53 | exports.make = makeHheaTable; 54 | -------------------------------------------------------------------------------- /vendor/tables/head.js: -------------------------------------------------------------------------------- 1 | // The `head` table contains global information about the font. 2 | // https://www.microsoft.com/typography/OTSPEC/head.htm 3 | 4 | 'use strict'; 5 | 6 | var check = require('../check'); 7 | var parse = require('../parse'); 8 | var table = require('../table'); 9 | 10 | // Parse the header `head` table 11 | function parseHeadTable(data, start) { 12 | var head = {}, 13 | p = new parse.Parser(data, start); 14 | head.version = p.parseVersion(); 15 | head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; 16 | head.checkSumAdjustment = p.parseULong(); 17 | head.magicNumber = p.parseULong(); 18 | check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); 19 | head.flags = p.parseUShort(); 20 | head.unitsPerEm = p.parseUShort(); 21 | head.created = p.parseLongDateTime(); 22 | head.modified = p.parseLongDateTime(); 23 | head.xMin = p.parseShort(); 24 | head.yMin = p.parseShort(); 25 | head.xMax = p.parseShort(); 26 | head.yMax = p.parseShort(); 27 | head.macStyle = p.parseUShort(); 28 | head.lowestRecPPEM = p.parseUShort(); 29 | head.fontDirectionHint = p.parseShort(); 30 | head.indexToLocFormat = p.parseShort(); // 50 31 | head.glyphDataFormat = p.parseShort(); 32 | return head; 33 | } 34 | 35 | function makeHeadTable(options) { 36 | return new table.Table('head', [ 37 | {name: 'version', type: 'FIXED', value: 0x00010000}, 38 | {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, 39 | {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, 40 | {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, 41 | {name: 'flags', type: 'USHORT', value: 0}, 42 | {name: 'unitsPerEm', type: 'USHORT', value: 1000}, 43 | {name: 'created', type: 'LONGDATETIME', value: 0}, 44 | {name: 'modified', type: 'LONGDATETIME', value: 0}, 45 | {name: 'xMin', type: 'SHORT', value: 0}, 46 | {name: 'yMin', type: 'SHORT', value: 0}, 47 | {name: 'xMax', type: 'SHORT', value: 0}, 48 | {name: 'yMax', type: 'SHORT', value: 0}, 49 | {name: 'macStyle', type: 'USHORT', value: 0}, 50 | {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, 51 | {name: 'fontDirectionHint', type: 'SHORT', value: 2}, 52 | {name: 'indexToLocFormat', type: 'SHORT', value: 0}, 53 | {name: 'glyphDataFormat', type: 'SHORT', value: 0} 54 | ], options); 55 | } 56 | 57 | exports.parse = parseHeadTable; 58 | exports.make = makeHeadTable; 59 | -------------------------------------------------------------------------------- /vendor/tables/post.js: -------------------------------------------------------------------------------- 1 | // The `post` table stores additional PostScript information, such as glyph names. 2 | // https://www.microsoft.com/typography/OTSPEC/post.htm 3 | 4 | 'use strict'; 5 | 6 | var encoding = require('../encoding'); 7 | var parse = require('../parse'); 8 | var table = require('../table'); 9 | 10 | // Parse the PostScript `post` table 11 | function parsePostTable(data, start) { 12 | var post = {}, 13 | p = new parse.Parser(data, start), 14 | i, nameLength; 15 | post.version = p.parseVersion(); 16 | post.italicAngle = p.parseFixed(); 17 | post.underlinePosition = p.parseShort(); 18 | post.underlineThickness = p.parseShort(); 19 | post.isFixedPitch = p.parseULong(); 20 | post.minMemType42 = p.parseULong(); 21 | post.maxMemType42 = p.parseULong(); 22 | post.minMemType1 = p.parseULong(); 23 | post.maxMemType1 = p.parseULong(); 24 | switch (post.version) { 25 | case 1: 26 | post.names = encoding.standardNames.slice(); 27 | break; 28 | case 2: 29 | post.numberOfGlyphs = p.parseUShort(); 30 | post.glyphNameIndex = new Array(post.numberOfGlyphs); 31 | for (i = 0; i < post.numberOfGlyphs; i++) { 32 | post.glyphNameIndex[i] = p.parseUShort(); 33 | } 34 | post.names = []; 35 | for (i = 0; i < post.numberOfGlyphs; i++) { 36 | if (post.glyphNameIndex[i] >= encoding.standardNames.length) { 37 | nameLength = p.parseChar(); 38 | post.names.push(p.parseString(nameLength)); 39 | } 40 | } 41 | break; 42 | case 2.5: 43 | post.numberOfGlyphs = p.parseUShort(); 44 | post.offset = new Array(post.numberOfGlyphs); 45 | for (i = 0; i < post.numberOfGlyphs; i++) { 46 | post.offset[i] = p.parseChar(); 47 | } 48 | break; 49 | } 50 | return post; 51 | } 52 | 53 | function makePostTable() { 54 | return new table.Table('post', [ 55 | {name: 'version', type: 'FIXED', value: 0x00030000}, 56 | {name: 'italicAngle', type: 'FIXED', value: 0}, 57 | {name: 'underlinePosition', type: 'FWORD', value: 0}, 58 | {name: 'underlineThickness', type: 'FWORD', value: 0}, 59 | {name: 'isFixedPitch', type: 'ULONG', value: 0}, 60 | {name: 'minMemType42', type: 'ULONG', value: 0}, 61 | {name: 'maxMemType42', type: 'ULONG', value: 0}, 62 | {name: 'minMemType1', type: 'ULONG', value: 0}, 63 | {name: 'maxMemType1', type: 'ULONG', value: 0} 64 | ]); 65 | } 66 | 67 | exports.parse = parsePostTable; 68 | exports.make = makePostTable; 69 | -------------------------------------------------------------------------------- /tests/shapes.js: -------------------------------------------------------------------------------- 1 | var PImage = require('../src/pureimage'); 2 | var fs = require('fs'); 3 | 4 | var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro'); 5 | fnt.load(function() { 6 | var img = PImage.make(800,400); 7 | var ctx = img.getContext('2d'); 8 | ctx.fillStyle = '#FFFFFF'; 9 | ctx.fillRect(0,0,800,400); 10 | ctx.fillStyle = '#000000'; 11 | ctx.setFont('Source Sans Pro',100); 12 | //ctx.fillText("Greetings",50,150); 13 | //ctx.fillText("Earthling",50,360); 14 | ctx.fillText("iag&",0,300); 15 | ctx.strokeText("iag&",0,400); 16 | 17 | 18 | 19 | ctx.beginPath(); 20 | ctx.moveTo(250,50); 21 | ctx.lineTo(300,50); 22 | ctx.lineTo(300,100); 23 | ctx.lineTo(250,100); 24 | ctx.lineTo(250,50); 25 | ctx.moveTo(360,160); 26 | ctx.lineTo(390,160); 27 | ctx.lineTo(390,190); 28 | ctx.lineTo(360,190); 29 | ctx.closePath(); 30 | ctx.fill(); 31 | 32 | 33 | 34 | //bottom right 35 | ctx.beginPath(); 36 | ctx.arc(475, 75, 75, 0, Math.PI*2, true); //outer counter-clockwise 37 | ctx.arc(475, 75, 25, 0, Math.PI*2, false); //inner clockwise 38 | //ctx.closePath(); 39 | ctx.fill(); 40 | 41 | 42 | 43 | 44 | ctx.beginPath(); 45 | ctx.moveTo(75,25); 46 | ctx.quadraticCurveTo(25,25,25,62.5); 47 | ctx.quadraticCurveTo(25,100,50,100); 48 | ctx.quadraticCurveTo(50,120,30,125); 49 | ctx.quadraticCurveTo(60,120,65,100); 50 | ctx.quadraticCurveTo(125,100,125,62.5); 51 | ctx.quadraticCurveTo(125,25,75,25); 52 | ctx.fill(); 53 | 54 | 55 | 56 | 57 | { 58 | var xoff = 400; 59 | var yoff = 0; 60 | ctx.beginPath(); 61 | ctx.moveTo(109 + xoff, 224 + yoff); 62 | ctx.bezierCurveTo(64 + xoff, 166 + yoff, 134 + xoff, 172 + yoff, 221 + xoff, 169 + yoff); 63 | ctx.bezierCurveTo(281 + xoff, 167 + yoff, 180 + xoff, 261 + yoff, 333 + xoff, 252 + yoff); 64 | ctx.bezierCurveTo(470 + xoff, 244 + yoff, 80 + xoff, 295 + yoff, 110 + xoff, 242 + yoff); 65 | ctx.closePath(); 66 | ctx.fill(); 67 | //ctx.stroke(); 68 | } 69 | 70 | /* 71 | var metrics = ctx.measureText("Greetings"); 72 | 73 | 74 | function hline(y) { 75 | ctx.fillStyle = "#00FF00"; 76 | ctx.beginPath(); 77 | ctx.moveTo(0, y); 78 | ctx.lineTo(799,y); 79 | ctx.stroke(); 80 | } 81 | function vline(x) { 82 | ctx.fillStyle = "#00FF00"; 83 | ctx.beginPath(); 84 | ctx.moveTo(x, 0); 85 | ctx.lineTo(x, 399); 86 | ctx.stroke(); 87 | } 88 | 89 | 90 | vline(50+0); 91 | vline(50+metrics.width); 92 | hline(150-metrics.emHeightAscent); 93 | hline(150-metrics.emHeightDescent); 94 | */ 95 | 96 | PImage.encodePNG(img, fs.createWriteStream("build/shapes.png"), function(){ 97 | console.log("rendered build/shapes.png"); 98 | }); 99 | }) 100 | -------------------------------------------------------------------------------- /src/text.js: -------------------------------------------------------------------------------- 1 | var opentype = require('opentype.js'); 2 | 3 | /** 4 | * Created by josh on 7/2/17. 5 | */ 6 | var _fonts = { }; 7 | const DEFAULT_FONT_FAMILY = 'source'; 8 | 9 | exports.registerFont = function(binaryPath, family, weight, style, variant) { 10 | _fonts[family] = { 11 | binary: binaryPath, 12 | family: family, 13 | weight: weight, 14 | style: style, 15 | variant: variant, 16 | loaded: false, 17 | font: null, 18 | load: function(cb) { 19 | if(this.loaded) { 20 | if(cb)cb(); 21 | return; 22 | } 23 | var self = this; 24 | opentype.load(binaryPath, function (err, font) { 25 | if (err) throw new Error('Could not load font: ' + err); 26 | self.loaded = true; 27 | self.font = font; 28 | if(cb)cb(); 29 | }); 30 | } 31 | }; 32 | return _fonts[family]; 33 | }; 34 | exports.debug_list_of_fonts = _fonts; 35 | 36 | function findFont(family) { 37 | if(_fonts[family]) return _fonts[family]; 38 | family = Object.keys(_fonts)[0]; 39 | return _fonts[family]; 40 | } 41 | 42 | exports.processTextPath = function(ctx,text,x,y, fill) { 43 | let font = findFont(ctx._font.family); 44 | var size = ctx._font.size; 45 | if(ctx.USE_FONT_GLYPH_CACHING) { 46 | var off = 0; 47 | for(var i=0; i { 139 | console.log("wrote to out.png. err = ",err); 140 | }); 141 | -------------------------------------------------------------------------------- /polytest1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /vendor/path.js: -------------------------------------------------------------------------------- 1 | // Geometric objects 2 | 3 | 'use strict'; 4 | 5 | // A bézier path containing a set of path commands similar to a SVG path. 6 | // Paths can be drawn on a context using `draw`. 7 | function Path() { 8 | this.commands = []; 9 | this.fill = 'black'; 10 | this.stroke = null; 11 | this.strokeWidth = 1; 12 | } 13 | 14 | Path.prototype.moveTo = function (x, y) { 15 | this.commands.push({ 16 | type: 'M', 17 | x: x, 18 | y: y 19 | }); 20 | }; 21 | 22 | Path.prototype.lineTo = function (x, y) { 23 | this.commands.push({ 24 | type: 'L', 25 | x: x, 26 | y: y 27 | }); 28 | }; 29 | 30 | Path.prototype.curveTo = Path.prototype.bezierCurveTo = function (x1, y1, x2, y2, x, y) { 31 | this.commands.push({ 32 | type: 'C', 33 | x1: x1, 34 | y1: y1, 35 | x2: x2, 36 | y2: y2, 37 | x: x, 38 | y: y 39 | }); 40 | }; 41 | 42 | Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function (x1, y1, x, y) { 43 | this.commands.push({ 44 | type: 'Q', 45 | x1: x1, 46 | y1: y1, 47 | x: x, 48 | y: y 49 | }); 50 | }; 51 | 52 | Path.prototype.close = Path.prototype.closePath = function () { 53 | this.commands.push({ 54 | type: 'Z' 55 | }); 56 | }; 57 | 58 | // Add the given path or list of commands to the commands of this path. 59 | Path.prototype.extend = function (pathOrCommands) { 60 | if (pathOrCommands.commands) { 61 | pathOrCommands = pathOrCommands.commands; 62 | } 63 | Array.prototype.push.apply(this.commands, pathOrCommands); 64 | }; 65 | 66 | // Draw the path to a 2D context. 67 | Path.prototype.draw = function (ctx) { 68 | var i, cmd; 69 | ctx.beginPath(); 70 | for (i = 0; i < this.commands.length; i += 1) { 71 | cmd = this.commands[i]; 72 | if (cmd.type === 'M') { 73 | ctx.moveTo(cmd.x, cmd.y); 74 | } else if (cmd.type === 'L') { 75 | ctx.lineTo(cmd.x, cmd.y); 76 | } else if (cmd.type === 'C') { 77 | ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); 78 | } else if (cmd.type === 'Q') { 79 | ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); 80 | } else if (cmd.type === 'Z') { 81 | ctx.closePath(); 82 | } 83 | } 84 | if (this.fill) { 85 | ctx.fillStyle = this.fill; 86 | ctx.fill(); 87 | } 88 | if (this.stroke) { 89 | ctx.strokeStyle = this.stroke; 90 | ctx.lineWidth = this.strokeWidth; 91 | ctx.stroke(); 92 | } 93 | }; 94 | 95 | // Convert the Path to a string of path data instructions 96 | // See http://www.w3.org/TR/SVG/paths.html#PathData 97 | // Parameters: 98 | // - decimalPlaces: The amount of decimal places for floating-point values (default: 2) 99 | Path.prototype.toPathData = function (decimalPlaces) { 100 | decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; 101 | 102 | function floatToString(v) { 103 | if (Math.round(v) === v) { 104 | return '' + Math.round(v); 105 | } else { 106 | return v.toFixed(decimalPlaces); 107 | } 108 | } 109 | 110 | function packValues() { 111 | var s = ''; 112 | for (var i = 0; i < arguments.length; i += 1) { 113 | var v = arguments[i]; 114 | if (v > 0 && i > 0) { 115 | s += ' '; 116 | } 117 | s += floatToString(v); 118 | } 119 | return s; 120 | } 121 | 122 | var d = ''; 123 | for (var i = 0; i < this.commands.length; i += 1) { 124 | var cmd = this.commands[i]; 125 | if (cmd.type === 'M') { 126 | d += 'M' + packValues(cmd.x, cmd.y); 127 | } else if (cmd.type === 'L') { 128 | d += 'L' + packValues(cmd.x, cmd.y); 129 | } else if (cmd.type === 'C') { 130 | d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); 131 | } else if (cmd.type === 'Q') { 132 | d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); 133 | } else if (cmd.type === 'Z') { 134 | d += 'Z'; 135 | } 136 | } 137 | return d; 138 | }; 139 | 140 | // Convert the path to a SVG element, as a string. 141 | // Parameters: 142 | // - decimalPlaces: The amount of decimal places for floating-point values (default: 2) 143 | Path.prototype.toSVG = function (decimalPlaces) { 144 | var svg = '= 1) { 47 | os2.ulCodePageRange1 = p.parseULong(); 48 | os2.ulCodePageRange2 = p.parseULong(); 49 | } 50 | if (os2.version >= 2) { 51 | os2.sxHeight = p.parseShort(); 52 | os2.sCapHeight = p.parseShort(); 53 | os2.usDefaultChar = p.parseUShort(); 54 | os2.usBreakChar = p.parseUShort(); 55 | os2.usMaxContent = p.parseUShort(); 56 | } 57 | return os2; 58 | } 59 | 60 | function makeOS2Table(options) { 61 | return new table.Table('OS/2', [ 62 | {name: 'version', type: 'USHORT', value: 0x0003}, 63 | {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, 64 | {name: 'usWeightClass', type: 'USHORT', value: 0}, 65 | {name: 'usWidthClass', type: 'USHORT', value: 0}, 66 | {name: 'fsType', type: 'USHORT', value: 0}, 67 | {name: 'ySubscriptXSize', type: 'SHORT', value: 0}, 68 | {name: 'ySubscriptYSize', type: 'SHORT', value: 0}, 69 | {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, 70 | {name: 'ySubscriptYOffset', type: 'SHORT', value: 0}, 71 | {name: 'ySuperscriptXSize', type: 'SHORT', value: 0}, 72 | {name: 'ySuperscriptYSize', type: 'SHORT', value: 0}, 73 | {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, 74 | {name: 'ySuperscriptYOffset', type: 'SHORT', value: 0}, 75 | {name: 'yStrikeoutSize', type: 'SHORT', value: 0}, 76 | {name: 'yStrikeoutPosition', type: 'SHORT', value: 0}, 77 | {name: 'sFamilyClass', type: 'SHORT', value: 0}, 78 | {name: 'bFamilyType', type: 'BYTE', value: 0}, 79 | {name: 'bSerifStyle', type: 'BYTE', value: 0}, 80 | {name: 'bWeight', type: 'BYTE', value: 0}, 81 | {name: 'bProportion', type: 'BYTE', value: 0}, 82 | {name: 'bContrast', type: 'BYTE', value: 0}, 83 | {name: 'bStrokeVariation', type: 'BYTE', value: 0}, 84 | {name: 'bArmStyle', type: 'BYTE', value: 0}, 85 | {name: 'bLetterform', type: 'BYTE', value: 0}, 86 | {name: 'bMidline', type: 'BYTE', value: 0}, 87 | {name: 'bXHeight', type: 'BYTE', value: 0}, 88 | {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, 89 | {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, 90 | {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, 91 | {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, 92 | {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, 93 | {name: 'fsSelection', type: 'USHORT', value: 0}, 94 | {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, 95 | {name: 'usLastCharIndex', type: 'USHORT', value: 0}, 96 | {name: 'sTypoAscender', type: 'SHORT', value: 0}, 97 | {name: 'sTypoDescender', type: 'SHORT', value: 0}, 98 | {name: 'sTypoLineGap', type: 'SHORT', value: 0}, 99 | {name: 'usWinAscent', type: 'USHORT', value: 0}, 100 | {name: 'usWinDescent', type: 'USHORT', value: 0}, 101 | {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, 102 | {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, 103 | {name: 'sxHeight', type: 'SHORT', value: 0}, 104 | {name: 'sCapHeight', type: 'SHORT', value: 0}, 105 | {name: 'usDefaultChar', type: 'USHORT', value: 0}, 106 | {name: 'usBreakChar', type: 'USHORT', value: 0}, 107 | {name: 'usMaxContext', type: 'USHORT', value: 0} 108 | ], options); 109 | } 110 | 111 | exports.parse = parseOS2Table; 112 | exports.make = makeOS2Table; 113 | -------------------------------------------------------------------------------- /vendor/tables/name.js: -------------------------------------------------------------------------------- 1 | // The `name` naming table. 2 | // https://www.microsoft.com/typography/OTSPEC/name.htm 3 | 4 | 'use strict'; 5 | 6 | var encode = require('../types').encode; 7 | var parse = require('../parse'); 8 | var table = require('../table'); 9 | 10 | // NameIDs for the name table. 11 | var nameTableNames = [ 12 | 'copyright', // 0 13 | 'fontFamily', // 1 14 | 'fontSubfamily', // 2 15 | 'uniqueID', // 3 16 | 'fullName', // 4 17 | 'version', // 5 18 | 'postScriptName', // 6 19 | 'trademark', // 7 20 | 'manufacturer', // 8 21 | 'designer', // 9 22 | 'description', // 10 23 | 'manufacturerURL', // 11 24 | 'designerURL', // 12 25 | 'licence', // 13 26 | 'licenceURL', // 14 27 | 'reserved', // 15 28 | 'preferredFamily', // 16 29 | 'preferredSubfamily', // 17 30 | 'compatibleFullName', // 18 31 | 'sampleText', // 19 32 | 'postScriptFindFontName', // 20 33 | 'wwsFamily', // 21 34 | 'wwsSubfamily' // 22 35 | ]; 36 | 37 | // Parse the naming `name` table 38 | // Only Windows Unicode English names are supported. 39 | // Format 1 additional fields are not supported 40 | function parseNameTable(data, start) { 41 | var name = {}, 42 | p = new parse.Parser(data, start); 43 | name.format = p.parseUShort(); 44 | var count = p.parseUShort(), 45 | stringOffset = p.offset + p.parseUShort(); 46 | var platformID, encodingID, languageID, nameID, property, byteLength, 47 | offset, str, i, j, codePoints; 48 | var unknownCount = 0; 49 | for(i = 0; i < count; i++) { 50 | platformID = p.parseUShort(); 51 | encodingID = p.parseUShort(); 52 | languageID = p.parseUShort(); 53 | nameID = p.parseUShort(); 54 | property = nameTableNames[nameID]; 55 | byteLength = p.parseUShort(); 56 | offset = p.parseUShort(); 57 | // platformID - encodingID - languageID standard combinations : 58 | // 1 - 0 - 0 : Macintosh, Roman, English 59 | // 3 - 1 - 0x409 : Windows, Unicode BMP (UCS-2), en-US 60 | if (platformID === 3 && encodingID === 1 && languageID === 0x409) { 61 | codePoints = []; 62 | var length = byteLength/2; 63 | for(j = 0; j < length; j++, offset += 2) { 64 | codePoints[j] = parse.getShort(data, stringOffset+offset); 65 | } 66 | str = String.fromCharCode.apply(null, codePoints); 67 | if (property) { 68 | name[property] = str; 69 | } 70 | else { 71 | unknownCount++; 72 | name['unknown'+unknownCount] = str; 73 | } 74 | } 75 | 76 | } 77 | if (name.format === 1) { 78 | name.langTagCount = p.parseUShort(); 79 | } 80 | return name; 81 | } 82 | 83 | function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { 84 | return new table.Table('NameRecord', [ 85 | {name: 'platformID', type: 'USHORT', value: platformID}, 86 | {name: 'encodingID', type: 'USHORT', value: encodingID}, 87 | {name: 'languageID', type: 'USHORT', value: languageID}, 88 | {name: 'nameID', type: 'USHORT', value: nameID}, 89 | {name: 'length', type: 'USHORT', value: length}, 90 | {name: 'offset', type: 'USHORT', value: offset} 91 | ]); 92 | } 93 | 94 | function addMacintoshNameRecord(t, recordID, s, offset) { 95 | // Macintosh, Roman, English 96 | var stringBytes = encode.STRING(s); 97 | t.records.push(makeNameRecord(1, 0, 0, recordID, stringBytes.length, offset)); 98 | t.strings.push(stringBytes); 99 | offset += stringBytes.length; 100 | return offset; 101 | } 102 | 103 | function addWindowsNameRecord(t, recordID, s, offset) { 104 | // Windows, Unicode BMP (UCS-2), US English 105 | var utf16Bytes = encode.UTF16(s); 106 | t.records.push(makeNameRecord(3, 1, 0x0409, recordID, utf16Bytes.length, offset)); 107 | t.strings.push(utf16Bytes); 108 | offset += utf16Bytes.length; 109 | return offset; 110 | } 111 | 112 | function makeNameTable(options) { 113 | var i, s; 114 | var t = new table.Table('name', [ 115 | {name: 'format', type: 'USHORT', value: 0}, 116 | {name: 'count', type: 'USHORT', value: 0}, 117 | {name: 'stringOffset', type: 'USHORT', value: 0} 118 | ]); 119 | t.records = []; 120 | t.strings = []; 121 | var offset = 0; 122 | // Add Macintosh records first 123 | for (i = 0; i < nameTableNames.length; i += 1) { 124 | if (options[nameTableNames[i]] !== undefined) { 125 | s = options[nameTableNames[i]]; 126 | offset = addMacintoshNameRecord(t, i, s, offset); 127 | } 128 | } 129 | // Then add Windows records 130 | for (i = 0; i < nameTableNames.length; i += 1) { 131 | if (options[nameTableNames[i]] !== undefined) { 132 | s = options[nameTableNames[i]]; 133 | offset = addWindowsNameRecord(t, i, s, offset); 134 | } 135 | } 136 | 137 | t.count = t.records.length; 138 | t.stringOffset = 6 + t.count * 12; 139 | for (i = 0; i < t.records.length; i += 1) { 140 | t.fields.push({name: 'record_' + i, type: 'TABLE', value: t.records[i]}); 141 | } 142 | for (i = 0; i < t.strings.length; i += 1) { 143 | t.fields.push({name: 'string_' + i, type: 'LITERAL', value: t.strings[i]}); 144 | } 145 | return t; 146 | } 147 | 148 | exports.parse = parseNameTable; 149 | exports.make = makeNameTable; 150 | -------------------------------------------------------------------------------- /src/transform.js: -------------------------------------------------------------------------------- 1 | //transform code from https://github.com/kcmoot/transform-tracker/blob/master/transform-tracker.js 2 | 3 | 4 | /* 5 | * Transform tracker 6 | * 7 | * @author Kevin Moot 8 | * Based on a class created by Simon Sarris - www.simonsarris.com - sarris@acm.org 9 | */ 10 | 11 | "use strict"; 12 | 13 | function Transform(context) { 14 | this.context = context; 15 | this.matrix = [1,0,0,1,0,0]; //initialize with the identity matrix 16 | this.stack = []; 17 | 18 | //========================================== 19 | // Constructor, getter/setter 20 | //========================================== 21 | 22 | this.setContext = function(context) { 23 | this.context = context; 24 | }; 25 | 26 | this.getMatrix = function() { 27 | return this.matrix; 28 | }; 29 | 30 | this.setMatrix = function(m) { 31 | this.matrix = [m[0],m[1],m[2],m[3],m[4],m[5]]; 32 | this.setTransform(); 33 | }; 34 | 35 | this.cloneMatrix = function(m) { 36 | return [m[0],m[1],m[2],m[3],m[4],m[5]]; 37 | }; 38 | 39 | //========================================== 40 | // Stack 41 | //========================================== 42 | 43 | this.save = function() { 44 | var matrix = this.cloneMatrix(this.getMatrix()); 45 | this.stack.push(matrix); 46 | 47 | if (this.context) this.context.save(); 48 | }; 49 | 50 | this.restore = function() { 51 | if (this.stack.length > 0) { 52 | var matrix = this.stack.pop(); 53 | this.setMatrix(matrix); 54 | } 55 | 56 | if (this.context) this.context.restore(); 57 | }; 58 | 59 | //========================================== 60 | // Matrix 61 | //========================================== 62 | 63 | this.setTransform = function() { 64 | if (this.context) { 65 | this.context.setTransform( 66 | this.matrix[0], 67 | this.matrix[1], 68 | this.matrix[2], 69 | this.matrix[3], 70 | this.matrix[4], 71 | this.matrix[5] 72 | ); 73 | } 74 | }; 75 | 76 | this.translate = function(x, y) { 77 | this.matrix[4] += this.matrix[0] * x + this.matrix[2] * y; 78 | this.matrix[5] += this.matrix[1] * x + this.matrix[3] * y; 79 | 80 | this.setTransform(); 81 | }; 82 | 83 | this.rotate = function(rad) { 84 | var c = Math.cos(rad); 85 | var s = Math.sin(rad); 86 | var m11 = this.matrix[0] * c + this.matrix[2] * s; 87 | var m12 = this.matrix[1] * c + this.matrix[3] * s; 88 | var m21 = this.matrix[0] * -s + this.matrix[2] * c; 89 | var m22 = this.matrix[1] * -s + this.matrix[3] * c; 90 | this.matrix[0] = m11; 91 | this.matrix[1] = m12; 92 | this.matrix[2] = m21; 93 | this.matrix[3] = m22; 94 | 95 | this.setTransform(); 96 | }; 97 | 98 | this.scale = function(sx, sy) { 99 | this.matrix[0] *= sx; 100 | this.matrix[1] *= sx; 101 | this.matrix[2] *= sy; 102 | this.matrix[3] *= sy; 103 | 104 | this.setTransform(); 105 | }; 106 | 107 | //========================================== 108 | // Matrix extensions 109 | //========================================== 110 | 111 | this.rotateDegrees = function(deg) { 112 | var rad = deg * Math.PI / 180; 113 | this.rotate(rad); 114 | }; 115 | 116 | this.rotateAbout = function(rad, x, y) { 117 | this.translate(x, y); 118 | this.rotate(rad); 119 | this.translate(-x, -y); 120 | this.setTransform(); 121 | } 122 | 123 | this.rotateDegreesAbout = function(deg, x, y) { 124 | this.translate(x, y); 125 | this.rotateDegrees(deg); 126 | this.translate(-x, -y); 127 | this.setTransform(); 128 | } 129 | 130 | this.identity = function() { 131 | this.m = [1,0,0,1,0,0]; 132 | this.setTransform(); 133 | }; 134 | 135 | this.multiply = function(matrix) { 136 | var m11 = this.matrix[0] * matrix.m[0] + this.matrix[2] * matrix.m[1]; 137 | var m12 = this.matrix[1] * matrix.m[0] + this.matrix[3] * matrix.m[1]; 138 | 139 | var m21 = this.matrix[0] * matrix.m[2] + this.matrix[2] * matrix.m[3]; 140 | var m22 = this.matrix[1] * matrix.m[2] + this.matrix[3] * matrix.m[3]; 141 | 142 | var dx = this.matrix[0] * matrix.m[4] + this.matrix[2] * matrix.m[5] + this.matrix[4]; 143 | var dy = this.matrix[1] * matrix.m[4] + this.matrix[3] * matrix.m[5] + this.matrix[5]; 144 | 145 | this.matrix[0] = m11; 146 | this.matrix[1] = m12; 147 | this.matrix[2] = m21; 148 | this.matrix[3] = m22; 149 | this.matrix[4] = dx; 150 | this.matrix[5] = dy; 151 | this.setTransform(); 152 | }; 153 | 154 | this.invert = function() { 155 | var d = 1 / (this.matrix[0] * this.matrix[3] - this.matrix[1] * this.matrix[2]); 156 | var m0 = this.matrix[3] * d; 157 | var m1 = -this.matrix[1] * d; 158 | var m2 = -this.matrix[2] * d; 159 | var m3 = this.matrix[0] * d; 160 | var m4 = d * (this.matrix[2] * this.matrix[5] - this.matrix[3] * this.matrix[4]); 161 | var m5 = d * (this.matrix[1] * this.matrix[4] - this.matrix[0] * this.matrix[5]); 162 | this.matrix[0] = m0; 163 | this.matrix[1] = m1; 164 | this.matrix[2] = m2; 165 | this.matrix[3] = m3; 166 | this.matrix[4] = m4; 167 | this.matrix[5] = m5; 168 | this.setTransform(); 169 | }; 170 | 171 | //========================================== 172 | // Helpers 173 | //========================================== 174 | 175 | this.transformPoint = function(pt) { 176 | var x = pt.x; 177 | var y = pt.y; 178 | return { 179 | x: x * this.matrix[0] + y * this.matrix[2] + this.matrix[4], 180 | y: x * this.matrix[1] + y * this.matrix[3] + this.matrix[5] 181 | }; 182 | }; 183 | } 184 | 185 | exports.Transform = Transform; 186 | -------------------------------------------------------------------------------- /src/pureimage.js: -------------------------------------------------------------------------------- 1 | // Pure Image uses existing libraries for font parsing, jpeg/png encode/decode 2 | // and borrowed code for transform management and unsigned integer manipulation 3 | 4 | //2014-11-14 line count: 418, 411, 407, 376, 379, 367 5 | //2014-11-15 line count: 401, 399, 386, 369, 349, 6 | 7 | 8 | var fs = require('fs'); 9 | var PNG = require('pngjs').PNG; 10 | var JPEG = require('jpeg-js'); 11 | var uint32 = require('./uint32'); 12 | var Bitmap = require('./bitmap'); 13 | var text = require('./text'); 14 | 15 | 16 | exports.make = function(w,h,options) { 17 | return new Bitmap(w,h,options); 18 | }; 19 | 20 | exports.encodePNGToStream = function(bitmap, outstream) { 21 | return new Promise((res,rej)=>{ 22 | var png = new PNG({ 23 | width:bitmap.width, 24 | height:bitmap.height 25 | }); 26 | 27 | for(var i=0; i{ res(); }) 41 | .on('error', (err) => { rej(err); }) 42 | }); 43 | } 44 | 45 | exports.encodePNGToBuffer = function(bitmap) { 46 | return new Promise((res,rej)=>{ 47 | var png = new PNG({ 48 | width:bitmap.width, 49 | height:bitmap.height 50 | }); 51 | 52 | for(var i=0; i { 69 | var data = { 70 | data: img.data, 71 | width: img.width, 72 | height: img.height 73 | }; 74 | outstream.on('error', (err) => rej(err)); 75 | outstream.write(JPEG.encode(data, 50).data, () => res()); 76 | }); 77 | }; 78 | 79 | exports.decodeJPEGFromBuffer = function(buf) { 80 | return new Promise((res,rej)=>{ 81 | try { 82 | var rawImageData = JPEG.decode(buf); 83 | var bitmap = new Bitmap(rawImageData.width, rawImageData.height); 84 | for (var i = 0; i < rawImageData.width; i++) { 85 | for (var j = 0; j < rawImageData.height; j++) { 86 | var n = (j * rawImageData.width + i) * 4; 87 | bitmap.setPixelRGBA_i(i, j, 88 | rawImageData.data[n + 0], 89 | rawImageData.data[n + 1], 90 | rawImageData.data[n + 2], 91 | rawImageData.data[n + 3] 92 | ); 93 | } 94 | } 95 | res(bitmap); 96 | } catch (e) { 97 | console.log(e); 98 | rej(e); 99 | } 100 | }); 101 | }; 102 | 103 | exports.decodeJPEGFromStream = function(data) { 104 | return new Promise((res,rej)=>{ 105 | try { 106 | var chunks = []; 107 | data.on('data',(chunk)=>{ 108 | chunks.push(chunk); 109 | }); 110 | data.on('end',()=>{ 111 | var buf = Buffer.concat(chunks); 112 | var rawImageData = JPEG.decode(buf); 113 | var bitmap = new Bitmap(rawImageData.width, rawImageData.height); 114 | for (var i = 0; i < rawImageData.width; i++) { 115 | for (var j = 0; j < rawImageData.height; j++) { 116 | var n = (j * rawImageData.width + i) * 4; 117 | bitmap.setPixelRGBA_i(i, j, 118 | rawImageData.data[n + 0], 119 | rawImageData.data[n + 1], 120 | rawImageData.data[n + 2], 121 | rawImageData.data[n + 3] 122 | ); 123 | } 124 | } 125 | res(bitmap); 126 | }); 127 | } catch (e) { 128 | console.log(e); 129 | rej(e); 130 | } 131 | }); 132 | }; 133 | 134 | exports.decodeJPEGFromBuffer = function(buf) { 135 | return new Promise((res,rej)=>{ 136 | try { 137 | var rawImageData = JPEG.decode(buf); 138 | var bitmap = new Bitmap(rawImageData.width, rawImageData.height); 139 | for (var i = 0; i < rawImageData.width; i++) { 140 | for (var j = 0; j < rawImageData.height; j++) { 141 | var n = (j * rawImageData.width + i) * 4; 142 | bitmap.setPixelRGBA_i(i, j, 143 | rawImageData.data[n + 0], 144 | rawImageData.data[n + 1], 145 | rawImageData.data[n + 2], 146 | rawImageData.data[n + 3] 147 | ); 148 | } 149 | } 150 | res(bitmap); 151 | } catch (e) { 152 | console.log(e); 153 | rej(e); 154 | } 155 | }); 156 | }; 157 | 158 | exports.decodePNGFromStream = function(instream) { 159 | return new Promise((res,rej)=>{ 160 | instream.pipe(new PNG()) 161 | .on("parsed", function() { 162 | var bitmap = new Bitmap(this.width,this.height); 163 | for(var i=0; i{ 173 | var png = new PNG(); 174 | png.parse(buf) 175 | .on("parsed", function() { 176 | var bitmap = new Bitmap(this.width,this.height); 177 | for(var i=0; i { 115 | var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro'); 116 | fnt.load(function() { 117 | var img = PImage.make(200,200); 118 | var ctx = img.getContext('2d'); 119 | ctx.fillStyle = '#ffffff'; 120 | ctx.font = "48pt 'Source Sans Pro'"; 121 | ctx.fillText("ABC", 80, 80); 122 | }); 123 | }); 124 | ``` 125 | 126 | 127 | Write out to a PNG file 128 | 129 | ``` 130 | PImage.encodePNGToStream(img1, fs.createWriteStream('out.png')).then(()=> { 131 | console.log("wrote out the png file to out.png"); 132 | }).catch((e)=>{ 133 | console.log("there was an error writing"); 134 | }); 135 | ``` 136 | 137 | Read a jpeg, resize it, then save it out 138 | 139 | ``` 140 | PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img)=>{ 141 | console.log("size is",img.width,img.height); 142 | var img2 = PImage.make(50,50); 143 | var c = img2.getContext('2d'); 144 | c.drawImage(img, 145 | 0, 0, img.width, img.height, // source dimensions 146 | 0, 0, 50, 50 // destination dimensions 147 | ); 148 | var pth = path.join(BUILD_DIR,"resized_bird.jpg"); 149 | PImage.encodeJPEGToStream(img2,fs.createWriteStream(pth)).then(()=> { 150 | console.log("done writing"); 151 | }); 152 | ``` 153 | 154 | Or, if you already have an image stored as a buffer, you can use: 155 | 156 | ``` 157 | PImage.decodeJPEGFromBuffer(buffer).then((img) => { 158 | 159 | // do stuff with the image here. 160 | 161 | }) 162 | ``` 163 | 164 | 165 | Thanks! 166 | =============== 167 | 168 | Thanks to Nodebox / EMRG for [opentype.js](https://github.com/nodebox/opentype.js/) 169 | 170 | Thanks to Rosetta Code for [Bresenham's in JS](http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#JavaScript) 171 | 172 | Thanks to Kuba Niegowski for [PngJS](https://github.com/niegowski/node-pngjs) 173 | 174 | Thanks to Eugene Ware for [jpeg-js]( https://github.com/eugeneware/jpeg-js ) 175 | 176 | Thanks for patches from: 177 | 178 | * Dan [danielbarela](https://github.com/danielbarela) 179 | * Eugene Kulabuhov [ekulabuhov](https://github.com/ekulabuhov) 180 | * Lethexa [lethexa](https://github.com/lethexa) 181 | * The Louie [the-louie](https://github.com/the-louie) 182 | * Jan Marsch [kekscom](https://github.com/kekscom) 183 | * 184 | -------------------------------------------------------------------------------- /vendor/parse.js: -------------------------------------------------------------------------------- 1 | // Parsing utility functions 2 | 3 | 'use strict'; 4 | 5 | // Retrieve an unsigned byte from the DataView. 6 | exports.getByte = function getByte(dataView, offset) { 7 | return dataView.getUint8(offset); 8 | }; 9 | 10 | exports.getCard8 = exports.getByte; 11 | 12 | // Retrieve an unsigned 16-bit short from the DataView. 13 | // The value is stored in big endian. 14 | exports.getUShort = function (dataView, offset) { 15 | return dataView.getUint16(offset, false); 16 | }; 17 | 18 | exports.getCard16 = exports.getUShort; 19 | 20 | // Retrieve a signed 16-bit short from the DataView. 21 | // The value is stored in big endian. 22 | exports.getShort = function (dataView, offset) { 23 | return dataView.getInt16(offset, false); 24 | }; 25 | 26 | // Retrieve an unsigned 32-bit long from the DataView. 27 | // The value is stored in big endian. 28 | exports.getULong = function (dataView, offset) { 29 | return dataView.getUint32(offset, false); 30 | }; 31 | 32 | // Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. 33 | // The value is stored in big endian. 34 | exports.getFixed = function (dataView, offset) { 35 | var decimal, fraction; 36 | decimal = dataView.getInt16(offset, false); 37 | fraction = dataView.getUint16(offset + 2, false); 38 | return decimal + fraction / 65535; 39 | }; 40 | 41 | // Retrieve a 4-character tag from the DataView. 42 | // Tags are used to identify tables. 43 | exports.getTag = function (dataView, offset) { 44 | var tag = '', i; 45 | for (i = offset; i < offset + 4; i += 1) { 46 | tag += String.fromCharCode(dataView.getInt8(i)); 47 | } 48 | return tag; 49 | }; 50 | 51 | // Retrieve an offset from the DataView. 52 | // Offsets are 1 to 4 bytes in length, depending on the offSize argument. 53 | exports.getOffset = function (dataView, offset, offSize) { 54 | var i, v; 55 | v = 0; 56 | for (i = 0; i < offSize; i += 1) { 57 | v <<= 8; 58 | v += dataView.getUint8(offset + i); 59 | } 60 | return v; 61 | }; 62 | 63 | // Retrieve a number of bytes from start offset to the end offset from the DataView. 64 | exports.getBytes = function (dataView, startOffset, endOffset) { 65 | var bytes, i; 66 | bytes = []; 67 | for (i = startOffset; i < endOffset; i += 1) { 68 | bytes.push(dataView.getUint8(i)); 69 | } 70 | return bytes; 71 | }; 72 | 73 | // Convert the list of bytes to a string. 74 | exports.bytesToString = function (bytes) { 75 | var s, i; 76 | s = ''; 77 | for (i = 0; i < bytes.length; i += 1) { 78 | s += String.fromCharCode(bytes[i]); 79 | } 80 | return s; 81 | }; 82 | 83 | var typeOffsets = { 84 | byte: 1, 85 | uShort: 2, 86 | short: 2, 87 | uLong: 4, 88 | fixed: 4, 89 | longDateTime: 8, 90 | tag: 4 91 | }; 92 | 93 | // A stateful parser that changes the offset whenever a value is retrieved. 94 | // The data is a DataView. 95 | function Parser(data, offset) { 96 | this.data = data; 97 | this.offset = offset; 98 | this.relativeOffset = 0; 99 | } 100 | 101 | Parser.prototype.parseByte = function () { 102 | var v = this.data.getUint8(this.offset + this.relativeOffset); 103 | this.relativeOffset += 1; 104 | return v; 105 | }; 106 | 107 | Parser.prototype.parseChar = function () { 108 | var v = this.data.getInt8(this.offset + this.relativeOffset); 109 | this.relativeOffset += 1; 110 | return v; 111 | }; 112 | 113 | Parser.prototype.parseCard8 = Parser.prototype.parseByte; 114 | 115 | Parser.prototype.parseUShort = function () { 116 | var v = this.data.getUint16(this.offset + this.relativeOffset); 117 | this.relativeOffset += 2; 118 | return v; 119 | }; 120 | Parser.prototype.parseCard16 = Parser.prototype.parseUShort; 121 | Parser.prototype.parseSID = Parser.prototype.parseUShort; 122 | Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; 123 | 124 | Parser.prototype.parseShort = function () { 125 | var v = this.data.getInt16(this.offset + this.relativeOffset); 126 | this.relativeOffset += 2; 127 | return v; 128 | }; 129 | 130 | Parser.prototype.parseF2Dot14 = function () { 131 | var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; 132 | this.relativeOffset += 2; 133 | return v; 134 | }; 135 | 136 | Parser.prototype.parseULong = function () { 137 | var v = exports.getULong(this.data, this.offset + this.relativeOffset); 138 | this.relativeOffset += 4; 139 | return v; 140 | }; 141 | 142 | Parser.prototype.parseFixed = function () { 143 | var v = exports.getFixed(this.data, this.offset + this.relativeOffset); 144 | this.relativeOffset += 4; 145 | return v; 146 | }; 147 | 148 | Parser.prototype.parseOffset16List = 149 | Parser.prototype.parseUShortList = function (count) { 150 | var offsets = new Array(count), 151 | dataView = this.data, 152 | offset = this.offset + this.relativeOffset; 153 | for (var i = 0; i < count; i++) { 154 | offsets[i] = exports.getUShort(dataView, offset); 155 | offset += 2; 156 | } 157 | this.relativeOffset += count * 2; 158 | return offsets; 159 | }; 160 | 161 | Parser.prototype.parseString = function (length) { 162 | var dataView = this.data, 163 | offset = this.offset + this.relativeOffset, 164 | string = ''; 165 | this.relativeOffset += length; 166 | for (var i = 0; i < length; i++) { 167 | string += String.fromCharCode(dataView.getUint8(offset + i)); 168 | } 169 | return string; 170 | }; 171 | 172 | Parser.prototype.parseTag = function () { 173 | return this.parseString(4); 174 | }; 175 | 176 | // LONGDATETIME is a 64-bit integer. 177 | // JavaScript and unix timestamps traditionally use 32 bits, so we 178 | // only take the last 32 bits. 179 | Parser.prototype.parseLongDateTime = function() { 180 | var v = exports.getULong(this.data, this.offset + this.relativeOffset + 4); 181 | this.relativeOffset += 8; 182 | return v; 183 | }; 184 | 185 | Parser.prototype.parseFixed = function() { 186 | var v = exports.getULong(this.data, this.offset + this.relativeOffset); 187 | this.relativeOffset += 4; 188 | return v / 65536; 189 | }; 190 | 191 | Parser.prototype.parseVersion = function() { 192 | var major = exports.getUShort(this.data, this.offset + this.relativeOffset); 193 | // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 194 | // This returns the correct number if minor = 0xN000 where N is 0-9 195 | var minor = exports.getUShort(this.data, this.offset + this.relativeOffset + 2); 196 | this.relativeOffset += 4; 197 | return major + minor / 0x1000 / 10; 198 | }; 199 | 200 | Parser.prototype.skip = function (type, amount) { 201 | if (amount === undefined) { 202 | amount = 1; 203 | } 204 | this.relativeOffset += typeOffsets[type] * amount; 205 | }; 206 | 207 | exports.Parser = Parser; 208 | -------------------------------------------------------------------------------- /vendor/tables/cmap.js: -------------------------------------------------------------------------------- 1 | // The `cmap` table stores the mappings from characters to glyphs. 2 | // https://www.microsoft.com/typography/OTSPEC/cmap.htm 3 | 4 | 'use strict'; 5 | 6 | var check = require('../check'); 7 | var parse = require('../parse'); 8 | var table = require('../table'); 9 | 10 | // Parse the `cmap` table. This table stores the mappings from characters to glyphs. 11 | // There are many available formats, but we only support the Windows format 4. 12 | // This function returns a `CmapEncoding` object or null if no supported format could be found. 13 | function parseCmapTable(data, start) { 14 | var version, numTables, offset, platformId, encodingId, format, segCount, 15 | endCountParser, startCountParser, idDeltaParser, idRangeOffsetParser, glyphIndexOffset, 16 | endCount, startCount, i, c, idDelta, idRangeOffset, p, glyphIndex; 17 | var cmap = {}; 18 | cmap.version = version = parse.getUShort(data, start); 19 | check.argument(version === 0, 'cmap table version should be 0.'); 20 | 21 | // The cmap table can contain many sub-tables, each with their own format. 22 | // We're only interested in a "platform 3" table. This is a Windows format. 23 | cmap.numtables = numTables = parse.getUShort(data, start + 2); 24 | offset = -1; 25 | for (i = 0; i < numTables; i += 1) { 26 | platformId = parse.getUShort(data, start + 4 + (i * 8)); 27 | encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); 28 | if (platformId === 3 && (encodingId === 1 || encodingId === 0)) { 29 | offset = parse.getULong(data, start + 4 + (i * 8) + 4); 30 | break; 31 | } 32 | } 33 | if (offset === -1) { 34 | // There is no cmap table in the font that we support, so return null. 35 | // This font will be marked as unsupported. 36 | return null; 37 | } 38 | 39 | p = new parse.Parser(data, start + offset); 40 | cmap.format = format = p.parseUShort(); 41 | check.argument(format === 4, 'Only format 4 cmap tables are supported.'); 42 | // Length in bytes of the sub-tables. 43 | cmap.length = p.parseUShort(); 44 | cmap.language = p.parseUShort(); 45 | // segCount is stored x 2. 46 | cmap.segCount = segCount = p.parseUShort() >> 1; 47 | // Skip searchRange, entrySelector, rangeShift. 48 | p.skip('uShort', 3); 49 | 50 | // The "unrolled" mapping from character codes to glyph indices. 51 | cmap.glyphIndexMap = {}; 52 | 53 | endCountParser = new parse.Parser(data, start + offset + 14); 54 | startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); 55 | idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); 56 | idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); 57 | glyphIndexOffset = start + offset + 16 + segCount * 8; 58 | for (i = 0; i < segCount - 1; i += 1) { 59 | endCount = endCountParser.parseUShort(); 60 | startCount = startCountParser.parseUShort(); 61 | idDelta = idDeltaParser.parseShort(); 62 | idRangeOffset = idRangeOffsetParser.parseUShort(); 63 | for (c = startCount; c <= endCount; c += 1) { 64 | if (idRangeOffset !== 0) { 65 | // The idRangeOffset is relative to the current position in the idRangeOffset array. 66 | // Take the current offset in the idRangeOffset array. 67 | glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); 68 | // Add the value of the idRangeOffset, which will move us into the glyphIndex array. 69 | glyphIndexOffset += idRangeOffset; 70 | // Then add the character index of the current segment, multiplied by 2 for USHORTs. 71 | glyphIndexOffset += (c - startCount) * 2; 72 | glyphIndex = parse.getUShort(data, glyphIndexOffset); 73 | if (glyphIndex !== 0) { 74 | glyphIndex = (glyphIndex + idDelta) & 0xFFFF; 75 | } 76 | } else { 77 | glyphIndex = (c + idDelta) & 0xFFFF; 78 | } 79 | cmap.glyphIndexMap[c] = glyphIndex; 80 | } 81 | } 82 | return cmap; 83 | } 84 | 85 | function addSegment(t, code, glyphIndex) { 86 | t.segments.push({ 87 | end: code, 88 | start: code, 89 | delta: -(code - glyphIndex), 90 | offset: 0 91 | }); 92 | } 93 | 94 | function addTerminatorSegment(t) { 95 | t.segments.push({ 96 | end: 0xFFFF, 97 | start: 0xFFFF, 98 | delta: 1, 99 | offset: 0 100 | }); 101 | } 102 | 103 | function makeCmapTable(glyphs) { 104 | var i, j, glyph; 105 | var t = new table.Table('cmap', [ 106 | {name: 'version', type: 'USHORT', value: 0}, 107 | {name: 'numTables', type: 'USHORT', value: 1}, 108 | {name: 'platformID', type: 'USHORT', value: 3}, 109 | {name: 'encodingID', type: 'USHORT', value: 1}, 110 | {name: 'offset', type: 'ULONG', value: 12}, 111 | {name: 'format', type: 'USHORT', value: 4}, 112 | {name: 'length', type: 'USHORT', value: 0}, 113 | {name: 'language', type: 'USHORT', value: 0}, 114 | {name: 'segCountX2', type: 'USHORT', value: 0}, 115 | {name: 'searchRange', type: 'USHORT', value: 0}, 116 | {name: 'entrySelector', type: 'USHORT', value: 0}, 117 | {name: 'rangeShift', type: 'USHORT', value: 0} 118 | ]); 119 | 120 | t.segments = []; 121 | for (i = 0; i < glyphs.length; i += 1) { 122 | glyph = glyphs[i]; 123 | for (j = 0; j < glyph.unicodes.length; j += 1) { 124 | addSegment(t, glyph.unicodes[j], i); 125 | } 126 | } 127 | addTerminatorSegment(t); 128 | 129 | var segCount; 130 | segCount = t.segments.length; 131 | t.segCountX2 = segCount * 2; 132 | t.searchRange = Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2))) * 2; 133 | t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); 134 | t.rangeShift = t.segCountX2 - t.searchRange; 135 | 136 | // Set up parallel segment arrays. 137 | var endCounts = [], 138 | startCounts = [], 139 | idDeltas = [], 140 | idRangeOffsets = [], 141 | glyphIds = []; 142 | 143 | for (i = 0; i < segCount; i += 1) { 144 | var segment = t.segments[i]; 145 | endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); 146 | startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); 147 | idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); 148 | idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); 149 | if (segment.glyphId !== undefined) { 150 | glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); 151 | } 152 | } 153 | t.fields = t.fields.concat(endCounts); 154 | t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); 155 | t.fields = t.fields.concat(startCounts); 156 | t.fields = t.fields.concat(idDeltas); 157 | t.fields = t.fields.concat(idRangeOffsets); 158 | t.fields = t.fields.concat(glyphIds); 159 | 160 | t.length = 14 + // Subtable header 161 | endCounts.length * 2 + 162 | 2 + // reservedPad 163 | startCounts.length * 2 + 164 | idDeltas.length * 2 + 165 | idRangeOffsets.length * 2 + 166 | glyphIds.length * 2; 167 | return t; 168 | } 169 | 170 | exports.parse = parseCmapTable; 171 | exports.make = makeCmapTable; 172 | -------------------------------------------------------------------------------- /vendor/opentype.js: -------------------------------------------------------------------------------- 1 | // opentype.js 2 | // https://github.com/nodebox/opentype.js 3 | // (c) 2014 Frederik De Bleser 4 | // opentype.js may be freely distributed under the MIT license. 5 | 6 | /* global ArrayBuffer, DataView, Uint8Array, XMLHttpRequest */ 7 | 8 | 'use strict'; 9 | 10 | var encoding = require('./encoding'); 11 | var _font = require('./font'); 12 | var glyph = require('./glyph'); 13 | var parse = require('./parse'); 14 | var path = require('./path'); 15 | 16 | var cmap = require('./tables/cmap'); 17 | var cff = require('./tables/cff'); 18 | var glyf = require('./tables/glyf'); 19 | var gpos = require('./tables/gpos'); 20 | var head = require('./tables/head'); 21 | var hhea = require('./tables/hhea'); 22 | var hmtx = require('./tables/hmtx'); 23 | var kern = require('./tables/kern'); 24 | var loca = require('./tables/loca'); 25 | var maxp = require('./tables/maxp'); 26 | var _name = require('./tables/name'); 27 | var os2 = require('./tables/os2'); 28 | var post = require('./tables/post'); 29 | 30 | // File loaders ///////////////////////////////////////////////////////// 31 | 32 | // Convert a Node.js Buffer to an ArrayBuffer 33 | function toArrayBuffer(buffer) { 34 | var i, 35 | arrayBuffer = new ArrayBuffer(buffer.length), 36 | data = new Uint8Array(arrayBuffer); 37 | 38 | for (i = 0; i < buffer.length; i += 1) { 39 | data[i] = buffer[i]; 40 | } 41 | 42 | return arrayBuffer; 43 | } 44 | 45 | function loadFromFile(path, callback) { 46 | var fs = require('fs'); 47 | fs.readFile(path, function (err, buffer) { 48 | if (err) { 49 | return callback(err.message); 50 | } 51 | 52 | callback(null, toArrayBuffer(buffer)); 53 | }); 54 | } 55 | 56 | function loadFromUrl(url, callback) { 57 | var request = new XMLHttpRequest(); 58 | request.open('get', url, true); 59 | request.responseType = 'arraybuffer'; 60 | request.onload = function () { 61 | if (request.status !== 200) { 62 | return callback('Font could not be loaded: ' + request.statusText); 63 | } 64 | return callback(null, request.response); 65 | }; 66 | request.send(); 67 | } 68 | 69 | // Public API /////////////////////////////////////////////////////////// 70 | 71 | // Parse the OpenType file data (as an ArrayBuffer) and return a Font object. 72 | // If the file could not be parsed (most likely because it contains Postscript outlines) 73 | // we return an empty Font object with the `supported` flag set to `false`. 74 | function parseBuffer(buffer) { 75 | var font, data, version, numTables, i, p, tag, offset, hmtxOffset, glyfOffset, locaOffset, 76 | cffOffset, kernOffset, gposOffset, indexToLocFormat, numGlyphs, locaTable, 77 | shortVersion; 78 | // OpenType fonts use big endian byte ordering. 79 | // We can't rely on typed array view types, because they operate with the endianness of the host computer. 80 | // Instead we use DataViews where we can specify endianness. 81 | 82 | font = new _font.Font(); 83 | data = new DataView(buffer, 0); 84 | 85 | version = parse.getFixed(data, 0); 86 | if (version === 1.0) { 87 | font.outlinesFormat = 'truetype'; 88 | } else { 89 | version = parse.getTag(data, 0); 90 | if (version === 'OTTO') { 91 | font.outlinesFormat = 'cff'; 92 | } else { 93 | throw new Error('Unsupported OpenType version ' + version); 94 | } 95 | } 96 | 97 | numTables = parse.getUShort(data, 4); 98 | 99 | // Offset into the table records. 100 | p = 12; 101 | for (i = 0; i < numTables; i += 1) { 102 | tag = parse.getTag(data, p); 103 | offset = parse.getULong(data, p + 8); 104 | switch (tag) { 105 | case 'cmap': 106 | font.tables.cmap = cmap.parse(data, offset); 107 | font.encoding = new encoding.CmapEncoding(font.tables.cmap); 108 | if (!font.encoding) { 109 | font.supported = false; 110 | } 111 | break; 112 | case 'head': 113 | font.tables.head = head.parse(data, offset); 114 | font.unitsPerEm = font.tables.head.unitsPerEm; 115 | indexToLocFormat = font.tables.head.indexToLocFormat; 116 | break; 117 | case 'hhea': 118 | font.tables.hhea = hhea.parse(data, offset); 119 | font.ascender = font.tables.hhea.ascender; 120 | font.descender = font.tables.hhea.descender; 121 | font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; 122 | break; 123 | case 'hmtx': 124 | hmtxOffset = offset; 125 | break; 126 | case 'maxp': 127 | font.tables.maxp = maxp.parse(data, offset); 128 | font.numGlyphs = numGlyphs = font.tables.maxp.numGlyphs; 129 | break; 130 | case 'name': 131 | font.tables.name = _name.parse(data, offset); 132 | font.familyName = font.tables.name.fontFamily; 133 | font.styleName = font.tables.name.fontSubfamily; 134 | break; 135 | case 'OS/2': 136 | font.tables.os2 = os2.parse(data, offset); 137 | break; 138 | case 'post': 139 | font.tables.post = post.parse(data, offset); 140 | font.glyphNames = new encoding.GlyphNames(font.tables.post); 141 | break; 142 | case 'glyf': 143 | glyfOffset = offset; 144 | break; 145 | case 'loca': 146 | locaOffset = offset; 147 | break; 148 | case 'CFF ': 149 | cffOffset = offset; 150 | break; 151 | case 'kern': 152 | kernOffset = offset; 153 | break; 154 | case 'GPOS': 155 | gposOffset = offset; 156 | break; 157 | } 158 | p += 16; 159 | } 160 | 161 | if (glyfOffset && locaOffset) { 162 | shortVersion = indexToLocFormat === 0; 163 | locaTable = loca.parse(data, locaOffset, numGlyphs, shortVersion); 164 | font.glyphs = glyf.parse(data, glyfOffset, locaTable, font); 165 | hmtx.parse(data, hmtxOffset, font.numberOfHMetrics, font.numGlyphs, font.glyphs); 166 | encoding.addGlyphNames(font); 167 | } else if (cffOffset) { 168 | cff.parse(data, cffOffset, font); 169 | encoding.addGlyphNames(font); 170 | } else { 171 | font.supported = false; 172 | } 173 | 174 | if (font.supported) { 175 | if (kernOffset) { 176 | font.kerningPairs = kern.parse(data, kernOffset); 177 | } else { 178 | font.kerningPairs = {}; 179 | } 180 | if (gposOffset) { 181 | gpos.parse(data, gposOffset, font); 182 | } 183 | } 184 | 185 | return font; 186 | } 187 | 188 | // Asynchronously load the font from a URL or a filesystem. When done, call the callback 189 | // with two arguments `(err, font)`. The `err` will be null on success, 190 | // the `font` is a Font object. 191 | // 192 | // We use the node.js callback convention so that 193 | // opentype.js can integrate with frameworks like async.js. 194 | function load(url, callback) { 195 | var isNode = typeof window === 'undefined'; 196 | var loadFn = isNode ? loadFromFile : loadFromUrl; 197 | loadFn(url, function (err, arrayBuffer) { 198 | if (err) { 199 | return callback(err); 200 | } 201 | var font = parseBuffer(arrayBuffer); 202 | if (!font.supported) { 203 | return callback('Font is not supported (is this a Postscript font?)'); 204 | } 205 | return callback(null, font); 206 | }); 207 | } 208 | 209 | exports.Font = _font.Font; 210 | exports.Glyph = glyph.Glyph; 211 | exports.Path = path.Path; 212 | exports.parse = parseBuffer; 213 | exports.load = load; 214 | -------------------------------------------------------------------------------- /vendor/glyph.js: -------------------------------------------------------------------------------- 1 | // The Glyph object 2 | 3 | 'use strict'; 4 | 5 | var check = require('./check'); 6 | var draw = require('./draw'); 7 | var path = require('./path'); 8 | 9 | // A Glyph is an individual mark that often corresponds to a character. 10 | // Some glyphs, such as ligatures, are a combination of many characters. 11 | // Glyphs are the basic building blocks of a font. 12 | // 13 | // The `Glyph` class contains utility methods for drawing the path and its points. 14 | function Glyph(options) { 15 | this.font = options.font || null; 16 | this.index = options.index || 0; 17 | this.name = options.name || null; 18 | this.unicode = options.unicode || undefined; 19 | this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; 20 | this.xMin = options.xMin || 0; 21 | this.yMin = options.yMin || 0; 22 | this.xMax = options.xMax || 0; 23 | this.yMax = options.yMax || 0; 24 | this.advanceWidth = options.advanceWidth || 0; 25 | this.path = options.path || null; 26 | } 27 | 28 | Glyph.prototype.addUnicode = function (unicode) { 29 | if (this.unicodes.length === 0) { 30 | this.unicode = unicode; 31 | } 32 | this.unicodes.push(unicode); 33 | }; 34 | 35 | // Convert the glyph to a Path we can draw on a drawing context. 36 | // 37 | // x - Horizontal position of the glyph. (default: 0) 38 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 39 | // fontSize - Font size, in pixels (default: 72). 40 | Glyph.prototype.getPath = function (x, y, fontSize) { 41 | var scale, p, commands, cmd; 42 | x = x !== undefined ? x : 0; 43 | y = y !== undefined ? y : 0; 44 | fontSize = fontSize !== undefined ? fontSize : 72; 45 | scale = 1 / this.font.unitsPerEm * fontSize; 46 | p = new path.Path(); 47 | commands = this.path.commands; 48 | for (var i = 0; i < commands.length; i += 1) { 49 | cmd = commands[i]; 50 | if (cmd.type === 'M') { 51 | p.moveTo(x + (cmd.x * scale), y + (-cmd.y * scale)); 52 | } else if (cmd.type === 'L') { 53 | p.lineTo(x + (cmd.x * scale), y + (-cmd.y * scale)); 54 | } else if (cmd.type === 'Q') { 55 | p.quadraticCurveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), 56 | x + (cmd.x * scale), y + (-cmd.y * scale)); 57 | } else if (cmd.type === 'C') { 58 | p.curveTo(x + (cmd.x1 * scale), y + (-cmd.y1 * scale), 59 | x + (cmd.x2 * scale), y + (-cmd.y2 * scale), 60 | x + (cmd.x * scale), y + (-cmd.y * scale)); 61 | } else if (cmd.type === 'Z') { 62 | p.closePath(); 63 | } 64 | } 65 | return p; 66 | }; 67 | 68 | // Split the glyph into contours. 69 | // This function is here for backwards compatibility, and to 70 | // provide raw access to the TrueType glyph outlines. 71 | Glyph.prototype.getContours = function () { 72 | var contours, currentContour, i, pt; 73 | if (this.points === undefined) { 74 | return []; 75 | } 76 | contours = []; 77 | currentContour = []; 78 | for (i = 0; i < this.points.length; i += 1) { 79 | pt = this.points[i]; 80 | currentContour.push(pt); 81 | if (pt.lastPointOfContour) { 82 | contours.push(currentContour); 83 | currentContour = []; 84 | } 85 | } 86 | check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); 87 | return contours; 88 | }; 89 | 90 | // Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. 91 | Glyph.prototype.getMetrics = function () { 92 | var commands = this.path.commands; 93 | var xCoords = []; 94 | var yCoords = []; 95 | for (var i = 0; i < commands.length; i += 1) { 96 | var cmd = commands[i]; 97 | if (cmd.type !== 'Z') { 98 | xCoords.push(cmd.x); 99 | yCoords.push(cmd.y); 100 | } 101 | if (cmd.type === 'Q' || cmd.type === 'C') { 102 | xCoords.push(cmd.x1); 103 | yCoords.push(cmd.y1); 104 | } 105 | if (cmd.type === 'C') { 106 | xCoords.push(cmd.x2); 107 | yCoords.push(cmd.y2); 108 | } 109 | } 110 | var metrics = { 111 | xMin: Math.min.apply(null, xCoords), 112 | yMin: Math.min.apply(null, yCoords), 113 | xMax: Math.max.apply(null, xCoords), 114 | yMax: Math.max.apply(null, yCoords), 115 | leftSideBearing: 0 116 | }; 117 | metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); 118 | return metrics; 119 | }; 120 | 121 | // Draw the glyph on the given context. 122 | // 123 | // ctx - The drawing context. 124 | // x - Horizontal position of the glyph. (default: 0) 125 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 126 | // fontSize - Font size, in pixels (default: 72). 127 | Glyph.prototype.draw = function (ctx, x, y, fontSize) { 128 | this.getPath(x, y, fontSize).draw(ctx); 129 | }; 130 | 131 | // Draw the points of the glyph. 132 | // On-curve points will be drawn in blue, off-curve points will be drawn in red. 133 | // 134 | // ctx - The drawing context. 135 | // x - Horizontal position of the glyph. (default: 0) 136 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 137 | // fontSize - Font size, in pixels (default: 72). 138 | Glyph.prototype.drawPoints = function (ctx, x, y, fontSize) { 139 | 140 | function drawCircles(l, x, y, scale) { 141 | var j, PI_SQ = Math.PI * 2; 142 | ctx.beginPath(); 143 | for (j = 0; j < l.length; j += 1) { 144 | ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); 145 | ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, PI_SQ, false); 146 | } 147 | ctx.closePath(); 148 | ctx.fill(); 149 | } 150 | 151 | var scale, i, blueCircles, redCircles, path, cmd; 152 | x = x !== undefined ? x : 0; 153 | y = y !== undefined ? y : 0; 154 | fontSize = fontSize !== undefined ? fontSize : 24; 155 | scale = 1 / this.font.unitsPerEm * fontSize; 156 | 157 | blueCircles = []; 158 | redCircles = []; 159 | path = this.path; 160 | for (i = 0; i < path.commands.length; i += 1) { 161 | cmd = path.commands[i]; 162 | if (cmd.x !== undefined) { 163 | blueCircles.push({x: cmd.x, y: -cmd.y}); 164 | } 165 | if (cmd.x1 !== undefined) { 166 | redCircles.push({x: cmd.x1, y: -cmd.y1}); 167 | } 168 | if (cmd.x2 !== undefined) { 169 | redCircles.push({x: cmd.x2, y: -cmd.y2}); 170 | } 171 | } 172 | 173 | ctx.fillStyle = 'blue'; 174 | drawCircles(blueCircles, x, y, scale); 175 | ctx.fillStyle = 'red'; 176 | drawCircles(redCircles, x, y, scale); 177 | }; 178 | 179 | // Draw lines indicating important font measurements. 180 | // Black lines indicate the origin of the coordinate system (point 0,0). 181 | // Blue lines indicate the glyph bounding box. 182 | // Green line indicates the advance width of the glyph. 183 | // 184 | // ctx - The drawing context. 185 | // x - Horizontal position of the glyph. (default: 0) 186 | // y - Vertical position of the *baseline* of the glyph. (default: 0) 187 | // fontSize - Font size, in pixels (default: 72). 188 | Glyph.prototype.drawMetrics = function (ctx, x, y, fontSize) { 189 | var scale; 190 | x = x !== undefined ? x : 0; 191 | y = y !== undefined ? y : 0; 192 | fontSize = fontSize !== undefined ? fontSize : 24; 193 | scale = 1 / this.font.unitsPerEm * fontSize; 194 | ctx.lineWidth = 1; 195 | // Draw the origin 196 | ctx.strokeStyle = 'black'; 197 | draw.line(ctx, x, -10000, x, 10000); 198 | draw.line(ctx, -10000, y, 10000, y); 199 | // Draw the glyph box 200 | ctx.strokeStyle = 'blue'; 201 | draw.line(ctx, x + (this.xMin * scale), -10000, x + (this.xMin * scale), 10000); 202 | draw.line(ctx, x + (this.xMax * scale), -10000, x + (this.xMax * scale), 10000); 203 | draw.line(ctx, -10000, y + (-this.yMin * scale), 10000, y + (-this.yMin * scale)); 204 | draw.line(ctx, -10000, y + (-this.yMax * scale), 10000, y + (-this.yMax * scale)); 205 | // Draw the advance width 206 | ctx.strokeStyle = 'green'; 207 | draw.line(ctx, x + (this.advanceWidth * scale), -10000, x + (this.advanceWidth * scale), 10000); 208 | }; 209 | 210 | exports.Glyph = Glyph; 211 | -------------------------------------------------------------------------------- /src/uint32.js: -------------------------------------------------------------------------------- 1 | 2 | //from https://github.com/fxa/uint32.js 3 | 4 | /* jshint bitwise: false */ 5 | 6 | /** 7 | * @license (c) Franz X Antesberger 2013 8 | */ 9 | (function (exporter) { 10 | 'use strict'; 11 | 12 | var POW_2_32 = 0x0100000000; 13 | var POW_2_52 = 0x10000000000000; 14 | 15 | // 16 | // Creating and Extracting 17 | // 18 | 19 | /** 20 | * Creates an uint32 from the given bytes in big endian order. 21 | * @param {Number} highByte the high byte 22 | * @param {Number} secondHighByte the 2nd high byte 23 | * @param {Number} thirdHighByte the 3rd high byte 24 | * @param {Number} lowByte the low byte 25 | * @returns highByte concat secondHighByte concat thirdHighByte concat lowByte 26 | */ 27 | exporter.fromBytesBigEndian = function (highByte, secondHighByte, thirdHighByte, lowByte) { 28 | return ((highByte << 24) | (secondHighByte << 16) | (thirdHighByte << 8) | lowByte) >>> 0; 29 | }; 30 | 31 | /** 32 | * Returns the byte. 33 | * e.g. when byteNo is 0, the high byte is returned, when byteNo = 3 the low byte is returned. 34 | * @param {Number} uint32value the source to be extracted 35 | * @param {Number} byteNo 0-3 the byte number, 0 is the high byte, 3 the low byte 36 | * @returns {Number} the 0-255 byte according byteNo 37 | */ 38 | exporter.getByteBigEndian = function (uint32value, byteNo) { 39 | return (uint32value >>> (8 * (3 - byteNo))) & 0xff; 40 | }; 41 | 42 | /** 43 | * Returns the bytes as array. 44 | * @param {Number} uint32value the source to be extracted 45 | * @returns {Array} the array [highByte, 2ndHighByte, 3rdHighByte, lowByte] 46 | */ 47 | exporter.getBytesBigEndian = function (uint32value) { 48 | return [ 49 | exporter.getByteBigEndian(uint32value, 0), 50 | exporter.getByteBigEndian(uint32value, 1), 51 | exporter.getByteBigEndian(uint32value, 2), 52 | exporter.getByteBigEndian(uint32value, 3) 53 | ]; 54 | }; 55 | 56 | /** 57 | * Converts a given uin32 to a hex string including leading zeros. 58 | * @param {Number} uint32value the uint32 to be stringified 59 | * @param {Number} optionalMinLength the optional (default 8) 60 | */ 61 | exporter.toHex = function (uint32value, optionalMinLength) { 62 | optionalMinLength = optionalMinLength || 8; 63 | var result = uint32value.toString(16); 64 | if (result.length < optionalMinLength) { 65 | result = new Array(optionalMinLength - result.length + 1).join('0') + result; 66 | } 67 | return result; 68 | }; 69 | 70 | /** 71 | * Converts a number to an uint32. 72 | * @param {Number} number the number to be converted. 73 | * @return {Number} an uint32 value 74 | */ 75 | exporter.toUint32 = function (number) { 76 | // the shift operator forces js to perform the internal ToUint32 (see ecmascript spec 9.6) 77 | return number >>> 0; 78 | }; 79 | 80 | /** 81 | * Returns the part above the uint32 border. 82 | * Depending to the javascript engine, that are the 54-32 = 22 high bits 83 | * @param {Number} number the number to extract the high part 84 | * @return {Number} the high part of the number 85 | */ 86 | exporter.highPart = function (number) { 87 | return exporter.toUint32(number / POW_2_32); 88 | }; 89 | 90 | // 91 | // Bitwise Logical Operators 92 | // 93 | 94 | /** 95 | * Returns a bitwise OR operation on two or more values. 96 | * @param {Number} uint32val0 first uint32 value 97 | * @param {Number} argv one or more uint32 values 98 | * @return {Number} the bitwise OR uint32 value 99 | */ 100 | exporter.or = function (uint32val0, argv) { 101 | var result = uint32val0; 102 | for (var index = 1; index < arguments.length; index += 1) { 103 | result = (result | arguments[index]); 104 | } 105 | return result >>> 0; 106 | }; 107 | 108 | /** 109 | * Returns a bitwise AND operation on two or more values. 110 | * @param {Number} uint32val0 first uint32 value 111 | * @param {Number} argv one or more uint32 values 112 | * @return {Number} the bitwise AND uint32 value 113 | */ 114 | exporter.and = function (uint32val0, argv) { 115 | var result = uint32val0; 116 | for (var index = 1; index < arguments.length; index += 1) { 117 | result = (result & arguments[index]); 118 | } 119 | return result >>> 0; 120 | }; 121 | 122 | /** 123 | * Returns a bitwise XOR operation on two or more values. 124 | * @param {Number} uint32val0 first uint32 value 125 | * @param {Number} argv one or more uint32 values 126 | * @return {Number} the bitwise XOR uint32 value 127 | */ 128 | exporter.xor = function (uint32val0, argv) { 129 | var result = uint32val0; 130 | for (var index = 1; index < arguments.length; index += 1) { 131 | result = (result ^ arguments[index]); 132 | } 133 | return result >>> 0; 134 | }; 135 | 136 | exporter.not = function (uint32val) { 137 | return (~uint32val) >>> 0; 138 | }; 139 | 140 | // 141 | // Shifting and Rotating 142 | // 143 | 144 | /** 145 | * Returns the uint32 representation of a << operation. 146 | * @param {Number} uint32val the word to be shifted 147 | * @param {Number} numBits the number of bits to be shifted (0-31) 148 | * @returns {Number} the uint32 value of the shifted word 149 | */ 150 | exporter.shiftLeft = function (uint32val, numBits) { 151 | return (uint32val << numBits) >>> 0; 152 | }; 153 | 154 | /** 155 | * Returns the uint32 representation of a >>> operation. 156 | * @param {Number} uint32val the word to be shifted 157 | * @param {Number} numBits the number of bits to be shifted (0-31) 158 | * @returns {Number} the uint32 value of the shifted word 159 | */ 160 | exporter.shiftRight = function (uint32val, numBits) { 161 | return uint32val >>> numBits; 162 | }; 163 | 164 | exporter.rotateLeft = function (uint32val, numBits) { 165 | return (((uint32val << numBits) >>> 0) | (uint32val >>> (32 - numBits))) >>> 0; 166 | }; 167 | 168 | exporter.rotateRight = function (uint32val, numBits) { 169 | return (((uint32val) >>> (numBits)) | ((uint32val) << (32 - numBits)) >>> 0) >>> 0; 170 | }; 171 | 172 | // 173 | // Logical Gates 174 | // 175 | 176 | /** 177 | * Bitwise choose bits from y or z, as a bitwise x ? y : z 178 | */ 179 | exporter.choose = function (x, y, z) { 180 | return ((x & (y ^ z)) ^ z) >>> 0; 181 | }; 182 | 183 | /** 184 | * Majority gate for three parameters. Takes bitwise the majority of x, y and z, 185 | * @see https://en.wikipedia.org/wiki/Majority_function 186 | */ 187 | exporter.majority = function (x, y, z) { 188 | return ((x & (y | z)) | (y & z)) >>> 0; 189 | }; 190 | 191 | // 192 | // Arithmetic 193 | // 194 | 195 | /** 196 | * Adds the given values modulus 2^32. 197 | * @returns the sum of the given values modulus 2^32 198 | */ 199 | exporter.addMod32 = function (uint32val0/*, optionalValues*/) { 200 | var result = uint32val0; 201 | for (var index = 1; index < arguments.length; index += 1) { 202 | result += arguments[index]; 203 | } 204 | return result >>> 0; 205 | }; 206 | 207 | /** 208 | * Returns the log base 2 of the given value. That is the number of the highest set bit. 209 | * @param {Number} uint32val the value, the log2 is calculated of 210 | * @return {Number} the logarithm base 2, an integer between 0 and 31 211 | */ 212 | exporter.log2 = function (uint32val) { 213 | return Math.floor(Math.log(uint32val) / Math.LN2); 214 | }; 215 | 216 | /* 217 | // this implementation does the same, looks much funnier, but takes 2 times longer (according to jsperf) ... 218 | var log2_u = new Uint32Array(2); 219 | var log2_d = new Float64Array(log2_u.buffer); 220 | 221 | exporter.log2 = function (uint32val) { 222 | // Ported from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float to javascript 223 | // (public domain) 224 | if (uint32val === 0) { 225 | return -Infinity; 226 | } 227 | // fill in the low part 228 | log2_u[0] = uint32val; 229 | // set the mantissa to 2^52 230 | log2_u[1] = 0x43300000; 231 | // subtract 2^52 232 | log2_d[0] -= 0x10000000000000; 233 | return (log2_u[1] >>> 20) - 0x3FF; 234 | }; 235 | */ 236 | 237 | /** 238 | * Returns the the low and the high uint32 of the multiplication. 239 | * @param {Number} factor1 an uint32 240 | * @param {Number} factor2 an uint32 241 | * @param {Uint32Array[2]} resultUint32Array2 the Array, where the result will be written to 242 | * @returns undefined 243 | */ 244 | exporter.mult = function (factor1, factor2, resultUint32Array2) { 245 | var high16 = ((factor1 & 0xffff0000) >>> 0) * factor2; 246 | var low16 = (factor1 & 0x0000ffff) * factor2; 247 | // the addition is dangerous, because the result will be rounded, so the result depends on the lowest bits, which will be cut away! 248 | var carry = ((exporter.toUint32(high16) + exporter.toUint32(low16)) >= POW_2_32) ? 1 : 0; 249 | resultUint32Array2[0] = (exporter.highPart(high16) + exporter.highPart(low16) + carry) >>> 0; 250 | resultUint32Array2[1] = ((high16 >>> 0) + (low16 >>> 0));// >>> 0; 251 | }; 252 | 253 | }) ((typeof module !== 'undefined') ? module.exports = {} : window.uint32 = {}); 254 | -------------------------------------------------------------------------------- /vendor/tables/gpos.js: -------------------------------------------------------------------------------- 1 | // The `GPOS` table contains kerning pairs, among other things. 2 | // https://www.microsoft.com/typography/OTSPEC/gpos.htm 3 | 4 | 'use strict'; 5 | 6 | var check = require('../check'); 7 | var parse = require('../parse'); 8 | 9 | // Parse ScriptList and FeatureList tables of GPOS, GSUB, GDEF, BASE, JSTF tables. 10 | // These lists are unused by now, this function is just the basis for a real parsing. 11 | function parseTaggedListTable(data, start) { 12 | var p = new parse.Parser(data, start), 13 | n = p.parseUShort(), 14 | list = []; 15 | for (var i = 0; i < n; i++) { 16 | list[p.parseTag()] = { offset: p.parseUShort() }; 17 | } 18 | return list; 19 | } 20 | 21 | // Parse a coverage table in a GSUB, GPOS or GDEF table. 22 | // Format 1 is a simple list of glyph ids, 23 | // Format 2 is a list of ranges. It is expanded in a list of glyphs, maybe not the best idea. 24 | function parseCoverageTable(data, start) { 25 | var p = new parse.Parser(data, start), 26 | format = p.parseUShort(), 27 | count = p.parseUShort(); 28 | if (format === 1) { 29 | return p.parseUShortList(count); 30 | } 31 | else if (format === 2) { 32 | var i, begin, end, index, coverage = []; 33 | for (; count--;) { 34 | begin = p.parseUShort(); 35 | end = p.parseUShort(); 36 | index = p.parseUShort(); 37 | for (i = begin; i <= end; i++) { 38 | coverage[index++] = i; 39 | } 40 | } 41 | return coverage; 42 | } 43 | } 44 | 45 | // Parse a Class Definition Table in a GSUB, GPOS or GDEF table. 46 | // Returns a function that gets a class value from a glyph ID. 47 | function parseClassDefTable(data, start) { 48 | var p = new parse.Parser(data, start), 49 | format = p.parseUShort(); 50 | if (format === 1) { 51 | // Format 1 specifies a range of consecutive glyph indices, one class per glyph ID. 52 | var startGlyph = p.parseUShort(), 53 | glyphCount = p.parseUShort(), 54 | classes = p.parseUShortList(glyphCount); 55 | return function(glyphID) { 56 | return classes[glyphID - startGlyph] || 0; 57 | }; 58 | } 59 | else if (format === 2) { 60 | // Format 2 defines multiple groups of glyph indices that belong to the same class. 61 | var rangeCount = p.parseUShort(), 62 | startGlyphs = [], 63 | endGlyphs = [], 64 | classValues = []; 65 | for (var i = 0; i < rangeCount; i++) { 66 | startGlyphs[i] = p.parseUShort(); 67 | endGlyphs[i] = p.parseUShort(); 68 | classValues[i] = p.parseUShort(); 69 | } 70 | return function(glyphID) { 71 | var l, c, r; 72 | l = 0; 73 | r = startGlyphs.length - 1; 74 | while (l < r) { 75 | c = (l + r + 1) >> 1; 76 | if (glyphID < startGlyphs[c]) { 77 | r = c - 1; 78 | } else { 79 | l = c; 80 | } 81 | } 82 | if (startGlyphs[l] <= glyphID && glyphID <= endGlyphs[l]) { 83 | return classValues[l] || 0; 84 | } 85 | return 0; 86 | }; 87 | } 88 | } 89 | 90 | // Parse a pair adjustment positioning subtable, format 1 or format 2 91 | // The subtable is returned in the form of a lookup function. 92 | function parsePairPosSubTable(data, start) { 93 | var p = new parse.Parser(data, start); 94 | var format, coverageOffset, coverage, valueFormat1, valueFormat2, 95 | sharedPairSets, firstGlyph, secondGlyph, value1, value2; 96 | // This part is common to format 1 and format 2 subtables 97 | format = p.parseUShort(); 98 | coverageOffset = p.parseUShort(); 99 | coverage = parseCoverageTable(data, start+coverageOffset); 100 | // valueFormat 4: XAdvance only, 1: XPlacement only, 0: no ValueRecord for second glyph 101 | // Only valueFormat1=4 and valueFormat2=0 is supported. 102 | valueFormat1 = p.parseUShort(); 103 | valueFormat2 = p.parseUShort(); 104 | if (valueFormat1 !== 4 || valueFormat2 !== 0) return; 105 | sharedPairSets = {}; 106 | if (format === 1) { 107 | // Pair Positioning Adjustment: Format 1 108 | var pairSetCount, pairSetOffsets, pairSetOffset, sharedPairSet, pairValueCount, pairSet; 109 | pairSetCount = p.parseUShort(); 110 | pairSet = []; 111 | // Array of offsets to PairSet tables-from beginning of PairPos subtable-ordered by Coverage Index 112 | pairSetOffsets = p.parseOffset16List(pairSetCount); 113 | for (firstGlyph = 0; firstGlyph < pairSetCount; firstGlyph++) { 114 | pairSetOffset = pairSetOffsets[firstGlyph]; 115 | sharedPairSet = sharedPairSets[pairSetOffset]; 116 | if (!sharedPairSet) { 117 | // Parse a pairset table in a pair adjustment subtable format 1 118 | sharedPairSet = {}; 119 | p.relativeOffset = pairSetOffset; 120 | pairValueCount = p.parseUShort(); 121 | for (; pairValueCount--;) { 122 | secondGlyph = p.parseUShort(); 123 | if (valueFormat1) value1 = p.parseShort(); 124 | if (valueFormat2) value2 = p.parseShort(); 125 | // We only support valueFormat1 = 4 and valueFormat2 = 0, 126 | // so value1 is the XAdvance and value2 is empty. 127 | sharedPairSet[secondGlyph] = value1; 128 | } 129 | } 130 | pairSet[coverage[firstGlyph]] = sharedPairSet; 131 | } 132 | return function(leftGlyph, rightGlyph) { 133 | var pairs = pairSet[leftGlyph]; 134 | if (pairs) return pairs[rightGlyph]; 135 | }; 136 | } 137 | else if (format === 2) { 138 | // Pair Positioning Adjustment: Format 2 139 | var classDef1Offset, classDef2Offset, class1Count, class2Count, i, j, 140 | getClass1, getClass2, kerningMatrix, kerningRow, covered; 141 | classDef1Offset = p.parseUShort(); 142 | classDef2Offset = p.parseUShort(); 143 | class1Count = p.parseUShort(); 144 | class2Count = p.parseUShort(); 145 | getClass1 = parseClassDefTable(data, start+classDef1Offset); 146 | getClass2 = parseClassDefTable(data, start+classDef2Offset); 147 | 148 | // Parse kerning values by class pair. 149 | kerningMatrix = []; 150 | for (i = 0; i < class1Count; i++) { 151 | kerningRow = kerningMatrix[i] = []; 152 | for (j = 0; j < class2Count; j++) { 153 | if (valueFormat1) value1 = p.parseShort(); 154 | if (valueFormat2) value2 = p.parseShort(); 155 | // We only support valueFormat1 = 4 and valueFormat2 = 0, 156 | // so value1 is the XAdvance and value2 is empty. 157 | kerningRow[j] = value1; 158 | } 159 | } 160 | 161 | // Convert coverage list to a hash 162 | covered = {}; 163 | for(i = 0; i < coverage.length; i++) covered[coverage[i]] = 1; 164 | 165 | // Get the kerning value for a specific glyph pair. 166 | return function(leftGlyph, rightGlyph) { 167 | if (!covered[leftGlyph]) return null; 168 | var class1 = getClass1(leftGlyph), 169 | class2 = getClass2(rightGlyph), 170 | kerningRow = kerningMatrix[class1]; 171 | return kerningRow ? kerningRow[class2] : null; 172 | }; 173 | } 174 | } 175 | 176 | // Parse a LookupTable (present in of GPOS, GSUB, GDEF, BASE, JSTF tables). 177 | function parseLookupTable(data, start) { 178 | var p = new parse.Parser(data, start); 179 | var table, lookupType, lookupFlag, useMarkFilteringSet, subTableCount, subTableOffsets, subtables, i; 180 | lookupType = p.parseUShort(); 181 | lookupFlag = p.parseUShort(); 182 | useMarkFilteringSet = lookupFlag & 0x10; 183 | subTableCount = p.parseUShort(); 184 | subTableOffsets = p.parseOffset16List(subTableCount); 185 | table = { 186 | lookupType: lookupType, 187 | lookupFlag: lookupFlag, 188 | markFilteringSet: useMarkFilteringSet ? p.parseUShort() : -1 189 | }; 190 | // LookupType 2, Pair adjustment 191 | if (lookupType === 2) { 192 | subtables = []; 193 | for (i = 0; i < subTableCount; i++) { 194 | subtables.push(parsePairPosSubTable(data, start + subTableOffsets[i])); 195 | } 196 | // Return a function which finds the kerning values in the subtables. 197 | table.getKerningValue = function(leftGlyph, rightGlyph) { 198 | for (var i = subtables.length; i--;) { 199 | var value = subtables[i](leftGlyph, rightGlyph); 200 | if (value !== undefined) return value; 201 | } 202 | return 0; 203 | }; 204 | } 205 | return table; 206 | } 207 | 208 | // Parse the `GPOS` table which contains, among other things, kerning pairs. 209 | // https://www.microsoft.com/typography/OTSPEC/gpos.htm 210 | function parseGposTable(data, start, font) { 211 | var p, tableVersion, lookupListOffset, scriptList, i, featureList, lookupCount, 212 | lookupTableOffsets, lookupListAbsoluteOffset, table; 213 | 214 | p = new parse.Parser(data, start); 215 | tableVersion = p.parseFixed(); 216 | check.argument(tableVersion === 1, 'Unsupported GPOS table version.'); 217 | 218 | // ScriptList and FeatureList - ignored for now 219 | scriptList = parseTaggedListTable(data, start+p.parseUShort()); 220 | // 'kern' is the feature we are looking for. 221 | featureList = parseTaggedListTable(data, start+p.parseUShort()); 222 | 223 | // LookupList 224 | lookupListOffset = p.parseUShort(); 225 | p.relativeOffset = lookupListOffset; 226 | lookupCount = p.parseUShort(); 227 | lookupTableOffsets = p.parseOffset16List(lookupCount); 228 | lookupListAbsoluteOffset = start + lookupListOffset; 229 | for (i = 0; i < lookupCount; i++) { 230 | table = parseLookupTable(data, lookupListAbsoluteOffset + lookupTableOffsets[i]); 231 | if (table.lookupType === 2 && !font.getGposKerningValue) font.getGposKerningValue = table.getKerningValue; 232 | } 233 | } 234 | 235 | exports.parse = parseGposTable; 236 | -------------------------------------------------------------------------------- /vendor/types.js: -------------------------------------------------------------------------------- 1 | // Data types used in the OpenType font file. 2 | // All OpenType fonts use Motorola-style byte ordering (Big Endian) 3 | 4 | 'use strict'; 5 | 6 | var check = require('./check'); 7 | 8 | var LIMIT16 = 32768; // The limit at which a 16-bit number switches signs == 2^15 9 | var LIMIT32 = 2147483648; // The limit at which a 32-bit number switches signs == 2 ^ 31 10 | 11 | var decode = {}; 12 | var encode = {}; 13 | var sizeOf = {}; 14 | 15 | // Return a function that always returns the same value. 16 | function constant(v) { 17 | return function () { 18 | return v; 19 | }; 20 | } 21 | 22 | // OpenType data types ////////////////////////////////////////////////////// 23 | 24 | // Convert an 8-bit unsigned integer to a list of 1 byte. 25 | encode.BYTE = function (v) { 26 | check.argument(v >= 0 && v <= 255, 'Byte value should be between 0 and 255.'); 27 | return [v]; 28 | }; 29 | 30 | sizeOf.BYTE = constant(1); 31 | 32 | // Convert a 8-bit signed integer to a list of 1 byte. 33 | encode.CHAR = function (v) { 34 | return [v.charCodeAt(0)]; 35 | }; 36 | 37 | sizeOf.BYTE = constant(1); 38 | 39 | // Convert an ASCII string to a list of bytes. 40 | encode.CHARARRAY = function (v) { 41 | var b = []; 42 | for (var i = 0; i < v.length; i += 1) { 43 | b.push(v.charCodeAt(i)); 44 | } 45 | return b; 46 | }; 47 | 48 | sizeOf.CHARARRAY = function (v) { 49 | return v.length; 50 | }; 51 | 52 | // Convert a 16-bit unsigned integer to a list of 2 bytes. 53 | encode.USHORT = function (v) { 54 | return [(v >> 8) & 0xFF, v & 0xFF]; 55 | }; 56 | 57 | sizeOf.USHORT = constant(2); 58 | 59 | // Convert a 16-bit signed integer to a list of 2 bytes. 60 | encode.SHORT = function (v) { 61 | // Two's complement 62 | if (v >= LIMIT16){ 63 | v = - ( 2 * LIMIT16 - v); 64 | } 65 | return [(v >> 8) & 0xFF, v & 0xFF]; 66 | }; 67 | 68 | sizeOf.SHORT = constant(2); 69 | 70 | // Convert a 24-bit unsigned integer to a list of 3 bytes. 71 | encode.UINT24 = function (v) { 72 | return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; 73 | }; 74 | 75 | sizeOf.UINT24 = constant(3); 76 | 77 | // Convert a 32-bit unsigned integer to a list of 4 bytes. 78 | encode.ULONG = function (v) { 79 | return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; 80 | }; 81 | 82 | sizeOf.ULONG = constant(4); 83 | 84 | // Convert a 32-bit unsigned integer to a list of 4 bytes. 85 | encode.LONG = function (v) { 86 | // Two's complement 87 | if (v >= LIMIT32){ 88 | v = - ( 2 * LIMIT32 - v); 89 | } 90 | return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; 91 | }; 92 | 93 | sizeOf.LONG = constant(4); 94 | 95 | encode.FIXED = encode.ULONG; 96 | sizeOf.FIXED = sizeOf.ULONG; 97 | 98 | encode.FWORD = encode.SHORT; 99 | sizeOf.FWORD = sizeOf.SHORT; 100 | 101 | encode.UFWORD = encode.USHORT; 102 | sizeOf.UFWORD = sizeOf.USHORT; 103 | 104 | 105 | // FIXME Implement LONGDATETIME 106 | encode.LONGDATETIME = function () { 107 | return [0, 0, 0, 0, 0, 0, 0, 0]; 108 | }; 109 | 110 | sizeOf.LONGDATETIME = constant(8); 111 | 112 | // Convert a 4-char tag to a list of 4 bytes. 113 | encode.TAG = function (v) { 114 | check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); 115 | return [v.charCodeAt(0), 116 | v.charCodeAt(1), 117 | v.charCodeAt(2), 118 | v.charCodeAt(3)]; 119 | }; 120 | 121 | sizeOf.TAG = constant(4); 122 | 123 | // CFF data types /////////////////////////////////////////////////////////// 124 | 125 | encode.Card8 = encode.BYTE; 126 | sizeOf.Card8 = sizeOf.BYTE; 127 | 128 | encode.Card16 = encode.USHORT; 129 | sizeOf.Card16 = sizeOf.USHORT; 130 | 131 | encode.OffSize = encode.BYTE; 132 | sizeOf.OffSize = sizeOf.BYTE; 133 | 134 | encode.SID = encode.USHORT; 135 | sizeOf.SID = sizeOf.USHORT; 136 | 137 | // Convert a numeric operand or charstring number to a variable-size list of bytes. 138 | encode.NUMBER = function (v) { 139 | if (v >= -107 && v <= 107) { 140 | return [v + 139]; 141 | } else if (v >= 108 && v <= 1131 ) { 142 | v = v - 108; 143 | return [(v >> 8) + 247, v & 0xFF]; 144 | } else if (v >= -1131 && v <= -108) { 145 | v = -v - 108; 146 | return [(v >> 8) + 251, v & 0xFF]; 147 | } else if (v >= -32768 && v <= 32767) { 148 | return encode.NUMBER16(v); 149 | } else { 150 | return encode.NUMBER32(v); 151 | } 152 | }; 153 | 154 | sizeOf.NUMBER = function (v) { 155 | return encode.NUMBER(v).length; 156 | }; 157 | 158 | // Convert a signed number between -32768 and +32767 to a three-byte value. 159 | // This ensures we always use three bytes, but is not the most compact format. 160 | encode.NUMBER16 = function (v) { 161 | return [28, (v >> 8) & 0xFF, v & 0xFF]; 162 | }; 163 | 164 | sizeOf.NUMBER16 = constant(2); 165 | 166 | // Convert a signed number between -(2^31) and +(2^31-1) to a four-byte value. 167 | // This is useful if you want to be sure you always use four bytes, 168 | // at the expense of wasting a few bytes for smaller numbers. 169 | encode.NUMBER32 = function (v) { 170 | return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; 171 | }; 172 | 173 | sizeOf.NUMBER32 = constant(4); 174 | 175 | encode.NAME = encode.CHARARRAY; 176 | sizeOf.NAME = sizeOf.CHARARRAY; 177 | 178 | encode.STRING = encode.CHARARRAY; 179 | sizeOf.STRING = sizeOf.CHARARRAY; 180 | 181 | // Convert a ASCII string to a list of UTF16 bytes. 182 | encode.UTF16 = function (v) { 183 | var b = []; 184 | for (var i = 0; i < v.length; i += 1) { 185 | b.push(0); 186 | b.push(v.charCodeAt(i)); 187 | } 188 | return b; 189 | }; 190 | 191 | sizeOf.UTF16 = function (v) { 192 | return v.length * 2; 193 | }; 194 | 195 | // Convert a list of values to a CFF INDEX structure. 196 | // The values should be objects containing name / type / value. 197 | encode.INDEX = function (l) { 198 | var offSize, offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, 199 | dataSize, i, v; 200 | // Because we have to know which data type to use to encode the offsets, 201 | // we have to go through the values twice: once to encode the data and 202 | // calculate the offets, then again to encode the offsets using the fitting data type. 203 | offset = 1; // First offset is always 1. 204 | offsets = [offset]; 205 | data = []; 206 | dataSize = 0; 207 | for (i = 0; i < l.length; i += 1) { 208 | v = encode.OBJECT(l[i]); 209 | Array.prototype.push.apply(data, v); 210 | dataSize += v.length; 211 | offset += v.length; 212 | offsets.push(offset); 213 | } 214 | 215 | if (data.length === 0) { 216 | return [0, 0]; 217 | } 218 | 219 | encodedOffsets = []; 220 | offSize = (1 + Math.floor(Math.log(dataSize)/Math.log(2)) / 8) | 0; 221 | offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; 222 | for (i = 0; i < offsets.length; i += 1) { 223 | encodedOffset = offsetEncoder(offsets[i]); 224 | Array.prototype.push.apply(encodedOffsets, encodedOffset); 225 | } 226 | return Array.prototype.concat(encode.Card16(l.length), 227 | encode.OffSize(offSize), 228 | encodedOffsets, 229 | data); 230 | }; 231 | 232 | sizeOf.INDEX = function (v) { 233 | return encode.INDEX(v).length; 234 | }; 235 | 236 | // Convert an object to a CFF DICT structure. 237 | // The keys should be numeric. 238 | // The values should be objects containing name / type / value. 239 | encode.DICT = function (m) { 240 | var d = []; 241 | var keys = Object.keys(m); 242 | for (var i = 0; i < keys.length; i += 1) { 243 | // Object.keys() return string keys, but our keys are always numeric. 244 | var k = parseInt(keys[i], 0); 245 | var v = m[k]; 246 | // Value comes before the key. 247 | d = d.concat(encode.OPERAND(v.value, v.type)); 248 | d = d.concat(encode.OPERATOR(k)); 249 | } 250 | return d; 251 | }; 252 | 253 | sizeOf.DICT = function (m) { 254 | return encode.DICT(m).length; 255 | }; 256 | 257 | encode.OPERATOR = function (v) { 258 | if (v < 1200) { 259 | return [v]; 260 | } else { 261 | return [12, v - 1200]; 262 | } 263 | }; 264 | 265 | encode.OPERAND = function (v, type) { 266 | var d, i; 267 | d = []; 268 | if (Array.isArray(type)) { 269 | for (i = 0; i < type.length; i += 1) { 270 | check.argument(v.length === type.length, 'Not enough arguments given for type' + type); 271 | d = d.concat(encode.OPERAND(v[i], type[i])); 272 | } 273 | } else { 274 | if (type === 'SID') { 275 | d = d.concat(encode.NUMBER(v)); 276 | } else if (type === 'offset') { 277 | // We make it easy for ourselves and always encode offsets as 278 | // 4 bytes. This makes offset calculation for the top dict easier. 279 | d = d.concat(encode.NUMBER32(v)); 280 | } else { 281 | // FIXME Add support for booleans 282 | d = d.concat(encode.NUMBER(v)); 283 | } 284 | } 285 | return d; 286 | }; 287 | 288 | encode.OP = encode.BYTE; 289 | sizeOf.OP = sizeOf.BYTE; 290 | 291 | // Convert a list of CharString operations to bytes. 292 | encode.CHARSTRING = function (ops) { 293 | var d = [], i; 294 | for (i = 0; i < ops.length; i += 1) { 295 | var op = ops[i]; 296 | var encodingFunction = encode[op.type]; 297 | d = d.concat(encodingFunction(op.value)); 298 | } 299 | return d; 300 | }; 301 | 302 | sizeOf.CHARSTRING = function (ops) { 303 | return encode.CHARSTRING(ops).length; 304 | }; 305 | 306 | // Utility functions //////////////////////////////////////////////////////// 307 | 308 | // Convert an object containing name / type / value to bytes. 309 | encode.OBJECT = function (v) { 310 | var encodingFunction = encode[v.type]; 311 | check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); 312 | return encodingFunction(v.value); 313 | }; 314 | 315 | // Convert a table object to bytes. 316 | // A table contains a list of fields containing the metadata (name, type and default value). 317 | // The table itself has the field values set as attributes. 318 | encode.TABLE = function (table) { 319 | var d = []; 320 | for (var i = 0; i < table.fields.length; i += 1) { 321 | var field = table.fields[i]; 322 | var encodingFunction = encode[field.type]; 323 | check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type); 324 | var value = table[field.name]; 325 | if (value === undefined) { 326 | value = field.value; 327 | } 328 | var bytes = encodingFunction(value); 329 | d = d.concat(bytes); 330 | } 331 | return d; 332 | }; 333 | 334 | // Merge in a list of bytes. 335 | encode.LITERAL = function (v) { 336 | return v; 337 | }; 338 | 339 | sizeOf.LITERAL = function (v) { 340 | return v.length; 341 | }; 342 | 343 | 344 | exports.decode = decode; 345 | exports.encode = encode; 346 | exports.sizeOf = sizeOf; 347 | -------------------------------------------------------------------------------- /vendor/tables/sfnt.js: -------------------------------------------------------------------------------- 1 | // The `sfnt` wrapper provides organization for the tables in the font. 2 | // It is the top-level data structure in a font. 3 | // https://www.microsoft.com/typography/OTSPEC/otff.htm 4 | // Recommendations for creating OpenType Fonts: 5 | // http://www.microsoft.com/typography/otspec140/recom.htm 6 | 7 | 'use strict'; 8 | 9 | var check = require('../check'); 10 | var table = require('../table'); 11 | 12 | var cmap = require('./cmap'); 13 | var cff = require('./cff'); 14 | var head = require('./head'); 15 | var hhea = require('./hhea'); 16 | var hmtx = require('./hmtx'); 17 | var maxp = require('./maxp'); 18 | var _name = require('./name'); 19 | var os2 = require('./os2'); 20 | var post = require('./post'); 21 | 22 | function log2(v) { 23 | return Math.log(v) / Math.log(2) | 0; 24 | } 25 | 26 | function computeCheckSum(bytes) { 27 | while (bytes.length % 4 !== 0) { 28 | bytes.push(0); 29 | } 30 | var sum = 0; 31 | for (var i = 0; i < bytes.length; i += 4) { 32 | sum += (bytes[i] << 24) + 33 | (bytes[i + 1] << 16) + 34 | (bytes[i + 2] << 8) + 35 | (bytes[i + 3]); 36 | } 37 | sum %= Math.pow(2, 32); 38 | return sum; 39 | } 40 | 41 | function makeTableRecord(tag, checkSum, offset, length) { 42 | return new table.Table('Table Record', [ 43 | {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, 44 | {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, 45 | {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, 46 | {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} 47 | ]); 48 | } 49 | 50 | function makeSfntTable(tables) { 51 | var sfnt = new table.Table('sfnt', [ 52 | {name: 'version', type: 'TAG', value: 'OTTO'}, 53 | {name: 'numTables', type: 'USHORT', value: 0}, 54 | {name: 'searchRange', type: 'USHORT', value: 0}, 55 | {name: 'entrySelector', type: 'USHORT', value: 0}, 56 | {name: 'rangeShift', type: 'USHORT', value: 0} 57 | ]); 58 | sfnt.tables = tables; 59 | sfnt.numTables = tables.length; 60 | var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); 61 | sfnt.searchRange = 16 * highestPowerOf2; 62 | sfnt.entrySelector = log2(highestPowerOf2); 63 | sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; 64 | 65 | var recordFields = []; 66 | var tableFields = []; 67 | 68 | var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); 69 | while (offset % 4 !== 0) { 70 | offset += 1; 71 | tableFields.push({name: 'padding', type: 'BYTE', value: 0}); 72 | } 73 | 74 | for (var i = 0; i < tables.length; i += 1) { 75 | var t = tables[i]; 76 | check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); 77 | var tableLength = t.sizeOf(); 78 | var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); 79 | recordFields.push({name: tableRecord.tag + ' Table Record', type: 'TABLE', value: tableRecord}); 80 | tableFields.push({name: t.tableName + ' table', type: 'TABLE', value: t}); 81 | offset += tableLength; 82 | check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); 83 | while (offset % 4 !== 0) { 84 | offset += 1; 85 | tableFields.push({name: 'padding', type: 'BYTE', value: 0}); 86 | } 87 | } 88 | 89 | // Table records need to be sorted alphabetically. 90 | recordFields.sort(function (r1, r2) { 91 | if (r1.value.tag > r2.value.tag) { 92 | return 1; 93 | } else { 94 | return -1; 95 | } 96 | }); 97 | 98 | sfnt.fields = sfnt.fields.concat(recordFields); 99 | sfnt.fields = sfnt.fields.concat(tableFields); 100 | return sfnt; 101 | } 102 | 103 | // Get the metrics for a character. If the string has more than one character 104 | // this function returns metrics for the first available character. 105 | // You can provide optional fallback metrics if no characters are available. 106 | function metricsForChar(font, chars, notFoundMetrics) { 107 | for (var i = 0; i < chars.length; i += 1) { 108 | var glyphIndex = font.charToGlyphIndex(chars[i]); 109 | if (glyphIndex > 0) { 110 | var glyph = font.glyphs[glyphIndex]; 111 | return glyph.getMetrics(); 112 | } 113 | } 114 | return notFoundMetrics; 115 | } 116 | 117 | // Return the smallest and largest unicode values of the characters in this font. 118 | // For most fonts the smallest value would be 20 (space). 119 | function charCodeBounds(glyphs) { 120 | var minCode, maxCode; 121 | for (var i = 0; i < glyphs.length; i += 1) { 122 | var glyph = glyphs[i]; 123 | if (glyph.unicode >= 20) { 124 | if (minCode === undefined) { 125 | minCode = glyph.unicode; 126 | } else if (glyph.unicode < minCode) { 127 | minCode = glyph.unicode; 128 | } 129 | if (maxCode === undefined) { 130 | maxCode = glyph.unicode; 131 | } else if (glyph.unicode > maxCode) { 132 | maxCode = glyph.unicode; 133 | } 134 | } 135 | } 136 | return [minCode, maxCode]; 137 | } 138 | 139 | function average(vs) { 140 | var sum = 0; 141 | for (var i = 0; i < vs.length; i += 1) { 142 | sum += vs[i]; 143 | } 144 | return sum / vs.length; 145 | } 146 | 147 | // Convert the font object to a SFNT data structure. 148 | // This structure contains all the necessary tables and metadata to create a binary OTF file. 149 | function fontToSfntTable(font) { 150 | var xMins = []; 151 | var yMins = []; 152 | var xMaxs = []; 153 | var yMaxs = []; 154 | var advanceWidths = []; 155 | var leftSideBearings = []; 156 | var rightSideBearings = []; 157 | for (var i = 0; i < font.glyphs.length; i += 1) { 158 | var glyph = font.glyphs[i]; 159 | // Skip non-important characters. 160 | if (glyph.name === '.notdef') continue; 161 | var metrics = glyph.getMetrics(); 162 | xMins.push(metrics.xMin); 163 | yMins.push(metrics.yMin); 164 | xMaxs.push(metrics.xMax); 165 | yMaxs.push(metrics.yMax); 166 | leftSideBearings.push(metrics.leftSideBearing); 167 | rightSideBearings.push(metrics.rightSideBearing); 168 | advanceWidths.push(glyph.advanceWidth); 169 | } 170 | var globals = { 171 | xMin: Math.min.apply(null, xMins), 172 | yMin: Math.min.apply(null, yMins), 173 | xMax: Math.max.apply(null, xMaxs), 174 | yMax: Math.max.apply(null, yMaxs), 175 | advanceWidthMax: Math.max.apply(null, advanceWidths), 176 | advanceWidthAvg: average(advanceWidths), 177 | minLeftSideBearing: Math.min.apply(null, leftSideBearings), 178 | maxLeftSideBearing: Math.max.apply(null, leftSideBearings), 179 | minRightSideBearing: Math.min.apply(null, rightSideBearings) 180 | }; 181 | globals.ascender = globals.yMax; 182 | globals.descender = globals.yMin; 183 | 184 | var headTable = head.make({ 185 | unitsPerEm: font.unitsPerEm, 186 | xMin: globals.xMin, 187 | yMin: globals.yMin, 188 | xMax: globals.xMax, 189 | yMax: globals.yMax 190 | }); 191 | 192 | var hheaTable = hhea.make({ 193 | // Adding a little here makes OS X Quick Look happy 194 | ascender: globals.ascender, 195 | descender: globals.descender, 196 | advanceWidthMax: globals.advanceWidthMax, 197 | minLeftSideBearing: globals.minLeftSideBearing, 198 | minRightSideBearing: globals.minRightSideBearing, 199 | xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), 200 | numberOfHMetrics: font.glyphs.length 201 | }); 202 | 203 | var maxpTable = maxp.make(font.glyphs.length); 204 | 205 | var codeBounds = charCodeBounds(font.glyphs); 206 | var os2Table = os2.make({ 207 | xAvgCharWidth: Math.round(globals.advanceWidthAvg), 208 | usWeightClass: 500, // Medium FIXME Make this configurable 209 | usWidthClass: 5, // Medium (normal) FIXME Make this configurable 210 | usFirstCharIndex: codeBounds[0], 211 | usLastCharIndex: codeBounds[1], 212 | ulUnicodeRange1: 0x00000001, // Basic Latin 213 | // See http://typophile.com/node/13081 for more info on vertical metrics. 214 | // We get metrics for typical characters (such as "x" for xHeight). 215 | // We provide some fallback characters if characters are unavailable: their 216 | // ordering was chosen experimentally. 217 | sTypoAscender: globals.ascender, 218 | sTypoDescender: globals.descender, 219 | sTypoLineGap: 0, 220 | usWinAscent: globals.ascender, 221 | usWinDescent: -globals.descender, 222 | ulCodePageRange1: 0x00000001, // Basic Latin 223 | sxHeight: metricsForChar(font, 'xyvw', {yMax: 0}).yMax, 224 | sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, 225 | usBreakChar: font.hasChar(' ') ? 32 : 0 // Use space as the break character, if available. 226 | }); 227 | 228 | 229 | var hmtxTable = hmtx.make(font.glyphs); 230 | var cmapTable = cmap.make(font.glyphs); 231 | 232 | var fullName = font.familyName + ' ' + font.styleName; 233 | var postScriptName = font.familyName.replace(/\s/g, '') + '-' + font.styleName; 234 | var nameTable = _name.make({ 235 | copyright: font.copyright, 236 | fontFamily: font.familyName, 237 | fontSubfamily: font.styleName, 238 | uniqueID: font.manufacturer + ':' + fullName, 239 | fullName: fullName, 240 | version: font.version, 241 | postScriptName: postScriptName, 242 | trademark: font.trademark, 243 | manufacturer: font.manufacturer, 244 | designer: font.designer, 245 | description: font.description, 246 | manufacturerURL: font.manufacturerURL, 247 | designerURL: font.designerURL, 248 | license: font.license, 249 | licenseURL: font.licenseURL, 250 | preferredFamily: font.familyName, 251 | preferredSubfamily: font.styleName 252 | }); 253 | var postTable = post.make(); 254 | var cffTable = cff.make(font.glyphs, { 255 | version: font.version, 256 | fullName: fullName, 257 | familyName: font.familyName, 258 | weightName: font.styleName, 259 | postScriptName: postScriptName 260 | }); 261 | // Order the tables according to the the OpenType specification 1.4. 262 | var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; 263 | 264 | var sfntTable = makeSfntTable(tables); 265 | 266 | var bytes = sfntTable.encode(); 267 | var checkSum = computeCheckSum(bytes); 268 | headTable.checkSumAdjustment = 0xB1B0AFBA - checkSum; 269 | 270 | // Build the font again, now with the proper checkSum. 271 | sfntTable = makeSfntTable(tables); 272 | 273 | return sfntTable; 274 | } 275 | 276 | exports.computeCheckSum = computeCheckSum; 277 | exports.make = makeSfntTable; 278 | exports.fontToTable = fontToSfntTable; 279 | -------------------------------------------------------------------------------- /vendor/font.js: -------------------------------------------------------------------------------- 1 | // The Font object 2 | 3 | 'use strict'; 4 | 5 | var path = require('./path'); 6 | var sfnt = require('./tables/sfnt'); 7 | var encoding = require('./encoding'); 8 | 9 | // A Font represents a loaded OpenType font file. 10 | // It contains a set of glyphs and methods to draw text on a drawing context, 11 | // or to get a path representing the text. 12 | function Font(options) { 13 | options = options || {}; 14 | // OS X will complain if the names are empty, so we put a single space everywhere by default. 15 | this.familyName = options.familyName || ' '; 16 | this.styleName = options.styleName || ' '; 17 | this.designer = options.designer || ' '; 18 | this.designerURL = options.designerURL || ' '; 19 | this.manufacturer = options.manufacturer || ' '; 20 | this.manufacturerURL = options.manufacturerURL || ' '; 21 | this.license = options.license || ' '; 22 | this.licenseURL = options.licenseURL || ' '; 23 | this.version = options.version || 'Version 0.1'; 24 | this.description = options.description || ' '; 25 | this.copyright = options.copyright || ' '; 26 | this.trademark = options.trademark || ' '; 27 | this.unitsPerEm = options.unitsPerEm || 1000; 28 | this.supported = true; 29 | this.glyphs = options.glyphs || []; 30 | this.encoding = new encoding.DefaultEncoding(this); 31 | this.tables = {}; 32 | } 33 | 34 | // Check if the font has a glyph for the given character. 35 | Font.prototype.hasChar = function (c) { 36 | return this.encoding.charToGlyphIndex(c) !== null; 37 | }; 38 | 39 | // Convert the given character to a single glyph index. 40 | // Note that this function assumes that there is a one-to-one mapping between 41 | // the given character and a glyph; for complex scripts this might not be the case. 42 | Font.prototype.charToGlyphIndex = function (s) { 43 | return this.encoding.charToGlyphIndex(s); 44 | }; 45 | 46 | // Convert the given character to a single Glyph object. 47 | // Note that this function assumes that there is a one-to-one mapping between 48 | // the given character and a glyph; for complex scripts this might not be the case. 49 | Font.prototype.charToGlyph = function (c) { 50 | var glyphIndex, glyph; 51 | glyphIndex = this.charToGlyphIndex(c); 52 | glyph = this.glyphs[glyphIndex]; 53 | if (!glyph) { 54 | glyph = this.glyphs[0]; // .notdef 55 | } 56 | return glyph; 57 | }; 58 | 59 | // Convert the given text to a list of Glyph objects. 60 | // Note that there is no strict one-to-one mapping between characters and 61 | // glyphs, so the list of returned glyphs can be larger or smaller than the 62 | // length of the given string. 63 | Font.prototype.stringToGlyphs = function (s) { 64 | var i, c, glyphs; 65 | glyphs = []; 66 | for (i = 0; i < s.length; i += 1) { 67 | c = s[i]; 68 | glyphs.push(this.charToGlyph(c)); 69 | } 70 | return glyphs; 71 | }; 72 | 73 | Font.prototype.nameToGlyphIndex = function (name) { 74 | return this.glyphNames.nameToGlyphIndex(name); 75 | }; 76 | 77 | Font.prototype.nameToGlyph = function (name) { 78 | var glyphIndex, glyph; 79 | glyphIndex = this.nametoGlyphIndex(name); 80 | glyph = this.glyphs[glyphIndex]; 81 | if (!glyph) { 82 | glyph = this.glyphs[0]; // .notdef 83 | } 84 | return glyph; 85 | }; 86 | 87 | Font.prototype.glyphIndexToName = function (gid) { 88 | if (!this.glyphNames.glyphIndexToName) { 89 | return ''; 90 | } 91 | return this.glyphNames.glyphIndexToName(gid); 92 | }; 93 | 94 | // Retrieve the value of the kerning pair between the left glyph (or its index) 95 | // and the right glyph (or its index). If no kerning pair is found, return 0. 96 | // The kerning value gets added to the advance width when calculating the spacing 97 | // between glyphs. 98 | Font.prototype.getKerningValue = function (leftGlyph, rightGlyph) { 99 | leftGlyph = leftGlyph.index || leftGlyph; 100 | rightGlyph = rightGlyph.index || rightGlyph; 101 | var gposKerning = this.getGposKerningValue; 102 | return gposKerning ? gposKerning(leftGlyph, rightGlyph) : 103 | (this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0); 104 | }; 105 | 106 | // Helper function that invokes the given callback for each glyph in the given text. 107 | // The callback gets `(glyph, x, y, fontSize, options)`. 108 | Font.prototype.forEachGlyph = function (text, x, y, fontSize, options, callback) { 109 | var kerning, fontScale, glyphs, i, glyph, kerningValue; 110 | if (!this.supported) { 111 | return; 112 | } 113 | x = x !== undefined ? x : 0; 114 | y = y !== undefined ? y : 0; 115 | fontSize = fontSize !== undefined ? fontSize : 72; 116 | options = options || {}; 117 | kerning = options.kerning === undefined ? true : options.kerning; 118 | fontScale = 1 / this.unitsPerEm * fontSize; 119 | glyphs = this.stringToGlyphs(text); 120 | for (i = 0; i < glyphs.length; i += 1) { 121 | glyph = glyphs[i]; 122 | callback(glyph, x, y, fontSize, options); 123 | if (glyph.advanceWidth) { 124 | x += glyph.advanceWidth * fontScale; 125 | } 126 | if (kerning && i < glyphs.length - 1) { 127 | kerningValue = this.getKerningValue(glyph, glyphs[i + 1]); 128 | x += kerningValue * fontScale; 129 | } 130 | } 131 | }; 132 | 133 | // Create a Path object that represents the given text. 134 | // 135 | // text - The text to create. 136 | // x - Horizontal position of the beginning of the text. (default: 0) 137 | // y - Vertical position of the *baseline* of the text. (default: 0) 138 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 139 | // Options is an optional object that contains: 140 | // - kerning - Whether to take kerning information into account. (default: true) 141 | // 142 | // Returns a Path object. 143 | Font.prototype.getPath = function (text, x, y, fontSize, options) { 144 | var fullPath = new path.Path(); 145 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 146 | var path = glyph.getPath(x, y, fontSize); 147 | fullPath.extend(path); 148 | }); 149 | return fullPath; 150 | }; 151 | 152 | // Draw the text on the given drawing context. 153 | // 154 | // ctx - A 2D drawing context, like Canvas. 155 | // text - The text to create. 156 | // x - Horizontal position of the beginning of the text. (default: 0) 157 | // y - Vertical position of the *baseline* of the text. (default: 0) 158 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 159 | // Options is an optional object that contains: 160 | // - kerning - Whether to take kerning information into account. (default: true) 161 | Font.prototype.draw = function (ctx, text, x, y, fontSize, options) { 162 | this.getPath(text, x, y, fontSize, options).draw(ctx); 163 | }; 164 | 165 | // Draw the points of all glyphs in the text. 166 | // On-curve points will be drawn in blue, off-curve points will be drawn in red. 167 | // 168 | // ctx - A 2D drawing context, like Canvas. 169 | // text - The text to create. 170 | // x - Horizontal position of the beginning of the text. (default: 0) 171 | // y - Vertical position of the *baseline* of the text. (default: 0) 172 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 173 | // Options is an optional object that contains: 174 | // - kerning - Whether to take kerning information into account. (default: true) 175 | Font.prototype.drawPoints = function (ctx, text, x, y, fontSize, options) { 176 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 177 | glyph.drawPoints(ctx, x, y, fontSize); 178 | }); 179 | }; 180 | 181 | // Draw lines indicating important font measurements for all glyphs in the text. 182 | // Black lines indicate the origin of the coordinate system (point 0,0). 183 | // Blue lines indicate the glyph bounding box. 184 | // Green line indicates the advance width of the glyph. 185 | // 186 | // ctx - A 2D drawing context, like Canvas. 187 | // text - The text to create. 188 | // x - Horizontal position of the beginning of the text. (default: 0) 189 | // y - Vertical position of the *baseline* of the text. (default: 0) 190 | // fontSize - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. (default: 72) 191 | // Options is an optional object that contains: 192 | // - kerning - Whether to take kerning information into account. (default: true) 193 | Font.prototype.drawMetrics = function (ctx, text, x, y, fontSize, options) { 194 | this.forEachGlyph(text, x, y, fontSize, options, function (glyph, x, y, fontSize) { 195 | glyph.drawMetrics(ctx, x, y, fontSize); 196 | }); 197 | }; 198 | 199 | // Validate 200 | Font.prototype.validate = function () { 201 | var warnings = []; 202 | var font = this; 203 | 204 | function assert(predicate, message) { 205 | if (!predicate) { 206 | warnings.push(message); 207 | } 208 | } 209 | 210 | function assertStringAttribute(attrName) { 211 | assert(font[attrName] && font[attrName].trim().length > 0, 'No ' + attrName + ' specified.'); 212 | } 213 | 214 | // Identification information 215 | assertStringAttribute('familyName'); 216 | assertStringAttribute('weightName'); 217 | assertStringAttribute('manufacturer'); 218 | assertStringAttribute('copyright'); 219 | assertStringAttribute('version'); 220 | 221 | // Dimension information 222 | assert(this.unitsPerEm > 0, 'No unitsPerEm specified.'); 223 | }; 224 | 225 | // Convert the font object to a SFNT data structure. 226 | // This structure contains all the necessary tables and metadata to create a binary OTF file. 227 | Font.prototype.toTables = function () { 228 | return sfnt.fontToTable(this); 229 | }; 230 | 231 | Font.prototype.toBuffer = function () { 232 | var sfntTable = this.toTables(); 233 | var bytes = sfntTable.encode(); 234 | var buffer = new ArrayBuffer(bytes.length); 235 | var intArray = new Uint8Array(buffer); 236 | for (var i = 0; i < bytes.length; i++) { 237 | intArray[i] = bytes[i]; 238 | } 239 | return buffer; 240 | }; 241 | 242 | // Initiate a download of the OpenType font. 243 | Font.prototype.download = function () { 244 | var fileName = this.familyName.replace(/\s/g, '') + '-' + this.styleName + '.otf'; 245 | var buffer = this.toBuffer(); 246 | 247 | window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; 248 | window.requestFileSystem(window.TEMPORARY, buffer.byteLength, function (fs) { 249 | fs.root.getFile(fileName, {create: true}, function (fileEntry) { 250 | fileEntry.createWriter(function (writer) { 251 | var dataView = new DataView(buffer); 252 | var blob = new Blob([dataView], {type: 'font/opentype'}); 253 | writer.write(blob); 254 | 255 | writer.addEventListener('writeend', function () { 256 | // Navigating to the file will download it. 257 | location.href = fileEntry.toURL(); 258 | }, false); 259 | }); 260 | }); 261 | }, function (err) { 262 | throw err; 263 | }); 264 | }; 265 | 266 | exports.Font = Font; 267 | -------------------------------------------------------------------------------- /vendor/tables/glyf.js: -------------------------------------------------------------------------------- 1 | // The `glyf` table describes the glyphs in TrueType outline format. 2 | // http://www.microsoft.com/typography/otspec/glyf.htm 3 | 4 | 'use strict'; 5 | 6 | var check = require('../check'); 7 | var _glyph = require('../glyph'); 8 | var parse = require('../parse'); 9 | var path = require('../path'); 10 | 11 | // Parse the coordinate data for a glyph. 12 | function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { 13 | var v; 14 | if ((flag & shortVectorBitMask) > 0) { 15 | // The coordinate is 1 byte long. 16 | v = p.parseByte(); 17 | // The `same` bit is re-used for short values to signify the sign of the value. 18 | if ((flag & sameBitMask) === 0) { 19 | v = -v; 20 | } 21 | v = previousValue + v; 22 | } else { 23 | // The coordinate is 2 bytes long. 24 | // If the `same` bit is set, the coordinate is the same as the previous coordinate. 25 | if ((flag & sameBitMask) > 0) { 26 | v = previousValue; 27 | } else { 28 | // Parse the coordinate as a signed 16-bit delta value. 29 | v = previousValue + p.parseShort(); 30 | } 31 | } 32 | return v; 33 | } 34 | 35 | // Parse a TrueType glyph. 36 | function parseGlyph(data, start, index, font) { 37 | var p, glyph, flag, i, j, flags, 38 | endPointIndices, numberOfCoordinates, repeatCount, points, point, px, py, 39 | component, moreComponents; 40 | p = new parse.Parser(data, start); 41 | glyph = new _glyph.Glyph({font: font, index: index}); 42 | glyph.numberOfContours = p.parseShort(); 43 | glyph.xMin = p.parseShort(); 44 | glyph.yMin = p.parseShort(); 45 | glyph.xMax = p.parseShort(); 46 | glyph.yMax = p.parseShort(); 47 | if (glyph.numberOfContours > 0) { 48 | // This glyph is not a composite. 49 | endPointIndices = glyph.endPointIndices = []; 50 | for (i = 0; i < glyph.numberOfContours; i += 1) { 51 | endPointIndices.push(p.parseUShort()); 52 | } 53 | 54 | glyph.instructionLength = p.parseUShort(); 55 | glyph.instructions = []; 56 | for (i = 0; i < glyph.instructionLength; i += 1) { 57 | glyph.instructions.push(p.parseByte()); 58 | } 59 | 60 | numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; 61 | flags = []; 62 | for (i = 0; i < numberOfCoordinates; i += 1) { 63 | flag = p.parseByte(); 64 | flags.push(flag); 65 | // If bit 3 is set, we repeat this flag n times, where n is the next byte. 66 | if ((flag & 8) > 0) { 67 | repeatCount = p.parseByte(); 68 | for (j = 0; j < repeatCount; j += 1) { 69 | flags.push(flag); 70 | i += 1; 71 | } 72 | } 73 | } 74 | check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); 75 | 76 | if (endPointIndices.length > 0) { 77 | points = []; 78 | // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. 79 | if (numberOfCoordinates > 0) { 80 | for (i = 0; i < numberOfCoordinates; i += 1) { 81 | flag = flags[i]; 82 | point = {}; 83 | point.onCurve = !!(flag & 1); 84 | point.lastPointOfContour = endPointIndices.indexOf(i) >= 0; 85 | points.push(point); 86 | } 87 | px = 0; 88 | for (i = 0; i < numberOfCoordinates; i += 1) { 89 | flag = flags[i]; 90 | point = points[i]; 91 | point.x = parseGlyphCoordinate(p, flag, px, 2, 16); 92 | px = point.x; 93 | } 94 | 95 | py = 0; 96 | for (i = 0; i < numberOfCoordinates; i += 1) { 97 | flag = flags[i]; 98 | point = points[i]; 99 | point.y = parseGlyphCoordinate(p, flag, py, 4, 32); 100 | py = point.y; 101 | } 102 | } 103 | glyph.points = points; 104 | } else { 105 | glyph.points = []; 106 | } 107 | } else if (glyph.numberOfContours === 0) { 108 | glyph.points = []; 109 | } else { 110 | glyph.isComposite = true; 111 | glyph.points = []; 112 | glyph.components = []; 113 | moreComponents = true; 114 | while (moreComponents) { 115 | flags = p.parseUShort(); 116 | component = { 117 | glyphIndex: p.parseUShort(), 118 | xScale: 1, 119 | scale01: 0, 120 | scale10: 0, 121 | yScale: 1, 122 | dx: 0, 123 | dy: 0 124 | }; 125 | if ((flags & 1) > 0) { 126 | // The arguments are words 127 | component.dx = p.parseShort(); 128 | component.dy = p.parseShort(); 129 | } else { 130 | // The arguments are bytes 131 | component.dx = p.parseChar(); 132 | component.dy = p.parseChar(); 133 | } 134 | if ((flags & 8) > 0) { 135 | // We have a scale 136 | component.xScale = component.yScale = p.parseF2Dot14(); 137 | } else if ((flags & 64) > 0) { 138 | // We have an X / Y scale 139 | component.xScale = p.parseF2Dot14(); 140 | component.yScale = p.parseF2Dot14(); 141 | } else if ((flags & 128) > 0) { 142 | // We have a 2x2 transformation 143 | component.xScale = p.parseF2Dot14(); 144 | component.scale01 = p.parseF2Dot14(); 145 | component.scale10 = p.parseF2Dot14(); 146 | component.yScale = p.parseF2Dot14(); 147 | } 148 | 149 | glyph.components.push(component); 150 | moreComponents = !!(flags & 32); 151 | } 152 | } 153 | return glyph; 154 | } 155 | 156 | // Transform an array of points and return a new array. 157 | function transformPoints(points, transform) { 158 | var newPoints, i, pt, newPt; 159 | newPoints = []; 160 | for (i = 0; i < points.length; i += 1) { 161 | pt = points[i]; 162 | newPt = { 163 | x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, 164 | y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, 165 | onCurve: pt.onCurve, 166 | lastPointOfContour: pt.lastPointOfContour 167 | }; 168 | newPoints.push(newPt); 169 | } 170 | return newPoints; 171 | } 172 | 173 | 174 | function getContours(points) { 175 | var contours, currentContour, i, pt; 176 | contours = []; 177 | currentContour = []; 178 | for (i = 0; i < points.length; i += 1) { 179 | pt = points[i]; 180 | currentContour.push(pt); 181 | if (pt.lastPointOfContour) { 182 | contours.push(currentContour); 183 | currentContour = []; 184 | } 185 | } 186 | check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); 187 | return contours; 188 | } 189 | 190 | // Convert the TrueType glyph outline to a Path. 191 | function getPath(points) { 192 | var p, contours, i, realFirstPoint, j, contour, pt, firstPt, 193 | prevPt, midPt, curvePt, lastPt; 194 | p = new path.Path(); 195 | if (!points) { 196 | return p; 197 | } 198 | contours = getContours(points); 199 | for (i = 0; i < contours.length; i += 1) { 200 | contour = contours[i]; 201 | firstPt = contour[0]; 202 | lastPt = contour[contour.length - 1]; 203 | if (firstPt.onCurve) { 204 | curvePt = null; 205 | // The first point will be consumed by the moveTo command, 206 | // so skip it in the loop. 207 | realFirstPoint = true; 208 | } else { 209 | if (lastPt.onCurve) { 210 | // If the first point is off-curve and the last point is on-curve, 211 | // start at the last point. 212 | firstPt = lastPt; 213 | } else { 214 | // If both first and last points are off-curve, start at their middle. 215 | firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 }; 216 | } 217 | curvePt = firstPt; 218 | // The first point is synthesized, so don't skip the real first point. 219 | realFirstPoint = false; 220 | } 221 | p.moveTo(firstPt.x, firstPt.y); 222 | 223 | for (j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) { 224 | pt = contour[j]; 225 | prevPt = j === 0 ? firstPt : contour[j - 1]; 226 | if (prevPt.onCurve && pt.onCurve) { 227 | // This is a straight line. 228 | p.lineTo(pt.x, pt.y); 229 | } else if (prevPt.onCurve && !pt.onCurve) { 230 | curvePt = pt; 231 | } else if (!prevPt.onCurve && !pt.onCurve) { 232 | midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 }; 233 | p.quadraticCurveTo(prevPt.x, prevPt.y, midPt.x, midPt.y); 234 | curvePt = pt; 235 | } else if (!prevPt.onCurve && pt.onCurve) { 236 | // Previous point off-curve, this point on-curve. 237 | p.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y); 238 | curvePt = null; 239 | } else { 240 | throw new Error('Invalid state.'); 241 | } 242 | } 243 | if (firstPt !== lastPt) { 244 | // Connect the last and first points 245 | if (curvePt) { 246 | p.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y); 247 | } else { 248 | p.lineTo(firstPt.x, firstPt.y); 249 | } 250 | } 251 | } 252 | p.closePath(); 253 | return p; 254 | } 255 | 256 | // Parse all the glyphs according to the offsets from the `loca` table. 257 | function parseGlyfTable(data, start, loca, font) { 258 | var glyphs, i, j, offset, nextOffset, glyph, 259 | component, componentGlyph, transformedPoints; 260 | glyphs = []; 261 | // The last element of the loca table is invalid. 262 | for (i = 0; i < loca.length - 1; i += 1) { 263 | offset = loca[i]; 264 | nextOffset = loca[i + 1]; 265 | if (offset !== nextOffset) { 266 | glyphs.push(parseGlyph(data, start + offset, i, font)); 267 | } else { 268 | glyphs.push(new _glyph.Glyph({font: font, index: i})); 269 | } 270 | } 271 | // Go over the glyphs again, resolving the composite glyphs. 272 | for (i = 0; i < glyphs.length; i += 1) { 273 | glyph = glyphs[i]; 274 | if (glyph.isComposite) { 275 | for (j = 0; j < glyph.components.length; j += 1) { 276 | component = glyph.components[j]; 277 | componentGlyph = glyphs[component.glyphIndex]; 278 | if (componentGlyph.points) { 279 | transformedPoints = transformPoints(componentGlyph.points, component); 280 | glyph.points = glyph.points.concat(transformedPoints); 281 | } 282 | } 283 | } 284 | glyph.path = getPath(glyph.points); 285 | } 286 | return glyphs; 287 | } 288 | 289 | exports.parse = parseGlyfTable; 290 | -------------------------------------------------------------------------------- /vendor/encoding.js: -------------------------------------------------------------------------------- 1 | // Glyph encoding 2 | 3 | 'use strict'; 4 | 5 | var cffStandardStrings = [ 6 | '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 7 | 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 8 | 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 9 | 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 10 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 11 | 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 12 | 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', 13 | 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 14 | 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', 15 | 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 16 | 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 17 | 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 18 | 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', 19 | 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 20 | 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', 21 | 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 22 | 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', 23 | 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 24 | 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', 25 | 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 26 | 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 27 | 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 28 | 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', 29 | 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 30 | 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 31 | 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', 32 | 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 33 | 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 34 | 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 35 | 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 36 | 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 37 | 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', 38 | 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 39 | 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', 40 | 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 41 | 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 42 | 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 43 | 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 44 | 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 45 | 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 46 | 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 47 | 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', 48 | '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; 49 | 50 | var cffStandardEncoding = [ 51 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 52 | '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', 53 | 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 54 | 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 55 | 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 56 | 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 57 | 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 58 | 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', 59 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 60 | 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 61 | 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 62 | 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 63 | 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', 64 | 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 65 | 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', 66 | '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 67 | 'lslash', 'oslash', 'oe', 'germandbls']; 68 | 69 | var cffExpertEncoding = [ 70 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 71 | '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', 72 | 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 73 | 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 74 | 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 75 | 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', 76 | 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', 77 | 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', 78 | 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 79 | 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 80 | 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 81 | 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', 82 | '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 83 | 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 84 | 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', 85 | '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', 86 | 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', 87 | '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', 88 | 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', 89 | 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', 90 | 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 91 | 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', 92 | 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 93 | 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 94 | 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', 95 | 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; 96 | 97 | var standardNames = [ 98 | '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 99 | 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 100 | 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 101 | 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 102 | 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 103 | 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 104 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 105 | 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', 106 | 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', 107 | 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', 108 | 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 109 | 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', 110 | 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 111 | 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', 112 | 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 113 | 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', 114 | 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 115 | 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', 116 | 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 117 | 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', 118 | 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 119 | 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', 120 | 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', 121 | 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', 122 | 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; 123 | 124 | // This is the encoding used for fonts created from scratch. 125 | // It loops through all glyphs and finds the appropriate unicode value. 126 | // Since it's linear time, other encodings will be faster. 127 | function DefaultEncoding(font) { 128 | this.font = font; 129 | } 130 | 131 | DefaultEncoding.prototype.charToGlyphIndex = function (c) { 132 | var code, glyphs, i, glyph, j; 133 | code = c.charCodeAt(0); 134 | glyphs = this.font.glyphs; 135 | if (glyphs) { 136 | for (i = 0; i < glyphs.length; i += 1) { 137 | glyph = glyphs[i]; 138 | for (j = 0; j < glyph.unicodes.length; j += 1) { 139 | if (glyph.unicodes[j] === code) { 140 | return i; 141 | } 142 | } 143 | } 144 | } else { 145 | return null; 146 | } 147 | }; 148 | 149 | function CmapEncoding(cmap) { 150 | this.cmap = cmap; 151 | } 152 | 153 | CmapEncoding.prototype.charToGlyphIndex = function (c) { 154 | return this.cmap.glyphIndexMap[c.charCodeAt(0)] || 0; 155 | }; 156 | 157 | function CffEncoding(encoding, charset) { 158 | this.encoding = encoding; 159 | this.charset = charset; 160 | } 161 | 162 | CffEncoding.prototype.charToGlyphIndex = function (s) { 163 | var code, charName; 164 | code = s.charCodeAt(0); 165 | charName = this.encoding[code]; 166 | return this.charset.indexOf(charName); 167 | }; 168 | 169 | function GlyphNames(post) { 170 | var i; 171 | switch (post.version) { 172 | case 1: 173 | this.names = exports.standardNames.slice(); 174 | break; 175 | case 2: 176 | this.names = new Array(post.numberOfGlyphs); 177 | for (i = 0; i < post.numberOfGlyphs; i++) { 178 | if (post.glyphNameIndex[i] < exports.standardNames.length) { 179 | this.names[i] = exports.standardNames[post.glyphNameIndex[i]]; 180 | } else { 181 | this.names[i] = post.names[post.glyphNameIndex[i] - exports.standardNames.length]; 182 | } 183 | } 184 | break; 185 | case 2.5: 186 | this.names = new Array(post.numberOfGlyphs); 187 | for (i = 0; i < post.numberOfGlyphs; i++) { 188 | this.names[i] = exports.standardNames[i + post.glyphNameIndex[i]]; 189 | } 190 | break; 191 | case 3: 192 | this.names = []; 193 | break; 194 | } 195 | } 196 | 197 | GlyphNames.prototype.nameToGlyphIndex = function (name) { 198 | return this.names.indexOf(name); 199 | }; 200 | 201 | GlyphNames.prototype.glyphIndexToName = function (gid) { 202 | return this.names[gid]; 203 | }; 204 | 205 | function addGlyphNames(font) { 206 | var glyphIndexMap, charCodes, i, c, glyphIndex, glyph; 207 | glyphIndexMap = font.tables.cmap.glyphIndexMap; 208 | charCodes = Object.keys(glyphIndexMap); 209 | for (i = 0; i < charCodes.length; i += 1) { 210 | c = charCodes[i]; 211 | glyphIndex = glyphIndexMap[c]; 212 | glyph = font.glyphs[glyphIndex]; 213 | glyph.addUnicode(parseInt(c)); 214 | } 215 | for (i = 0; i < font.glyphs.length; i += 1) { 216 | glyph = font.glyphs[i]; 217 | if (font.cffEncoding) { 218 | glyph.name = font.cffEncoding.charset[i]; 219 | } else { 220 | glyph.name = font.glyphNames.glyphIndexToName(i); 221 | } 222 | } 223 | } 224 | 225 | exports.cffStandardStrings = cffStandardStrings; 226 | exports.cffStandardEncoding = cffStandardEncoding; 227 | exports.cffExpertEncoding = cffExpertEncoding; 228 | exports.standardNames = standardNames; 229 | exports.DefaultEncoding = DefaultEncoding; 230 | exports.CmapEncoding = CmapEncoding; 231 | exports.CffEncoding = CffEncoding; 232 | exports.GlyphNames = GlyphNames; 233 | exports.addGlyphNames = addGlyphNames; 234 | -------------------------------------------------------------------------------- /tests/runtests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by josh on 3/16/17. 3 | */ 4 | 5 | var test = require('tape'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var assert = require('assert'); 9 | var PImage = require('../src/pureimage'); 10 | 11 | var BUILD_DIR = "build"; 12 | mkdir(BUILD_DIR); 13 | 14 | const white = '#ffffff'; 15 | const WHITE = 0xFFFFFFFF; 16 | const black = '#000000'; 17 | const BLACK = 0x000000FF; 18 | const red = '#ff0000'; 19 | const RED = 0xFF0000FF; 20 | const blue = '#0000ff'; 21 | const BLUE = 0x0000FFFF; 22 | const green = '#00ff00'; 23 | const GREEN = 0x00FF00FF; 24 | 25 | 26 | 27 | test('rgba polygon', (t) => { 28 | let img = PImage.make(100,100); 29 | let ctx = img.getContext('2d'); 30 | 31 | ctx.fillStyle = '#ffffff'; 32 | ctx.fillRect(0,0,1,1); 33 | t.equal(img.getPixelRGBA(0,0),0xffffffff); 34 | 35 | ctx.fillStyle = '#000000'; 36 | ctx.fillRect(0,0,1,1); 37 | t.equal(img.getPixelRGBA(0,0),0x000000ff); 38 | 39 | ctx.fillStyle = 'rgba(255,0,0,1)'; 40 | ctx.fillRect(0,0,1,1); 41 | t.equal(img.getPixelRGBA(0,0),0xff0000ff); 42 | 43 | ctx.fillStyle = 'rgba(0,0,0,1)'; 44 | ctx.fillRect(0,0,1,1); 45 | t.equal(img.getPixelRGBA(0,0),0x000000ff); 46 | 47 | ctx.fillStyle = 'rgba(255,0,0,0.5)'; 48 | ctx.fillRect(0,0,1,1); 49 | t.equal(img.getPixelRGBA(0,0),0x7f0000bf); 50 | 51 | 52 | 53 | ctx.fillStyle = 'white'; 54 | ctx.fillRect(0,0,1,1); 55 | t.equal(img.getPixelRGBA(0,0),WHITE); 56 | ctx.globalAlpha = 0.5; 57 | ctx.fillStyle = red; 58 | ctx.fillRect(0,0,1,1); 59 | t.equal(img.getPixelRGBA(0,0),0xFF7f7fFF); 60 | // return t.end(); 61 | 62 | ctx.globalAlpha = 1.0; 63 | ctx.fillStyle = '#ffffff'; 64 | ctx.fillRect(0,0,100,100); 65 | ctx.fillStyle = '#000000'; 66 | ctx.fillRect(0,25,100,50); 67 | ctx.fillStyle = 'rgba(255,0,0,1.0)'; 68 | ctx.fillRect(50,0,50,50); 69 | ctx.fillStyle = 'rgba(255,0,0,0.5)'; 70 | ctx.fillRect(50,50,50,50); 71 | var path = 'build/rgba_fill.png'; 72 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 73 | console.log("wrote out the png file to",path); 74 | t.end(); 75 | }); 76 | }); 77 | 78 | test('clip path', (t) => { 79 | 80 | const img = PImage.make(100,100); 81 | const c = img.getContext('2d'); 82 | c.fillStyle = 'white'; 83 | c.fillRect(0,0,100,100); 84 | 85 | c.beginPath(); 86 | c.moveTo(10,10); 87 | c.lineTo(100,30); 88 | c.lineTo(50,90); 89 | c.lineTo(10,10); 90 | c.clip(); 91 | 92 | t.false(c.pixelInsideClip(0,0)); 93 | t.false(c.pixelInsideClip(0,20)); 94 | t.true(c.pixelInsideClip(40,30)); 95 | 96 | c.fillStyle = 'black'; 97 | c.fillRect(0,0,100,100); 98 | 99 | var path = 'build/clip_path.png'; 100 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 101 | console.log("wrote out the png file to",path); 102 | t.end(); 103 | }); 104 | }); 105 | 106 | test('fill rect', (t) => { 107 | var img = PImage.make(50,50); 108 | var c = img.getContext('2d'); 109 | c.fillStyle = black; 110 | c.fillRect(0,0,50,50); 111 | c.fillStyle = white; 112 | c.fillRect(0,0,25,25); 113 | 114 | var id1 = c.getImageData(0,0,100,100); 115 | t.equal(id1.width,50); 116 | t.equal(id1.height,50); 117 | t.equal(id1.data.length,50*50*4); 118 | 119 | t.equal(img.getPixelRGBA(0,0),0xFFFFFFFF); 120 | t.equal(img.getPixelRGBA(24,24),0xFFFFFFFF); 121 | t.equal(img.getPixelRGBA(25,25),0x000000FF); 122 | t.equal(img.getPixelRGBA(26,26),0x000000FF); 123 | t.end(); 124 | }); 125 | test('clear rect', (t)=>{ 126 | console.log("clear rect test"); 127 | //clear rect 128 | var img = PImage.make(100,100); 129 | var c = img.getContext('2d'); 130 | c.fillStyle = white; 131 | c.fillRect(0,0,100,100); 132 | c.clearRect(25,25,50,50); 133 | t.equal(img.getPixelRGBA(0,0),0xFFFFFFFF); 134 | t.equal(img.getPixelRGBA(30,30),0x00000000); 135 | t.end(); 136 | }); 137 | /*test('stroke rect', (t)=>{ 138 | var img = PImage.make(50,50); 139 | var c = img.getContext('2d'); 140 | c.fillStyle = black; 141 | c.fillRect(0,0,50,50); 142 | c.strokeStyle = white; 143 | c.strokeRect(0,0,25,25); 144 | t.equal(img.getPixelRGBA(0,0),0xFFFFFFFF); 145 | t.equal(img.getPixelRGBA(1,1),0x000000FF); 146 | t.end(); 147 | });*/ 148 | 149 | /* image loading and saving tests */ 150 | 151 | test('load png', (t)=>{ 152 | PImage.decodePNGFromStream(fs.createReadStream("tests/images/bird.png")).then((img)=>{ 153 | t.equal(img.width,200); 154 | t.equal(img.height,133); 155 | t.end(); 156 | }); 157 | }); 158 | 159 | test('load jpg', (t)=>{ 160 | PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img)=>{ 161 | t.equal(img.width,200); 162 | t.equal(img.height,133); 163 | t.end(); 164 | }).catch((e) => { 165 | t.fail(); 166 | }) 167 | }); 168 | 169 | function makeTestImage() { 170 | var img = PImage.make(50,50); 171 | var c = img.getContext('2d'); 172 | c.fillStyle = black; 173 | c.fillRect(0,0,50,50); 174 | c.fillStyle = white; 175 | c.fillRect(0,0,25,25); 176 | c.fillRect(25,25,25,25); 177 | return img; 178 | } 179 | 180 | test('save png', (t)=>{ 181 | var img = makeTestImage(); 182 | var pth = path.join(BUILD_DIR,"test.png"); 183 | PImage.encodePNGToStream(img,fs.createWriteStream(pth)).then(()=>{ 184 | console.log('done writing'); 185 | PImage.decodePNGFromStream(fs.createReadStream(pth)).then((img)=>{ 186 | t.equal(img.width,50); 187 | t.equal(img.height,50); 188 | t.equal(img.getPixelRGBA(0,0),0xFFFFFFFF); 189 | t.equal(img.getPixelRGBA(0,30),0x000000FF); 190 | t.end(); 191 | }); 192 | }); 193 | }); 194 | 195 | test('save jpg', (t)=>{ 196 | var img = makeTestImage(); 197 | var pth = path.join(BUILD_DIR,"test.jpg"); 198 | PImage.encodeJPEGToStream(img,fs.createWriteStream(pth)).then(()=>{ 199 | PImage.decodeJPEGFromStream(fs.createReadStream(pth)).then((img)=>{ 200 | t.equal(img.width,50); 201 | t.equal(img.height,50); 202 | t.equal(img.getPixelRGBA(0,0),0xFFFFFFFF); 203 | t.equal(img.getPixelRGBA(0,30),0x000000FF); 204 | t.end(); 205 | }); 206 | }); 207 | }); 208 | 209 | test('resize jpg', (t) => { 210 | PImage.decodeJPEGFromStream(fs.createReadStream("tests/images/bird.jpg")).then((img)=>{ 211 | t.equal(img.width,200); 212 | t.equal(img.height,133); 213 | 214 | var img2 = PImage.make(50,50); 215 | var c = img2.getContext('2d'); 216 | c.drawImage(img, 217 | 0, 0, 200, 133, // source dimensions 218 | 0, 0, 50, 50 // destination dimensions 219 | ); 220 | var pth = path.join(BUILD_DIR,"resized_bird.jpg"); 221 | PImage.encodeJPEGToStream(img2,fs.createWriteStream(pth)).then(()=> { 222 | PImage.decodeJPEGFromStream(fs.createReadStream(pth)).then((img)=> { 223 | t.equal(img.width,50); 224 | t.equal(img.height,50); 225 | t.end(); 226 | }); 227 | }); 228 | }).catch((e) => { 229 | console.log(e); 230 | t.fail(); 231 | }) 232 | 233 | }); 234 | 235 | 236 | function mkdir(dir) { 237 | if (!fs.existsSync(dir)) { 238 | console.log("mkdir",dir); 239 | fs.mkdirSync(dir); 240 | } 241 | } 242 | 243 | 244 | test('fill rect', (t) => { 245 | var img = PImage.make(100,100); 246 | var ctx = img.getContext('2d'); 247 | ctx.fillStyle = blue; 248 | ctx.fillRect(10,10,50,50); 249 | t.equal(img.getPixelRGBA(40,40),BLUE); 250 | t.equal(img.getPixelRGBA(0,0),BLACK); 251 | t.end(); 252 | }); 253 | 254 | test('stroke rect', (t) => { 255 | var img = PImage.make(100,100); 256 | var ctx = img.getContext('2d'); 257 | ctx.strokeStyle = blue; 258 | ctx.strokeRect(0,0,50,50); 259 | t.equal(img.getPixelRGBA(0,0),BLUE); 260 | t.equal(img.getPixelRGBA(1,1),BLACK); 261 | t.equal(img.getPixelRGBA(50,40),BLUE); 262 | t.equal(img.getPixelRGBA(50,49),BLUE); 263 | //t.equal(img.getPixelRGBA(50,50),BLUE); TODO: corner is missing for some reason 264 | t.end(); 265 | }); 266 | 267 | 268 | test('fill triangle', (t) => { 269 | var img = PImage.make(100,100); 270 | var ctx = img.getContext('2d'); 271 | ctx.fillStyle = blue; 272 | ctx.beginPath(); 273 | ctx.moveTo(10,10); 274 | ctx.lineTo(100,30); 275 | ctx.lineTo(90,90); 276 | ctx.lineTo(10,10); 277 | ctx.fill(); 278 | t.equal(img.getPixelRGBA(60,50),BLUE); 279 | t.end(); 280 | }); 281 | 282 | test('stroke triangle', (t) => { 283 | var img = PImage.make(100,100); 284 | var ctx = img.getContext('2d'); 285 | ctx.strokeStyle = blue; 286 | ctx.beginPath(); 287 | ctx.moveTo(10,10); 288 | ctx.lineTo(100,30); 289 | ctx.lineTo(90,90); 290 | ctx.lineTo(10,10); 291 | ctx.stroke(); 292 | t.equal(img.getPixelRGBA(10,10),BLUE); 293 | t.equal(img.getPixelRGBA(20,15),BLACK); 294 | t.end(); 295 | //var path = "build/p3.png"; 296 | //PImage.encodePNGToStream(img, fs.createWriteStream(path)).then((e)=>{ 297 | // console.log("wrote out the png file to ",path); 298 | // t.end(); 299 | //}); 300 | }); 301 | 302 | test('fill circle (arc)', (t)=>{ 303 | var img = PImage.make(100,100); 304 | var ctx = img.getContext('2d'); 305 | ctx.fillStyle = blue; 306 | ctx.beginPath(); 307 | ctx.arc(50,50,40,0,Math.PI*2,true); // Outer circle 308 | ctx.closePath(); 309 | ctx.fill(); 310 | t.equal(img.getPixelRGBA(50,20),BLUE); 311 | t.equal(img.getPixelRGBA(10,10),BLACK); 312 | t.end(); 313 | }); 314 | 315 | test('stroke circle (arc)', (t)=>{ 316 | var img = PImage.make(100,100); 317 | var ctx = img.getContext('2d'); 318 | ctx.strokeStyle = blue; 319 | ctx.beginPath(); 320 | ctx.arc(50,50,40,0,Math.PI*2,true); // Outer circle 321 | ctx.closePath(); 322 | ctx.stroke(); 323 | t.equal(img.getPixelRGBA(50,10),BLUE); 324 | t.equal(img.getPixelRGBA(50,50),BLACK); 325 | t.end(); 326 | }); 327 | 328 | test('fill partial circle (arcTo)', (t)=>{ 329 | var img = PImage.make(100,100); 330 | var ctx = img.getContext('2d'); 331 | ctx.fillStyle = green; 332 | ctx.beginPath(); 333 | ctx.arc(50,50,40,0,Math.PI,true); // Outer circle 334 | ctx.closePath(); 335 | ctx.fill(); 336 | //t.equal(img.getPixelRGBA(50,10),BLUE); 337 | //t.equal(img.getPixelRGBA(50,50),BLACK); 338 | //t.end(); 339 | 340 | var path = 'build/arcto.png'; 341 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 342 | // console.log("wrote out the png file to",path); 343 | t.end(); 344 | }); 345 | }); 346 | 347 | test('fill quad curves', (t) => { 348 | var img = PImage.make(150,150); 349 | 350 | var ctx = img.getContext('2d'); 351 | ctx.fillStyle = blue; 352 | 353 | // Quadratric curves example 354 | ctx.beginPath(); 355 | ctx.moveTo(75,25); 356 | ctx.quadraticCurveTo(25,25,25,62.5); 357 | ctx.quadraticCurveTo(25,100,50,100); 358 | ctx.quadraticCurveTo(50,120,30,125); 359 | ctx.quadraticCurveTo(60,120,65,100); 360 | ctx.quadraticCurveTo(125,100,125,62.5); 361 | ctx.quadraticCurveTo(125,25,75,25); 362 | ctx.fill(); 363 | 364 | t.equal(img.getPixelRGBA(80,25),BLACK); 365 | t.equal(img.getPixelRGBA(80,26),BLUE); 366 | t.end(); 367 | }); 368 | 369 | 370 | test('stroke quad curves', (t) => { 371 | var img = PImage.make(150,150); 372 | 373 | var ctx = img.getContext('2d'); 374 | ctx.strokeStyle = blue; 375 | 376 | // Quadratric curves example 377 | ctx.beginPath(); 378 | ctx.moveTo(75,25); 379 | ctx.quadraticCurveTo(25,25,25,62.5); 380 | ctx.quadraticCurveTo(25,100,50,100); 381 | ctx.quadraticCurveTo(50,120,30,125); 382 | ctx.quadraticCurveTo(60,120,65,100); 383 | ctx.quadraticCurveTo(125,100,125,62.5); 384 | ctx.quadraticCurveTo(125,25,75,25); 385 | ctx.stroke(); 386 | 387 | t.equal(img.getPixelRGBA(80,25),BLUE); 388 | t.equal(img.getPixelRGBA(50,50),BLACK); 389 | t.end(); 390 | }); 391 | 392 | test('fill cubic curves', (t) => { 393 | var img = PImage.make(150,150); 394 | var ctx = img.getContext('2d'); 395 | ctx.fillStyle = blue; 396 | // Cubic curves example 397 | ctx.beginPath(); 398 | ctx.moveTo(75,40); 399 | ctx.bezierCurveTo(75,37,70,25,50,25); 400 | ctx.bezierCurveTo(20,25,20,62.5,20,62.5); 401 | ctx.bezierCurveTo(20,80,40,102,75,120); 402 | ctx.bezierCurveTo(110,102,130,80,130,62.5); 403 | ctx.bezierCurveTo(130,62.5,130,25,100,25); 404 | ctx.bezierCurveTo(85,25,75,37,75,40); 405 | ctx.fill(); 406 | t.end(); 407 | }); 408 | 409 | test('stroke cubic curves', (t) => { 410 | var img = PImage.make(150,150); 411 | var ctx = img.getContext('2d'); 412 | ctx.strokeStyle = blue; 413 | // Cubic curves example 414 | ctx.beginPath(); 415 | ctx.moveTo(75,40); 416 | ctx.bezierCurveTo(75,37,70,25,50,25); 417 | ctx.bezierCurveTo(20,25,20,62.5,20,62.5); 418 | ctx.bezierCurveTo(20,80,40,102,75,120); 419 | ctx.bezierCurveTo(110,102,130,80,130,62.5); 420 | ctx.bezierCurveTo(130,62.5,130,25,100,25); 421 | ctx.bezierCurveTo(85,25,75,37,75,40); 422 | ctx.stroke(); 423 | t.end(); 424 | }); 425 | 426 | 427 | 428 | test('font test', (t) => { 429 | var fnt = PImage.registerFont('tests/fonts/SourceSansPro-Regular.ttf','Source Sans Pro'); 430 | fnt.load(function() { 431 | var img = PImage.make(200,200); 432 | var ctx = img.getContext('2d'); 433 | ctx.fillStyle = '#ffffff'; 434 | ctx.font = "48pt 'Source Sans Pro'"; 435 | ctx.fillText("ABC", 80, 80); 436 | var path = 'build/text.png'; 437 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 438 | // console.log("wrote out the png file to",path); 439 | t.end(); 440 | }); 441 | }); 442 | 443 | }); 444 | 445 | function calcCrop(img1, specs) { 446 | var scw = specs.width / img1.width; 447 | var sch = specs.height / img1.height; 448 | var sc = 1; 449 | if(sch > scw) { 450 | //scale height first 451 | sc = sch; 452 | } else { 453 | sc = scw; 454 | } 455 | //specs.width / scale 456 | var ow = specs.width / sc; 457 | var oh = specs.height / sc; 458 | // console.log(ow,oh); 459 | var ow2 = img1.width-ow; 460 | var oh2 = img1.height-oh; 461 | return { 462 | sx:Math.floor(ow2/2), 463 | sy:Math.floor(oh2/2), 464 | sw:ow, 465 | sh:oh, 466 | dx:0, 467 | dy:0, 468 | dw:specs.width, 469 | dh:specs.height 470 | } 471 | } 472 | 473 | test('image cropping', (t)=>{ 474 | var specs = {width:100, height:133}; 475 | PImage.decodeJPEGFromStream(fs.createReadStream('tests/images/bird.jpg')).then((src)=>{ 476 | // console.log('source image',src.width,src.height, "to",specs); 477 | var calcs = calcCrop(src, specs); 478 | // console.log(calcs); 479 | var img = PImage.make(specs.width, specs.height); 480 | var ctx = img.getContext('2d'); 481 | ctx.drawImage(src, 482 | calcs.sx, calcs.sy, calcs.sw, calcs.sh, 483 | calcs.dx, calcs.dy, calcs.dw, calcs.dh); 484 | PImage.encodePNGToStream(img, fs.createWriteStream('build/croptest.png')).then(()=>{ 485 | // console.log('wrote to build/croptest.png'); 486 | t.end(); 487 | }); 488 | }); 489 | }); 490 | 491 | test('aa polygon', (t) => { 492 | var img = PImage.make(100,100); 493 | var ctx = img.getContext('2d'); 494 | ctx.strokeStyle = white; 495 | ctx.lineWidth = 1; 496 | ctx.imageSmoothingEnabled = true; 497 | ctx.beginPath(); 498 | ctx.moveTo(10,10); 499 | ctx.lineTo(80,30); 500 | ctx.lineTo(50,90); 501 | ctx.lineTo(10,10); 502 | ctx.stroke(); 503 | 504 | var path = 'build/aa.png'; 505 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 506 | // console.log("wrote out the png file to",path); 507 | t.end(); 508 | }); 509 | }); 510 | 511 | test('aa polygon fill', (t) => { 512 | var img = PImage.make(100,100); 513 | var ctx = img.getContext('2d'); 514 | ctx.fillStyle = '#ffffff'; 515 | ctx.imageSmoothingEnabled = true; 516 | ctx.beginPath(); 517 | ctx.moveTo(10,10); 518 | ctx.lineTo(80,30); 519 | ctx.lineTo(50,90); 520 | ctx.lineTo(10,10); 521 | ctx.fill(); 522 | 523 | var path = 'build/aa_fill.png'; 524 | PImage.encodePNGToStream(img, fs.createWriteStream(path)).then(()=>{ 525 | // console.log("wrote out the png file to",path); 526 | t.end(); 527 | }); 528 | }); 529 | --------------------------------------------------------------------------------