Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

54
static/js/ketcher2/node_modules/svg2ttf/lib/math.js generated vendored Normal file
View File

@ -0,0 +1,54 @@
'use strict';
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function (point) {
return new Point(this.x + point.x, this.y + point.y);
};
Point.prototype.sub = function (point) {
return new Point(this.x - point.x, this.y - point.y);
};
Point.prototype.mul = function (value) {
return new Point(this.x * value, this.y * value);
};
Point.prototype.div = function (value) {
return new Point(this.x / value, this.y / value);
};
Point.prototype.dist = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Point.prototype.sqr = function () {
return this.x * this.x + this.y * this.y;
};
/*
* Check if 3 points are in line, and second in the midle.
* Used to replace quad curves with lines or join lines
*
*/
function isInLine(p1, m, p2, accuracy) {
var a = p1.sub(m).sqr();
var b = p2.sub(m).sqr();
var c = p1.sub(p2).sqr();
// control point not between anchors
if ((a > (b + c)) || (b > (a + c))) {
return false;
}
// count distance via scalar multiplication
var distance = Math.sqrt(Math.pow((p1.x - m.x) * (p2.y - m.y) - (p2.x - m.x) * (p1.y - m.y), 2) / c);
return distance < accuracy ? true : false;
}
module.exports.Point = Point;
module.exports.isInLine = isInLine;

348
static/js/ketcher2/node_modules/svg2ttf/lib/sfnt.js generated vendored Normal file
View File

@ -0,0 +1,348 @@
'use strict';
var _ = require('lodash');
function Font() {
this.ascent = 850;
this.copyright = '';
this.createdDate = new Date();
this.glyphs = [];
this.ligatures = [];
// Maping of code points to glyphs.
// Keys are actually numeric, thus should be `parseInt`ed.
this.codePoints = {};
this.isFixedPitch = 0;
this.italicAngle = 0;
this.familyClass = 0; // No Classification
this.familyName = '';
this.fsSelection = 0x40; // Characters are in the standard weight/style for the font.
// Non zero value can cause issues in IE, https://github.com/fontello/svg2ttf/issues/45
this.fsType = 0;
this.lowestRecPPEM = 8;
this.macStyle = 0;
this.modifiedDate = new Date();
this.panose = {
familyType: 2, // Latin Text
serifStyle: 0, // any
weight: 5, // book
proportion: 3, //modern
contrast: 0, //any
strokeVariation: 0, //any,
armStyle: 0, //any,
letterform: 0, //any,
midline: 0, //any,
xHeight: 0 //any,
};
this.revision = 1;
this.sfntNames = [];
this.underlineThickness = 0;
this.unitsPerEm = 1000;
this.weightClass = 400; // normal
this.width = 1000;
this.widthClass = 5; // Medium (normal)
this.ySubscriptXOffset = 0;
this.ySuperscriptXOffset = 0;
this.int_descent = -150;
//getters and setters
Object.defineProperty(this, 'descent', {
get: function () {
return this.int_descent;
},
set: function (value) {
this.int_descent = parseInt(Math.round(-Math.abs(value)), 10);
}
});
this.__defineGetter__('avgCharWidth', function () {
if (this.glyphs.length === 0) {
return 0;
}
var widths = _.map(this.glyphs, 'width');
return parseInt(widths.reduce(function (prev, cur) {
return prev + cur;
}) / widths.length, 10);
});
Object.defineProperty(this, 'ySubscriptXSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptXSize) ? this.int_ySubscriptXSize : (this.width * 0.6347), 10);
},
set: function (value) {
this.int_ySubscriptXSize = value;
}
});
Object.defineProperty(this, 'ySubscriptYSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptYSize) ? this.int_ySubscriptYSize : ((this.ascent - this.descent) * 0.7), 10);
},
set: function (value) {
this.int_ySubscriptYSize = value;
}
});
Object.defineProperty(this, 'ySubscriptYOffset', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptYOffset) ? this.int_ySubscriptYOffset : ((this.ascent - this.descent) * 0.14), 10);
},
set: function (value) {
this.int_ySubscriptYOffset = value;
}
});
Object.defineProperty(this, 'ySuperscriptXSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptXSize) ? this.int_ySuperscriptXSize : (this.width * 0.6347), 10);
},
set: function (value) {
this.int_ySuperscriptXSize = value;
}
});
Object.defineProperty(this, 'ySuperscriptYSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptYSize) ? this.int_ySuperscriptYSize : ((this.ascent - this.descent) * 0.7), 10);
},
set: function (value) {
this.int_ySuperscriptYSize = value;
}
});
Object.defineProperty(this, 'ySuperscriptYOffset', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptYOffset) ? this.int_ySuperscriptYOffset : ((this.ascent - this.descent) * 0.48), 10);
},
set: function (value) {
this.int_ySuperscriptYOffset = value;
}
});
Object.defineProperty(this, 'yStrikeoutSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_yStrikeoutSize) ? this.int_yStrikeoutSize : ((this.ascent - this.descent) * 0.049), 10);
},
set: function (value) {
this.int_yStrikeoutSize = value;
}
});
Object.defineProperty(this, 'yStrikeoutPosition', {
get: function () {
return parseInt(!_.isUndefined(this.int_yStrikeoutPosition) ? this.int_yStrikeoutPosition : ((this.ascent - this.descent) * 0.258), 10);
},
set: function (value) {
this.int_yStrikeoutPosition = value;
}
});
Object.defineProperty(this, 'minLsb', {
get: function () {
return parseInt(_.min(_.map(this.glyphs, 'xMin')), 10);
}
});
Object.defineProperty(this, 'minRsb', {
get: function () {
if (!this.glyphs.length) return parseInt(this.width, 10);
return parseInt(_.reduce(this.glyphs, function (minRsb, glyph) {
return Math.min(minRsb, glyph.width - glyph.xMax);
}, 0), 10);
}
});
Object.defineProperty(this, 'xMin', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (xMin, glyph) {
return Math.min(xMin, glyph.xMin);
}, 0);
}
});
Object.defineProperty(this, 'yMin', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (yMin, glyph) {
return Math.min(yMin, glyph.yMin);
}, 0);
}
});
Object.defineProperty(this, 'xMax', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (xMax, glyph) {
return Math.max(xMax, glyph.xMax);
}, 0);
}
});
Object.defineProperty(this, 'yMax', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (yMax, glyph) {
return Math.max(yMax, glyph.yMax);
}, 0);
}
});
Object.defineProperty(this, 'avgWidth', {
get: function () {
var len = this.glyphs.length;
if (len === 0) {
return this.width;
}
var sumWidth = _.reduce(this.glyphs, function (sumWidth, glyph) {
return sumWidth + glyph.width;
}, 0);
return Math.round(sumWidth / len);
}
});
Object.defineProperty(this, 'maxWidth', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (maxWidth, glyph) {
return Math.max(maxWidth, glyph.width);
}, 0);
}
});
Object.defineProperty(this, 'maxExtent', {
get: function () {
if (!this.glyphs.length) return this.width;
return _.reduce(this.glyphs, function (maxExtent, glyph) {
return Math.max(maxExtent, glyph.xMax /*- glyph.xMin*/);
}, 0);
}
});
// Property used for `sTypoLineGap` in OS/2 and not used for `lineGap` in HHEA, because
// non zero lineGap causes bad offset in IE, https://github.com/fontello/svg2ttf/issues/37
Object.defineProperty(this, 'lineGap', {
get: function () {
return parseInt(!_.isUndefined(this.int_lineGap) ? this.int_lineGap : ((this.ascent - this.descent) * 0.09), 10);
},
set: function (value) {
this.int_lineGap = value;
}
});
Object.defineProperty(this, 'underlinePosition', {
get: function () {
return parseInt(!_.isUndefined(this.int_underlinePosition) ? this.int_underlinePosition : ((this.ascent - this.descent) * 0.01), 10);
},
set: function (value) {
this.int_underlinePosition = value;
}
});
}
function Glyph() {
this.contours = [];
this.d = '';
this.id = '';
this.height = 0;
this.name = '';
this.width = 0;
}
Object.defineProperty(Glyph.prototype, 'xMin', {
get: function () {
var xMin = 0;
var hasPoints = false;
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
xMin = Math.min(xMin, Math.floor(point.x));
hasPoints = true;
});
});
return hasPoints ? xMin : 0;
}
});
Object.defineProperty(Glyph.prototype, 'xMax', {
get: function () {
var xMax = 0;
var hasPoints = false;
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
xMax = Math.max(xMax, -Math.floor(-point.x));
hasPoints = true;
});
});
return hasPoints ? xMax : this.width;
}
});
Object.defineProperty(Glyph.prototype, 'yMin', {
get: function () {
var yMin = 0;
var hasPoints = false;
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
yMin = Math.min(yMin, Math.floor(point.y));
hasPoints = true;
});
});
return hasPoints ? yMin : 0;
}
});
Object.defineProperty(Glyph.prototype, 'yMax', {
get: function () {
var yMax = 0;
var hasPoints = false;
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
yMax = Math.max(yMax, -Math.floor(-point.y));
hasPoints = true;
});
});
return hasPoints ? yMax : 0;
}
});
function Contour() {
this.points = [];
}
function Point() {
this.onCurve = true;
this.x = 0;
this.y = 0;
}
function SfntName() {
this.id = 0;
this.value = '';
}
module.exports.Font = Font;
module.exports.Glyph = Glyph;
module.exports.Contour = Contour;
module.exports.Point = Point;
module.exports.SfntName = SfntName;
module.exports.toTTF = require('./ttf');

