├── .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 |
5 |
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 = '';
158 | return svg;
159 | };
160 |
161 | exports.Path = Path;
162 |
--------------------------------------------------------------------------------
/vendor/tables/os2.js:
--------------------------------------------------------------------------------
1 | // The `OS/2` table contains metrics required in OpenType fonts.
2 | // https://www.microsoft.com/typography/OTSPEC/os2.htm
3 |
4 | 'use strict';
5 |
6 | var parse = require('../parse');
7 | var table = require('../table');
8 |
9 | // Parse the OS/2 and Windows metrics `OS/2` table
10 | function parseOS2Table(data, start) {
11 | var os2 = {},
12 | p = new parse.Parser(data, start);
13 | os2.version = p.parseUShort();
14 | os2.xAvgCharWidth = p.parseShort();
15 | os2.usWeightClass = p.parseUShort();
16 | os2.usWidthClass = p.parseUShort();
17 | os2.fsType = p.parseUShort();
18 | os2.ySubscriptXSize = p.parseShort();
19 | os2.ySubscriptYSize = p.parseShort();
20 | os2.ySubscriptXOffset = p.parseShort();
21 | os2.ySubscriptYOffset = p.parseShort();
22 | os2.ySuperscriptXSize = p.parseShort();
23 | os2.ySuperscriptYSize = p.parseShort();
24 | os2.ySuperscriptXOffset = p.parseShort();
25 | os2.ySuperscriptYOffset = p.parseShort();
26 | os2.yStrikeoutSize = p.parseShort();
27 | os2.yStrikeoutPosition = p.parseShort();
28 | os2.sFamilyClass = p.parseShort();
29 | os2.panose = [];
30 | for (var i = 0; i < 10; i++) {
31 | os2.panose[i] = p.parseByte();
32 | }
33 | os2.ulUnicodeRange1 = p.parseULong();
34 | os2.ulUnicodeRange2 = p.parseULong();
35 | os2.ulUnicodeRange3 = p.parseULong();
36 | os2.ulUnicodeRange4 = p.parseULong();
37 | os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte());
38 | os2.fsSelection = p.parseUShort();
39 | os2.usFirstCharIndex = p.parseUShort();
40 | os2.usLastCharIndex = p.parseUShort();
41 | os2.sTypoAscender = p.parseShort();
42 | os2.sTypoDescender = p.parseShort();
43 | os2.sTypoLineGap = p.parseShort();
44 | os2.usWinAscent = p.parseUShort();
45 | os2.usWinDescent = p.parseUShort();
46 | if (os2.version >= 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 |
--------------------------------------------------------------------------------