43
static/js/ketcher2/node_modules/svg2ttf/lib/str.js generated vendored Normal file
View File

@ -0,0 +1,43 @@
'use strict';
function Str(str) {
if (!(this instanceof Str)) {
return new Str(str);
}
this.str = str;
this.toUTF8Bytes = function () {
var byteArray = [];
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) <= 0x7F) {
byteArray.push(str.charCodeAt(i));
} else {
var h = encodeURIComponent(str.charAt(i)).substr(1).split('%');
for (var j = 0; j < h.length; j++) {
byteArray.push(parseInt(h[j], 16));
}
}
}
return byteArray;
};
this.toUCS2Bytes = function () {
// Code is taken here:
// http://stackoverflow.com/questions/6226189/how-to-convert-a-string-to-bytearray
var byteArray = [];
var ch;
for (var i = 0; i < str.length; ++i) {
ch = str.charCodeAt(i); // get char
byteArray.push(ch >> 8);
byteArray.push(ch & 0xFF);
}
return byteArray;
};
}
module.exports = Str;

223
static/js/ketcher2/node_modules/svg2ttf/lib/svg.js generated vendored Normal file
View File

@ -0,0 +1,223 @@
'use strict';
var _ = require('lodash');
var cubic2quad = require('cubic2quad');
var DOMParser = require('xmldom').DOMParser;
var ucs2 = require('./ucs2');
function getGlyph(glyphElem) {
var glyph = {};
glyph.d = glyphElem.getAttribute('d').trim();
glyph.unicode = [];
if (glyphElem.getAttribute('unicode')) {
glyph.character = glyphElem.getAttribute('unicode');
var unicode = ucs2.decode(glyph.character);
// If more than one code point is involved, the glyph is a ligature glyph
if (unicode.length > 1) {
glyph.ligature = glyph.character;
glyph.ligatureCodes = unicode;
} else {
glyph.unicode.push(unicode[0]);
}
}
glyph.name = glyphElem.getAttribute('glyph-name');
if (glyphElem.getAttribute('horiz-adv-x')) {
glyph.width = parseInt(glyphElem.getAttribute('horiz-adv-x'), 10);
}
return glyph;
}
function deduplicateGlyps(glyphs, ligatures) {
// Result (the list of unique glyphs)
var result = [];
_.forEach(glyphs, function (glyph) {
// Search for glyphs with the same properties (width and d)
var canonical = _.find(result, { width: glyph.width, d: glyph.d });
if (canonical) {
// Add the code points to the unicode array.
// The fields “name” and “character” are not that important so we leave them how we first enounter them and throw the rest away
canonical.unicode = canonical.unicode.concat(glyph.unicode);
glyph.canonical = canonical;
} else {
result.push(glyph);
}
});
// Update ligatures to point to the canonical version
_.forEach(ligatures, function (ligature) {
while (_.has(ligature.glyph, 'canonical')) {
ligature.glyph = ligature.glyph.canonical;
}
});
return result;
}
function load(str) {
var attrs;
var doc = (new DOMParser()).parseFromString(str, 'application/xml');
var metadata, fontElem, fontFaceElem;
metadata = doc.getElementsByTagName('metadata')[0];
fontElem = doc.getElementsByTagName('font')[0];
if (!fontElem) {
throw new Error("Can't find <font> tag. Make sure you SVG file is font, not image.");
}
fontFaceElem = fontElem.getElementsByTagName('font-face')[0];
var familyName = fontFaceElem.getAttribute('font-family') || 'fontello';
var subfamilyName = fontFaceElem.getAttribute('font-style') || 'Regular';
var id = fontElem.getAttribute('id') || (familyName + '-' + subfamilyName).replace(/[\s\(\)\[\]<>%\/]/g, '').substr(0, 62);
var font = {
id: id,
familyName: familyName,
subfamilyName: subfamilyName,
stretch: fontFaceElem.getAttribute('font-stretch') || 'normal'
};
// Doesn't work with complex content like <strong>Copyright:></strong><em>Fontello</em>
if (metadata && metadata.textContent) {
font.metadata = metadata.textContent;
}
// Get <font> numeric attributes
attrs = {
width: 'horiz-adv-x',
//height: 'vert-adv-y',
horizOriginX: 'horiz-origin-x',
horizOriginY: 'horiz-origin-y',
vertOriginX: 'vert-origin-x',
vertOriginY: 'vert-origin-y'
};
_.forEach(attrs, function (val, key) {
if (fontElem.hasAttribute(val)) { font[key] = parseInt(fontElem.getAttribute(val), 10); }
});
// Get <font-face> numeric attributes
attrs = {
ascent: 'ascent',
descent: 'descent',
unitsPerEm: 'units-per-em'
};
_.forEach(attrs, function (val, key) {
if (fontFaceElem.hasAttribute(val)) { font[key] = parseInt(fontFaceElem.getAttribute(val), 10); }
});
if (fontFaceElem.hasAttribute('font-weight')) {
font.weightClass = fontFaceElem.getAttribute('font-weight');
}
var missingGlyphElem = fontElem.getElementsByTagName('missing-glyph')[0];
if (missingGlyphElem) {
font.missingGlyph = {};
font.missingGlyph.d = missingGlyphElem.getAttribute('d') || '';
if (missingGlyphElem.getAttribute('horiz-adv-x')) {
font.missingGlyph.width = parseInt(missingGlyphElem.getAttribute('horiz-adv-x'), 10);
}
}
var glyphs = [];
var ligatures = [];
_.forEach(fontElem.getElementsByTagName('glyph'), function (glyphElem) {
var glyph = getGlyph(glyphElem);
if (_.has(glyph, 'ligature')) {
ligatures.push({
ligature: glyph.ligature,
unicode: glyph.ligatureCodes,
glyph: glyph
});
}
glyphs.push(glyph);
});
glyphs = deduplicateGlyps(glyphs, ligatures);
font.glyphs = glyphs;
font.ligatures = ligatures;
return font;
}
function cubicToQuad(segment, index, x, y, accuracy) {
if (segment[0] === 'C') {
var quadCurves = cubic2quad(
x, y,
segment[1], segment[2],
segment[3], segment[4],
segment[5], segment[6],
accuracy
);
var res = [];
for (var i = 2; i < quadCurves.length; i += 4) {
res.push([ 'Q', quadCurves[i], quadCurves[i + 1], quadCurves[i + 2], quadCurves[i + 3] ]);
}
return res;
}
}
// Converts svg points to contours. All points must be converted
// to relative ones, smooth curves must be converted to generic ones
// before this conversion.
//
function toSfntCoutours(svgPath) {
var resContours = [];
var resContour = [];
svgPath.iterate(function (segment, index, x, y) {
//start new contour
if (index === 0 || segment[0] === 'M') {
resContour = [];
resContours.push(resContour);
}
var name = segment[0];
if (name === 'Q') {
//add control point of quad spline, it is not on curve
resContour.push({ x: segment[1], y: segment[2], onCurve: false });
}
// add on-curve point
if (name === 'H') {
// vertical line has Y coordinate only, X remains the same
resContour.push({ x: segment[1], y: y, onCurve: true });
} else if (name === 'V') {
// horizontal line has X coordinate only, Y remains the same
resContour.push({ x: x, y: segment[1], onCurve: true });
} else if (name !== 'Z') {
// for all commands (except H and V) X and Y are placed in the end of the segment
resContour.push({ x: segment[segment.length - 2], y: segment[segment.length - 1], onCurve: true });
}
});
return resContours;
}
module.exports.load = load;
module.exports.cubicToQuad = cubicToQuad;
module.exports.toSfntCoutours = toSfntCoutours;

161
static/js/ketcher2/node_modules/svg2ttf/lib/ttf.js generated vendored Normal file
View File

@ -0,0 +1,161 @@
'use strict';
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
var createGSUBTable = require('./ttf/tables/gsub');
var createOS2Table = require('./ttf/tables/os2');
var createCMapTable = require('./ttf/tables/cmap');
var createGlyfTable = require('./ttf/tables/glyf');
var createHeadTable = require('./ttf/tables/head');
var createHHeadTable = require('./ttf/tables/hhea');
var createHtmxTable = require('./ttf/tables/hmtx');
var createLocaTable = require('./ttf/tables/loca');
var createMaxpTable = require('./ttf/tables/maxp');
var createNameTable = require('./ttf/tables/name');
var createPostTable = require('./ttf/tables/post');
var utils = require('./ttf/utils');
// Tables
var TABLES = [
{ innerName: 'GSUB', order: 4, create: createGSUBTable }, // GSUB
{ innerName: 'OS/2', order: 4, create: createOS2Table }, // OS/2
{ innerName: 'cmap', order: 6, create: createCMapTable }, // cmap
{ innerName: 'glyf', order: 8, create: createGlyfTable }, // glyf
{ innerName: 'head', order: 2, create: createHeadTable }, // head
{ innerName: 'hhea', order: 1, create: createHHeadTable }, // hhea
{ innerName: 'hmtx', order: 5, create: createHtmxTable }, // hmtx
{ innerName: 'loca', order: 7, create: createLocaTable }, // loca
{ innerName: 'maxp', order: 3, create: createMaxpTable }, // maxp
{ innerName: 'name', order: 9, create: createNameTable }, // name
{ innerName: 'post', order: 10, create: createPostTable } // post
];
// Various constants
var CONST = {
VERSION: 0x10000,
CHECKSUM_ADJUSTMENT: 0xB1B0AFBA
};
function ulong(t) {
t &= 0xffffffff;
if (t < 0) {
t += 0x100000000;
}
return t;
}
function calc_checksum(buf) {
var sum = 0;
var nlongs = Math.floor(buf.length / 4);
var i;
for (i = 0; i < nlongs; ++i) {
var t = buf.getUint32(i * 4);
sum = ulong(sum + t);
}
var leftBytes = buf.length - nlongs * 4; //extra 1..3 bytes found, because table is not aligned. Need to include them in checksum too.
if (leftBytes > 0) {
var leftRes = 0;
for (i = 0; i < 4; i++) {
leftRes = (leftRes << 8) + ((i < leftBytes) ? buf.getUint8(nlongs * 4 + i) : 0);
}
sum = ulong(sum + leftRes);
}
return sum;
}
function generateTTF(font) {
// Prepare TTF contours objects. Note, that while sfnt countours are classes,
// ttf contours are just plain arrays of points
_.forEach(font.glyphs, function (glyph) {
glyph.ttfContours = _.map(glyph.contours, function (contour) {
return contour.points;
});
});
// Process ttf contours data
_.forEach(font.glyphs, function (glyph) {
// 0.3px accuracy is ok. fo 1000x1000.
glyph.ttfContours = utils.simplify(glyph.ttfContours, 0.3);
glyph.ttfContours = utils.simplify(glyph.ttfContours, 0.3); // one pass is not enougth
// Interpolated points can be removed. 1.1px is acceptable
// measure - it will give us 1px error after coordinates rounding.
glyph.ttfContours = utils.interpolate(glyph.ttfContours, 1.1);
glyph.ttfContours = utils.roundPoints(glyph.ttfContours);
glyph.ttfContours = utils.removeClosingReturnPoints(glyph.ttfContours);
glyph.ttfContours = utils.toRelative(glyph.ttfContours);
});
// Add tables
var headerSize = 12 + 16 * TABLES.length; // TTF header plus table headers
var bufSize = headerSize;
_.forEach(TABLES, function (table) {
//store each table in its own buffer
table.buffer = table.create(font);
table.length = table.buffer.length;
table.corLength = table.length + (4 - table.length % 4) % 4; // table size should be divisible to 4
table.checkSum = calc_checksum(table.buffer);
bufSize += table.corLength;
});
//calculate offsets
var offset = headerSize;
_.forEach(_.sortBy(TABLES, 'order'), function (table) {
table.offset = offset;
offset += table.corLength;
});
//create TTF buffer
var buf = new ByteBuffer(bufSize);
//special constants
var entrySelector = Math.floor(Math.log(TABLES.length) / Math.LN2);
var searchRange = Math.pow(2, entrySelector) * 16;
var rangeShift = TABLES.length * 16 - searchRange;
// Add TTF header
buf.writeUint32(CONST.VERSION);
buf.writeUint16(TABLES.length);
buf.writeUint16(searchRange);
buf.writeUint16(entrySelector);
buf.writeUint16(rangeShift);
_.forEach(TABLES, function (table) {
buf.writeUint32(utils.identifier(table.innerName)); //inner name
buf.writeUint32(table.checkSum); //checksum
buf.writeUint32(table.offset); //offset
buf.writeUint32(table.length); //length
});
var headOffset = 0;
_.forEach(_.sortBy(TABLES, 'order'), function (table) {
if (table.innerName === 'head') { //we must store head offset to write font checksum
headOffset = buf.tell();
}
buf.writeBytes(table.buffer.buffer);
for (var i = table.length; i < table.corLength; i++) { //align table to be divisible to 4
buf.writeUint8(0);
}
});
// Write font checksum (corrected by magic value) into HEAD table
buf.setUint32(headOffset + 8, ulong(CONST.CHECKSUM_ADJUSTMENT - calc_checksum(buf)));
return buf;
}
module.exports = generateTTF;

View File

@ -0,0 +1,304 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/cmap.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
function getIDByUnicode(font, unicode) {
return font.codePoints[unicode] ? font.codePoints[unicode].id : 0;
}
// Calculate character segments with non-interruptable chains of unicodes
function getSegments(font, bounds) {
bounds = bounds || Number.MAX_VALUE;
var result = [];
var segment;
// prevEndCode only changes when a segment closes
_.forEach(font.codePoints, function (glyph, unicode) {
unicode = parseInt(unicode, 10);
if (unicode >= bounds) {
return false;
}
// Initialize first segment or add new segment if code "hole" is found
if (!segment || unicode !== (segment.end + 1)) {
if (segment) {
result.push(segment);
}
segment = {
start: unicode
};
}
segment.end = unicode;
});
// Need to finish the last segment
if (segment) {
result.push(segment);
}
_.forEach(result, function (segment) {
segment.length = segment.end - segment.start + 1;
});
return result;
}
// Returns an array of {unicode, glyph} sets for all valid code points up to bounds
function getCodePoints(codePoints, bounds) {
bounds = bounds || Number.MAX_VALUE;
var result = [];
_.forEach(codePoints, function (glyph, unicode) {
unicode = parseInt(unicode, 10);
// Since this is a sparse array, iterating will only yield the valid code points
if (unicode > bounds) {
return false;
}
result.push({
unicode: unicode,
glyph: glyph
});
});
return result;
}
function bufferForTable(format, length) {
var fieldWidth = format === 8 || format === 10 || format === 12 || format === 13 ? 4 : 2;
length += (0
+ fieldWidth // Format
+ fieldWidth // Length
+ fieldWidth // Language
);
var LANGUAGE = 0;
var buffer = new ByteBuffer(length);
var writer = fieldWidth === 4 ? buffer.writeUint32 : buffer.writeUint16;
// Format specifier
buffer.writeUint16(format);
if (fieldWidth === 4) {
// In case of formats 8.…, 10.…, 12.… and 13.…, this is the decimal part of the format number
// But since have not been any point releases, this can be zero in that case as well
buffer.writeUint16(0);
}
// Length
writer.call(buffer, length);
// Language code (0, only used for legacy quickdraw tables)
writer.call(buffer, LANGUAGE);
return buffer;
}
function createFormat0Table(font) {
var FORMAT = 0;
var i;
var length = 0xff + 1; //Format 0 maps only single-byte code points
var buffer = bufferForTable(FORMAT, length);
for (i = 0; i < length; i++) {
buffer.writeUint8(getIDByUnicode(font, i)); // existing char in table 0..255
}
return buffer;
}
function createFormat4Table(font) {
var FORMAT = 4;
var i;
var segments = getSegments(font, 0xFFFF);
var glyphIndexArrays = [];
_.forEach(segments, function (segment) {
var glyphIndexArray = [];
for (var unicode = segment.start; unicode <= segment.end; unicode++) {
glyphIndexArray.push(getIDByUnicode(font, unicode));
}
glyphIndexArrays.push(glyphIndexArray);
});
var segCount = segments.length + 1; // + 1 for the 0xFFFF section
var glyphIndexArrayLength = _.reduce(_.map(glyphIndexArrays, 'length'), function (result, count) { return result + count; }, 0);
var length = (0
+ 2 // segCountX2
+ 2 // searchRange
+ 2 // entrySelector
+ 2 // rangeShift
+ 2 * segCount // endCodes
+ 2 // Padding
+ 2 * segCount //startCodes
+ 2 * segCount //idDeltas
+ 2 * segCount //idRangeOffsets
+ 2 * glyphIndexArrayLength
);
var buffer = bufferForTable(FORMAT, length);
buffer.writeUint16(segCount * 2); // segCountX2
var maxExponent = Math.floor(Math.log(segCount) / Math.LN2);
var searchRange = 2 * Math.pow(2, maxExponent);
buffer.writeUint16(searchRange); // searchRange
buffer.writeUint16(maxExponent); // entrySelector
buffer.writeUint16(2 * segCount - searchRange); // rangeShift
// Array of end counts
_.forEach(segments, function (segment) {
buffer.writeUint16(segment.end);
});
buffer.writeUint16(0xFFFF); // endCountArray should be finished with 0xFFFF
buffer.writeUint16(0); // reservedPad
// Array of start counts
_.forEach(segments, function (segment) {
buffer.writeUint16(segment.start); //startCountArray
});
buffer.writeUint16(0xFFFF); // startCountArray should be finished with 0xFFFF
// Array of deltas. Leave it zero to not complicate things when using the glyph index array
for (i = 0; i < segments.length; i++) {
buffer.writeUint16(0); // delta is always zero because we use the glyph array
}
buffer.writeUint16(1); // idDeltaArray should be finished with 1
// Array of range offsets
var offset = 0;
for (i = 0; i < segments.length; i++) {
buffer.writeUint16(2 * ((segments.length - i + 1) + offset));
offset += glyphIndexArrays[i].length;
}
buffer.writeUint16(0); // rangeOffsetArray should be finished with 0
_.forEach(glyphIndexArrays, function (glyphIndexArray) {
_.forEach(glyphIndexArray, function (glyphId) {
buffer.writeUint16(glyphId);
});
});
return buffer;
}
function createFormat12Table(font) {
var FORMAT = 12;
var codePoints = getCodePoints(font.codePoints);
var length = (0
+ 4 // nGroups
+ 4 * codePoints.length // startCharCode
+ 4 * codePoints.length // endCharCode
+ 4 * codePoints.length // startGlyphCode
);
var buffer = bufferForTable(FORMAT, length);
buffer.writeUint32(codePoints.length); // nGroups
_.forEach(codePoints, function (codePoint) {
buffer.writeUint32(codePoint.unicode); // startCharCode
buffer.writeUint32(codePoint.unicode); // endCharCode
buffer.writeUint32(codePoint.glyph.id); // startGlyphCode
});
return buffer;
}
function createCMapTable(font) {
var TABLE_HEAD = (0
+ 2 // platform
+ 2 // encoding
+ 4 // offset
);
var singleByteTable = createFormat0Table(font);
var twoByteTable = createFormat4Table(font);
var fourByteTable = createFormat12Table(font);
// Subtable headers must be sorted by platformID, encodingID
var tableHeaders = [
// subtable 4, unicode
{
platformID: 0,
encodingID: 3,
table: twoByteTable
},
// subtable 12, unicode
{
platformID: 0,
encodingID: 4,
table: fourByteTable
},
// subtable 0, mac standard
{
platformID: 1,
encodingID: 0,
table: singleByteTable
},
// subtable 4, windows standard, identical to the unicode table
{
platformID: 3,
encodingID: 1,
table: twoByteTable
},
// subtable 12, windows ucs4
{
platformID: 3,
encodingID: 10,
table: fourByteTable
}
];
var tables = [
twoByteTable,
singleByteTable,
fourByteTable
];
var tableOffset = (0
+ 2 // version
+ 2 // number of subtable headers
+ tableHeaders.length * TABLE_HEAD
);
// Calculate offsets for each table
_.forEach(tables, function (table) {
table._tableOffset = tableOffset;
tableOffset += table.length;
});
var length = tableOffset;
var buffer = new ByteBuffer(length);
// Write table header.
buffer.writeUint16(0); // version
buffer.writeUint16(tableHeaders.length); // count
// Write subtable headers
_.forEach(tableHeaders, function (header) {
buffer.writeUint16(header.platformID); // platform
buffer.writeUint16(header.encodingID); // encoding
buffer.writeUint32(header.table._tableOffset); // offset
});
// Write subtables
_.forEach(tables, function (table) {
buffer.writeBytes(table.buffer);
});
return buffer;
}
module.exports = createCMapTable;

View File

@ -0,0 +1,195 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/glyf.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
function getFlags(glyph) {
var result = [];
_.forEach(glyph.ttfContours, function (contour) {
_.forEach(contour, function (point) {
var flag = point.onCurve ? 1 : 0;
if (point.x === 0) {
flag += 16;
} else {
if (-0xFF <= point.x && point.x <= 0xFF) {
flag += 2; // the corresponding x-coordinate is 1 byte long
}
if (point.x > 0 && point.x <= 0xFF) {
flag += 16; // If x-Short Vector is set, this bit describes the sign of the value, with 1 equalling positive and 0 negative
}
}
if (point.y === 0) {
flag += 32;
} else {
if (-0xFF <= point.y && point.y <= 0xFF) {
flag += 4; // the corresponding y-coordinate is 1 byte long
}
if (point.y > 0 && point.y <= 0xFF) {
flag += 32; // If y-Short Vector is set, this bit describes the sign of the value, with 1 equalling positive and 0 negative.
}
}
result.push(flag);
});
});
return result;
}
//repeating flags can be packed
function compactFlags(flags) {
var result = [];
var prevFlag = -1;
var firstRepeat = false;
_.forEach(flags, function (flag) {
if (prevFlag === flag) {
if (firstRepeat) {
result[result.length - 1] += 8; //current flag repeats previous one, need to set 3rd bit of previous flag and set 1 to the current one
result.push(1);
firstRepeat = false;
} else {
result[result.length - 1]++; //when flag is repeating second or more times, we need to increase the last flag value
}
} else {
firstRepeat = true;
prevFlag = flag;
result.push(flag);
}
});
return result;
}
function getCoords(glyph, coordName) {
var result = [];
_.forEach(glyph.ttfContours, function (contour) {
result.push.apply(result, _.map(contour, coordName));
});
return result;
}
function compactCoords(coords) {
return _.filter(coords, function (coord) {
return coord !== 0;
});
}
//calculates length of glyph data in GLYF table
function glyphDataSize(glyph) {
// Ignore glyphs without outlines. These will get a length of zero in the “loca” table
if (!glyph.contours.length) {
return 0;
}
var result = 12; //glyph fixed properties
result += glyph.contours.length * 2; //add contours
_.forEach(glyph.ttf_x, function (x) {
//add 1 or 2 bytes for each coordinate depending of its size
result += ((-0xFF <= x && x <= 0xFF)) ? 1 : 2;
});
_.forEach(glyph.ttf_y, function (y) {
//add 1 or 2 bytes for each coordinate depending of its size
result += ((-0xFF <= y && y <= 0xFF)) ? 1 : 2;
});
// Add flags length to glyph size.
result += glyph.ttf_flags.length;
if (result % 4 !== 0) { // glyph size must be divisible by 4.
result += 4 - result % 4;
}
return result;
}
function tableSize(font) {
var result = 0;
_.forEach(font.glyphs, function (glyph) {
glyph.ttf_size = glyphDataSize(glyph);
result += glyph.ttf_size;
});
font.ttf_glyph_size = result; //sum of all glyph lengths
return result;
}
function createGlyfTable(font) {
_.forEach(font.glyphs, function (glyph) {
glyph.ttf_flags = getFlags(glyph);
glyph.ttf_flags = compactFlags(glyph.ttf_flags);
glyph.ttf_x = getCoords(glyph, 'x');
glyph.ttf_x = compactCoords(glyph.ttf_x);
glyph.ttf_y = getCoords(glyph, 'y');
glyph.ttf_y = compactCoords(glyph.ttf_y);
});
var buf = new ByteBuffer(tableSize(font));
_.forEach(font.glyphs, function (glyph) {
// Ignore glyphs without outlines. These will get a length of zero in the “loca” table
if (!glyph.contours.length) {
return;
}
var offset = buf.tell();
buf.writeInt16(glyph.contours.length); // numberOfContours
buf.writeInt16(glyph.xMin); // xMin
buf.writeInt16(glyph.yMin); // yMin
buf.writeInt16(glyph.xMax); // xMax
buf.writeInt16(glyph.yMax); // yMax
// Array of end points
var endPtsOfContours = -1;
var ttfContours = glyph.ttfContours;
_.forEach(ttfContours, function (contour) {
endPtsOfContours += contour.length;
buf.writeInt16(endPtsOfContours);
});
buf.writeInt16(0); // instructionLength, is not used here
// Array of flags
_.forEach(glyph.ttf_flags, function (flag) {
buf.writeInt8(flag);
});
// Array of X relative coordinates
_.forEach(glyph.ttf_x, function (x) {
if (-0xFF <= x && x <= 0xFF) {
buf.writeUint8(Math.abs(x));
} else {
buf.writeInt16(x);
}
});
// Array of Y relative coordinates
_.forEach(glyph.ttf_y, function (y) {
if (-0xFF <= y && y <= 0xFF) {
buf.writeUint8(Math.abs(y));
} else {
buf.writeInt16(y);
}
});
var tail = (buf.tell() - offset) % 4;
if (tail !== 0) { // glyph size must be divisible by 4.
for (; tail < 4; tail++) {
buf.writeUint8(0);
}
}
});
return buf;
}
module.exports = createGlyfTable;

View File

@ -0,0 +1,407 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/GSUB.htm
var _ = require('lodash');
var identifier = require('../utils.js').identifier;
var ByteBuffer = require('microbuffer');
function createScript() {
var scriptRecord = (0
+ 2 // Script DefaultLangSys Offset
+ 2 // Script[0] LangSysCount (0)
);
var langSys = (0
+ 2 // Script DefaultLangSys LookupOrder
+ 2 // Script DefaultLangSys ReqFeatureIndex
+ 2 // Script DefaultLangSys FeatureCount (0?)
+ 2 // Script Optional Feature Index[0]
);
var length = (0
+ scriptRecord
+ langSys
);
var buffer = new ByteBuffer(length);
// Script Record
// Offset to the start of langSys from the start of scriptRecord
buffer.writeUint16(scriptRecord); // DefaultLangSys
// Number of LangSys entries other than the default (none)
buffer.writeUint16(0);
// LangSys record (DefaultLangSys)
// LookupOrder
buffer.writeUint16(0);
// ReqFeatureIndex -> only one required feature: all ligatures
buffer.writeUint16(0);
// Number of FeatureIndex values for this language system (excludes the required feature)
buffer.writeUint16(1);
// FeatureIndex for the first optional feature
// Note: Adding the same feature to both the optional
// and the required features is a clear violation of the spec
// but it fixes IE not displaying the ligatures.
// See http://partners.adobe.com/public/developer/opentype/index_table_formats.html, Section “Language System Table”
// “FeatureCount: Number of FeatureIndex values for this language system-*excludes the required feature*” (emphasis added)
buffer.writeUint16(0);
return buffer;
}
function createScriptList() {
var scriptSize = (0
+ 4 // Tag
+ 2 // Offset
);
// tags should be arranged alphabetically
var scripts = [
[ 'DFLT', createScript() ],
[ 'latn', createScript() ]
];
var header = (0
+ 2 // Script count
+ scripts.length * scriptSize
);
var tableLengths = _.reduce(_.map(scripts, function (script) { return script[1].length; }), function (result, count) { return result + count; }, 0);
var length = (0
+ header
+ tableLengths
);
var buffer = new ByteBuffer(length);
// Script count
buffer.writeUint16(scripts.length);
// Write all ScriptRecords
var offset = header;
_.forEach(scripts, function (script) {
var name = script[0], table = script[1];
// Script identifier (DFLT/latn)
buffer.writeUint32(identifier(name));
// Offset to the ScriptRecord from start of the script list
buffer.writeUint16(offset);
// Increment offset by script table length
offset += table.length;
});
// Write all ScriptTables
_.forEach(scripts, function (script) {
var table = script[1];
buffer.writeBytes(table.buffer);
});
return buffer;
}
// Write one feature containing all ligatures
function createFeatureList() {
var header = (0
+ 2 // FeatureCount
+ 4 // FeatureTag[0]
+ 2 // Feature Offset[0]
);
var length = (0
+ header
+ 2 // FeatureParams[0]
+ 2 // LookupCount[0]
+ 2 // Lookup[0] LookupListIndex[0]
);
var buffer = new ByteBuffer(length);
// FeatureCount
buffer.writeUint16(1);
// FeatureTag[0]
buffer.writeUint32(identifier('liga'));
// Feature Offset[0]
buffer.writeUint16(header);
// FeatureParams[0]
buffer.writeUint16(0);
// LookupCount[0]
buffer.writeUint16(1);
// Index into lookup table. Since we only have ligatures, the index is always 0
buffer.writeUint16(0);
return buffer;
}
function createLigatureCoverage(font, ligatureGroups) {
var glyphCount = ligatureGroups.length;
var length = (0
+ 2 // CoverageFormat
+ 2 // GlyphCount
+ 2 * glyphCount // GlyphID[i]
);
var buffer = new ByteBuffer(length);
// CoverageFormat
buffer.writeUint16(1);
// Length
buffer.writeUint16(glyphCount);
_.forEach(ligatureGroups, function (group) {
buffer.writeUint16(group.startGlyph.id);
});
return buffer;
}
function createLigatureTable(font, ligature) {
var allCodePoints = font.codePoints;
var unicode = ligature.unicode;
var length = (0
+ 2 // LigGlyph
+ 2 // CompCount
+ 2 * (unicode.length - 1)
);
var buffer = new ByteBuffer(length);
// LigGlyph
var glyph = ligature.glyph;
buffer.writeUint16(glyph.id);
// CompCount
buffer.writeUint16(unicode.length);
// Compound glyphs (excluding first as its already in the coverage table)
for (var i = 1; i < unicode.length; i++) {
glyph = allCodePoints[unicode[i]];
buffer.writeUint16(glyph.id);
}
return buffer;
}
function createLigatureSet(font, codePoint, ligatures) {
var ligatureTables = [];
_.forEach(ligatures, function (ligature) {
ligatureTables.push(createLigatureTable(font, ligature));
});
var tableLengths = _.reduce(_.map(ligatureTables, 'length'), function (result, count) { return result + count; }, 0);
var offset = (0
+ 2 // LigatureCount
+ 2 * ligatures.length
);
var length = (0
+ offset
+ tableLengths
);
var buffer = new ByteBuffer(length);
// LigatureCount
buffer.writeUint16(ligatures.length);
// Ligature offsets
_.forEach(ligatureTables, function (table) {
// The offset to the current set, from SubstFormat
buffer.writeUint16(offset);
offset += table.length;
});
// Ligatures
_.forEach(ligatureTables, function (table) {
buffer.writeBytes(table.buffer);
});
return buffer;
}
function createLigatureList(font, ligatureGroups) {
var sets = [];
_.forEach(ligatureGroups, function (group) {
var set = createLigatureSet(font, group.codePoint, group.ligatures);
sets.push(set);
});
var setLengths = _.reduce(_.map(sets, 'length'), function (result, count) { return result + count; }, 0);
var coverage = createLigatureCoverage(font, ligatureGroups);
var tableOffset = (0
+ 2 // Lookup type
+ 2 // Lokup flag
+ 2 // SubTableCount
+ 2 // SubTable[0] Offset
);
var setOffset = (0
+ 2 // SubstFormat
+ 2 // Coverage offset
+ 2 // LigSetCount
+ 2 * sets.length // LigSet Offsets
);
var coverageOffset = setOffset + setLengths;
var length = (0
+ tableOffset
+ coverageOffset
+ coverage.length
);
var buffer = new ByteBuffer(length);
// Lookup type 4 ligatures
buffer.writeUint16(4);
// Lookup flag empty
buffer.writeUint16(0);
// Subtable count
buffer.writeUint16(1);
// Subtable[0] offset
buffer.writeUint16(tableOffset);
// SubstFormat
buffer.writeUint16(1);
// Coverage
buffer.writeUint16(coverageOffset);
// LigSetCount
buffer.writeUint16(sets.length);
_.forEach(sets, function (set) {
// The offset to the current set, from SubstFormat
buffer.writeUint16(setOffset);
setOffset += set.length;
});
_.forEach(sets, function (set) {
buffer.writeBytes(set.buffer);
});
buffer.writeBytes(coverage.buffer);
return buffer;
}
// Add a lookup for each ligature
function createLookupList(font) {
var ligatures = font.ligatures;
var groupedLigatures = {};
// Group ligatures by first code point
_.forEach(ligatures, function (ligature) {
var first = ligature.unicode[0];
if (!_.has(groupedLigatures, first)) {
groupedLigatures[first] = [];
}
groupedLigatures[first].push(ligature);
});
var ligatureGroups = [];
_.forEach(groupedLigatures, function (ligatures, codePoint) {
codePoint = parseInt(codePoint, 10);
// Order ligatures by length, descending
// “Ligatures with more components must be stored ahead of those with fewer components in order to be found”
// From: http://partners.adobe.com/public/developer/opentype/index_tag7.html#liga
ligatures.sort(function (ligA, ligB) {
return ligB.unicode.length - ligA.unicode.length;
});
ligatureGroups.push({
codePoint: codePoint,
ligatures: ligatures,
startGlyph: font.codePoints[codePoint]
});
});
ligatureGroups.sort(function (a, b) {
return a.startGlyph.id - b.startGlyph.id;
});
var offset = (0
+ 2 // Lookup count
+ 2 // Lookup[0] offset
);
var set = createLigatureList(font, ligatureGroups);
var length = (0
+ offset
+ set.length
);
var buffer = new ByteBuffer(length);
// Lookup count
buffer.writeUint16(1);
// Lookup[0] offset
buffer.writeUint16(offset);
// Lookup[0]
buffer.writeBytes(set.buffer);
return buffer;
}
function createGSUB(font) {
var scriptList = createScriptList();
var featureList = createFeatureList();
var lookupList = createLookupList(font);
var lists = [ scriptList, featureList, lookupList ];
var offset = (0
+ 4 // Version
+ 2 * lists.length // List offsets
);
// Calculate offsets
_.forEach(lists, function (list) {
list._listOffset = offset;
offset += list.length;
});
var length = offset;
var buffer = new ByteBuffer(length);
// Version
buffer.writeUint32(0x00010000);
// Offsets
_.forEach(lists, function (list) {
buffer.writeUint16(list._listOffset);
});
// List contents
_.forEach(lists, function (list) {
buffer.writeBytes(list.buffer);
});
return buffer;
}
module.exports = createGSUB;

View File

@ -0,0 +1,42 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/head.htm
var ByteBuffer = require('microbuffer');
function dateToUInt64(date) {
var startDate = new Date('1904-01-01T00:00:00.000Z');
return Math.floor((date - startDate) / 1000);
}
function createHeadTable(font) {
var buf = new ByteBuffer(54); // fixed table length
buf.writeInt32(0x10000); // version
buf.writeInt32(font.revision * 0x10000); // fontRevision
buf.writeUint32(0); // checkSumAdjustment
buf.writeUint32(0x5F0F3CF5); // magicNumber
// FLag meanings:
// Bit 0: Baseline for font at y=0;
// Bit 1: Left sidebearing point at x=0;
// Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear;
buf.writeUint16(0x000B); // flags
buf.writeUint16(font.unitsPerEm); // unitsPerEm
buf.writeUint64(dateToUInt64(font.createdDate)); // created
buf.writeUint64(dateToUInt64(font.modifiedDate)); // modified
buf.writeInt16(font.xMin); // xMin
buf.writeInt16(font.yMin); // yMin
buf.writeInt16(font.xMax); // xMax
buf.writeInt16(font.yMax); // yMax
buf.writeUint16(font.macStyle); //macStyle
buf.writeUint16(font.lowestRecPPEM); // lowestRecPPEM
buf.writeInt16(2); // fontDirectionHint
buf.writeInt16(font.ttf_glyph_size < 0x20000 ? 0 : 1); // indexToLocFormat, 0 for short offsets, 1 for long offsets
buf.writeInt16(0); // glyphDataFormat
return buf;
}
module.exports = createHeadTable;

View File

@ -0,0 +1,31 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/hhea.htm
var ByteBuffer = require('microbuffer');
function createHHeadTable(font) {
var buf = new ByteBuffer(36); // fixed table length
buf.writeInt32(0x10000); // version
buf.writeInt16(font.ascent); // ascent
buf.writeInt16(font.descent); // descend
// Non zero lineGap causes offset in IE, https://github.com/fontello/svg2ttf/issues/37
buf.writeInt16(0); // lineGap
buf.writeUint16(font.maxWidth); // advanceWidthMax
buf.writeInt16(font.minLsb); // minLeftSideBearing
buf.writeInt16(font.minRsb); // minRightSideBearing
buf.writeInt16(font.maxExtent); // xMaxExtent
buf.writeInt16(1); // caretSlopeRise
buf.writeInt16(0); // caretSlopeRun
buf.writeUint32(0); // reserved1
buf.writeUint32(0); // reserved2
buf.writeUint16(0); // reserved3
buf.writeInt16(0); // metricDataFormat
buf.writeUint16(font.glyphs.length); // numberOfHMetrics
return buf;
}
module.exports = createHHeadTable;

View File

@ -0,0 +1,19 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/hmtx.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
function createHtmxTable(font) {
var buf = new ByteBuffer(font.glyphs.length * 4);
_.forEach(font.glyphs, function (glyph) {
buf.writeUint16(glyph.width); //advanceWidth
buf.writeInt16(glyph.xMin); //lsb
});
return buf;
}
module.exports = createHtmxTable;

View File

@ -0,0 +1,43 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/loca.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
function tableSize(font, isShortFormat) {
var result = (font.glyphs.length + 1) * (isShortFormat ? 2 : 4); // by glyph count + tail
return result;
}
function createLocaTable(font) {
var isShortFormat = font.ttf_glyph_size < 0x20000;
var buf = new ByteBuffer(tableSize(font, isShortFormat));
var location = 0;
// Array of offsets in GLYF table for each glyph
_.forEach(font.glyphs, function (glyph) {
if (isShortFormat) {
buf.writeUint16(location);
location += glyph.ttf_size / 2; // actual location must be divided to 2 in short format
} else {
buf.writeUint32(location);
location += glyph.ttf_size; //actual location is stored as is in long format
}
});
// The last glyph location is stored to get last glyph length
if (isShortFormat) {
buf.writeUint16(location);
} else {
buf.writeUint32(location);
}
return buf;
}
module.exports = createLocaTable;

View File

@ -0,0 +1,46 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/maxp.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
// Find max points in glyph TTF contours.
function getMaxPoints(font) {
return _.max(_.map(font.glyphs, function (glyph) {
return _.reduce(glyph.ttfContours, function (sum, ctr) { return sum + ctr.length; }, 0);
}));
}
function getMaxContours(font) {
return _.max(_.map(font.glyphs, function (glyph) {
return glyph.ttfContours.length;
}));
}
function createMaxpTable(font) {
var buf = new ByteBuffer(32);
buf.writeInt32(0x10000); // version
buf.writeUint16(font.glyphs.length); // numGlyphs
buf.writeUint16(getMaxPoints(font)); // maxPoints
buf.writeUint16(getMaxContours(font)); // maxContours
buf.writeUint16(0); // maxCompositePoints
buf.writeUint16(0); // maxCompositeContours
buf.writeUint16(2); // maxZones
buf.writeUint16(0); // maxTwilightPoints
// It is unclear how to calculate maxStorage, maxFunctionDefs and maxInstructionDefs.
// These are magic constants now, with values exceeding values from FontForge
buf.writeUint16(10); // maxStorage
buf.writeUint16(10); // maxFunctionDefs
buf.writeUint16(0); // maxInstructionDefs
buf.writeUint16(255); // maxStackElements
buf.writeUint16(0); // maxSizeOfInstructions
buf.writeUint16(0); // maxComponentElements
buf.writeUint16(0); // maxComponentDepth
return buf;
}
module.exports = createMaxpTable;

View File

@ -0,0 +1,106 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/name.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
var Str = require('../../str');
var TTF_NAMES = {
COPYRIGHT: 0,
FONT_FAMILY: 1,
ID: 3,
DESCRIPTION: 10,
URL_VENDOR: 11
};
function tableSize(names) {
var result = 6; // table header
_.forEach(names, function (name) {
result += 12 + name.data.length; //name header and data
});
return result;
}
function getStrings(name, id) {
var result = [];
var str = new Str(name);
result.push({ data: str.toUTF8Bytes(), id: id, platformID : 1, encodingID : 0, languageID : 0 }); //mac standard
result.push({ data: str.toUCS2Bytes(), id: id, platformID : 3, encodingID : 1, languageID : 0x409 }); //windows standard
return result;
}
// Collect font names
function getNames(font) {
var result = [];
if (font.copyright) {
result.push.apply(result, getStrings(font.copyright, TTF_NAMES.COPYRIGHT));
}
if (font.familyName) {
result.push.apply(result, getStrings(font.familyName, TTF_NAMES.FONT_FAMILY));
}
if (font.id) {
result.push.apply(result, getStrings(font.id, TTF_NAMES.ID));
}
result.push.apply(result, getStrings('Generated by svg2ttf from Fontello project.', TTF_NAMES.DESCRIPTION));
result.push.apply(result, getStrings('http://fontello.com', TTF_NAMES.URL_VENDOR));
_.forEach(font.sfntNames, function (sfntName) {
result.push.apply(result, getStrings(sfntName.value, sfntName.id));
});
result.sort(function (a, b) {
var orderFields = [ 'platformID', 'encodingID', 'languageID', 'id' ];
var i;
for (i = 0; i < orderFields.length; i++) {
if (a[orderFields[i]] !== b[orderFields[i]]) {
return a[orderFields[i]] < b[orderFields[i]] ? -1 : 1;
}
}
return 0;
});
return result;
}
function createNameTable(font) {
var names = getNames(font);
var buf = new ByteBuffer(tableSize(names));
buf.writeUint16(0); // formatSelector
buf.writeUint16(names.length); // nameRecordsCount
var offsetPosition = buf.tell();
buf.writeUint16(0); // offset, will be filled later
var nameOffset = 0;
_.forEach(names, function (name) {
buf.writeUint16(name.platformID); // platformID
buf.writeUint16(name.encodingID); // platEncID
buf.writeUint16(name.languageID); // languageID, English (USA)
buf.writeUint16(name.id); // nameID
buf.writeUint16(name.data.length); // reclength
buf.writeUint16(nameOffset); // offset
nameOffset += name.data.length;
});
var actualStringDataOffset = buf.tell();
//Array of bytes with actual string data
_.forEach(names, function (name) {
buf.writeBytes(name.data);
});
//write actual string data offset
buf.seek(offsetPosition);
buf.writeUint16(actualStringDataOffset); // offset
return buf;
}
module.exports = createNameTable;

View File

@ -0,0 +1,74 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/os2.htm
var _ = require('lodash');
var identifier = require('../utils.js').identifier;
var ByteBuffer = require('microbuffer');
//get first glyph unicode
function getFirstCharIndex(font) {
return Math.max(0, Math.min(0xffff, Math.abs(_.minBy(Object.keys(font.codePoints), function (point) {
return parseInt(point, 10);
}))));
}
//get last glyph unicode
function getLastCharIndex(font) {
return Math.max(0, Math.min(0xffff, Math.abs(_.maxBy(Object.keys(font.codePoints), function (point) {
return parseInt(point, 10);
}))));
}
function createOS2Table(font) {
var buf = new ByteBuffer(86);
buf.writeUint16(1); //version
buf.writeInt16(font.avgWidth); // xAvgCharWidth
buf.writeUint16(font.weightClass); // usWeightClass
buf.writeUint16(font.widthClass); // usWidthClass
buf.writeInt16(font.fsType); // fsType
buf.writeInt16(font.ySubscriptXSize); // ySubscriptXSize
buf.writeInt16(font.ySubscriptYSize); //ySubscriptYSize
buf.writeInt16(font.ySubscriptXOffset); // ySubscriptXOffset
buf.writeInt16(font.ySubscriptYOffset); // ySubscriptYOffset
buf.writeInt16(font.ySuperscriptXSize); // ySuperscriptXSize
buf.writeInt16(font.ySuperscriptYSize); // ySuperscriptYSize
buf.writeInt16(font.ySuperscriptXOffset); // ySuperscriptXOffset
buf.writeInt16(font.ySuperscriptYOffset); // ySuperscriptYOffset
buf.writeInt16(font.yStrikeoutSize); // yStrikeoutSize
buf.writeInt16(font.yStrikeoutPosition); // yStrikeoutPosition
buf.writeInt16(font.familyClass); // sFamilyClass
buf.writeUint8(font.panose.familyType); // panose.bFamilyType
buf.writeUint8(font.panose.serifStyle); // panose.bSerifStyle
buf.writeUint8(font.panose.weight); // panose.bWeight
buf.writeUint8(font.panose.proportion); // panose.bProportion
buf.writeUint8(font.panose.contrast); // panose.bContrast
buf.writeUint8(font.panose.strokeVariation); // panose.bStrokeVariation
buf.writeUint8(font.panose.armStyle); // panose.bArmStyle
buf.writeUint8(font.panose.letterform); // panose.bLetterform
buf.writeUint8(font.panose.midline); // panose.bMidline
buf.writeUint8(font.panose.xHeight); // panose.bXHeight
// TODO: This field is used to specify the Unicode blocks or ranges based on the 'cmap' table.
buf.writeUint32(0); // ulUnicodeRange1
buf.writeUint32(0); // ulUnicodeRange2
buf.writeUint32(0); // ulUnicodeRange3
buf.writeUint32(0); // ulUnicodeRange4
buf.writeUint32(identifier('PfEd')); // achVendID, equal to PfEd
buf.writeUint16(font.fsSelection); // fsSelection
buf.writeUint16(getFirstCharIndex(font)); // usFirstCharIndex
buf.writeUint16(getLastCharIndex(font)); // usLastCharIndex
buf.writeInt16(font.ascent); // sTypoAscender
buf.writeInt16(font.descent); // sTypoDescender
buf.writeInt16(font.lineGap); // lineGap
// Enlarge win acscent/descent to avoid clipping
buf.writeInt16(Math.max(font.yMax, font.ascent)); // usWinAscent
buf.writeInt16(-Math.min(font.yMin, font.descent)); // usWinDescent
buf.writeInt32(1); // ulCodePageRange1, Latin 1
buf.writeInt32(0); // ulCodePageRange2
return buf;
}
module.exports = createOS2Table;

View File

@ -0,0 +1,73 @@
'use strict';
// See documentation here: http://www.microsoft.com/typography/otspec/post.htm
var _ = require('lodash');
var ByteBuffer = require('microbuffer');
function tableSize(font, names) {
var result = 36; // table header
result += font.glyphs.length * 2; // name declarations
_.forEach(names, function (name) {
result += name.length;
});
return result;
}
function pascalString(str) {
var bytes = [];
var len = str ? (str.length < 256 ? str.length : 255) : 0; //length in Pascal string is limited with 255
bytes.push(len);
for (var i = 0; i < len; i++) {
var char = str.charCodeAt(i);
bytes.push(char < 128 ? char : 95); //non-ASCII characters are substituted with '_'
}
return bytes;
}
function createPostTable(font) {
var names = [];
_.forEach(font.glyphs, function (glyph) {
if (glyph.unicode !== 0) {
names.push(pascalString(glyph.name));
}
});
var buf = new ByteBuffer(tableSize(font, names));
buf.writeInt32(0x20000); // formatType, version 2.0
buf.writeInt32(font.italicAngle); // italicAngle
buf.writeInt16(font.underlinePosition); // underlinePosition
buf.writeInt16(font.underlineThickness); // underlineThickness
buf.writeUint32(font.isFixedPitch); // isFixedPitch
buf.writeUint32(0); // minMemType42
buf.writeUint32(0); // maxMemType42
buf.writeUint32(0); // minMemType1
buf.writeUint32(0); // maxMemType1
buf.writeUint16(font.glyphs.length); // numberOfGlyphs
// Array of glyph name indexes
var index = 258; // first index of custom glyph name, it is calculated as glyph name index + 258
_.forEach(font.glyphs, function (glyph) {
if (glyph.unicode === 0) {
buf.writeUint16(0);// missed element should have .notDef name in the Macintosh standard order.
} else {
buf.writeUint16(index++);
}
});
// Array of glyph name indexes
_.forEach(names, function (name) {
buf.writeBytes(name);
});
return buf;
}
module.exports = createPostTable;

View File

@ -0,0 +1,129 @@
'use strict';
var _ = require('lodash');
var math = require('../math');
// Remove points, that looks like straight line
function simplify(contours, accuracy) {
return _.map(contours, function (contour) {
var i, curr, prev, next;
var p, pPrev, pNext;
// run from the end, to simplify array elements removal
for (i = contour.length - 2; i > 1; i--) {
prev = contour[i - 1];
next = contour[i + 1];
curr = contour[i];
// skip point (both oncurve & offcurve),
// if [prev,next] is straight line
if (prev.onCurve && next.onCurve) {
p = new math.Point(curr.x, curr.y);
pPrev = new math.Point(prev.x, prev.y);
pNext = new math.Point(next.x, next.y);
if (math.isInLine(pPrev, p, pNext, accuracy)) {
contour.splice(i, 1);
}
}
}
return contour;
});
}
// Remove interpolateable oncurve points
// Those should be in the middle of nebor offcurve points
function interpolate(contours, accuracy) {
return _.map(contours, function (contour) {
var resContour = [];
_.forEach(contour, function (point, idx) {
// Never skip first and last points
if (idx === 0 || idx === (contour.length - 1)) {
resContour.push(point);
return;
}
var prev = contour[idx - 1];
var next = contour[idx + 1];
var p, pPrev, pNext;
// skip interpolateable oncurve points (if exactly between previous and next offcurves)
if (!prev.onCurve && point.onCurve && !next.onCurve) {
p = new math.Point(point.x, point.y);
pPrev = new math.Point(prev.x, prev.y);
pNext = new math.Point(next.x, next.y);
if (pPrev.add(pNext).div(2).sub(p).dist() < accuracy) {
return;
}
}
// keep the rest
resContour.push(point);
});
return resContour;
});
}
function roundPoints(contours) {
return _.map(contours, function (contour) {
return _.map(contour, function (point) {
return { x: Math.round(point.x), y: Math.round(point.y), onCurve: point.onCurve };
});
});
}
// Remove closing point if it is the same as first point of contour.
// TTF doesn't need this point when drawing contours.
function removeClosingReturnPoints(contours) {
return _.map(contours, function (contour) {
var length = contour.length;
if (length > 1 &&
contour[0].x === contour[length - 1].x &&
contour[0].y === contour[length - 1].y) {
contour.splice(length - 1);
}
return contour;
});
}
function toRelative(contours) {
var prevPoint = { x: 0, y: 0 };
var resContours = [];
var resContour;
_.forEach(contours, function (contour) {
resContour = [];
resContours.push(resContour);
_.forEach(contour, function (point) {
resContour.push({
x: point.x - prevPoint.x,
y: point.y - prevPoint.y,
onCurve: point.onCurve
});
prevPoint = point;
});
});
return resContours;
}
function identifier(string, littleEndian) {
var result = 0;
for (var i = 0; i < string.length; i++) {
result = result << 8;
var index = littleEndian ? string.length - i - 1 : i;
result += string.charCodeAt(index);
}
return result;
}
module.exports.interpolate = interpolate;
module.exports.simplify = simplify;
module.exports.roundPoints = roundPoints;
module.exports.removeClosingReturnPoints = removeClosingReturnPoints;
module.exports.toRelative = toRelative;
module.exports.identifier = identifier;

50
static/js/ketcher2/node_modules/svg2ttf/lib/ucs2.js generated vendored Normal file
View File

@ -0,0 +1,50 @@
'use strict';
var _ = require('lodash');
// Taken from the punycode library
function ucs2encode(array) {
return _.map(array, function (value) {
var output = '';
if (value > 0xFFFF) {
value -= 0x10000;
output += String.fromCharCode(value >>> 10 & 0x3FF | 0xD800);
value = 0xDC00 | value & 0x3FF;
}
output += String.fromCharCode(value);
return output;
}).join('');
}
function ucs2decode(string) {
var output = [],
counter = 0,
length = string.length,
value,
extra;
while (counter < length) {
value = string.charCodeAt(counter++);
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
// high surrogate, and there is a next character
extra = string.charCodeAt(counter++);
if ((extra & 0xFC00) === 0xDC00) { // low surrogate
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
} else {
// unmatched surrogate; only append this code unit, in case the next
// code unit is the high surrogate of a surrogate pair
output.push(value);
counter--;
}
} else {
output.push(value);
}
}
return output;
}
module.exports = {
encode: ucs2encode,
decode: ucs2decode
};