pax_global_header00006660000000000000000000000064141100527230014505gustar00rootroot0000000000000052 comment=c6eb73bee3d90d3b880ad01ca09fbca5ee64126c svg2ttf-6.0.3/000077500000000000000000000000001411005272300131125ustar00rootroot00000000000000svg2ttf-6.0.3/.eslintrc.yml000066400000000000000000000104371411005272300155430ustar00rootroot00000000000000env: node: true es6: true rules: accessor-pairs: 2 array-bracket-spacing: [ 2, "always", { "singleValue": true, "objectsInArrays": true, "arraysInArrays": true } ] block-scoped-var: 2 block-spacing: 2 brace-style: [ 2, '1tbs', { "allowSingleLine": true } ] comma-dangle: 2 comma-spacing: 2 comma-style: 2 computed-property-spacing: [ 2, never ] # Postponed #consistent-return: 2 consistent-this: [ 2, self ] curly: [ 2, 'multi-line' ] # Postponed # dot-notation: [ 2, { allowKeywords: true } ] dot-location: [ 2, 'property' ] eol-last: 2 eqeqeq: 2 func-style: [ 2, declaration ] # Postponed #global-require: 2 guard-for-in: 2 handle-callback-err: 2 indent: [ 2, 2, { VariableDeclarator: { var: 2, let: 2, const: 3 }, SwitchCase: 1 } ] keyword-spacing: 2 linebreak-style: 2 max-depth: [ 1, 5 ] max-nested-callbacks: [ 1, 5 ] # string can exceed 80 chars, but should not overflow github website :) #max-len: [ 2, 120, 1000 ] new-cap: 2 new-parens: 2 # Postponed newline-after-var: 2 no-alert: 2 no-array-constructor: 2 #no-bitwise: 2 no-caller: 2 no-catch-shadow: 2 no-cond-assign: 2 no-console: 1 no-constant-condition: 2 no-control-regex: 2 no-debugger: 1 no-delete-var: 2 no-div-regex: 2 no-dupe-args: 2 no-dupe-keys: 2 no-duplicate-case: 2 no-else-return: 2 no-empty-character-class: 2 no-empty-pattern: 2 no-eq-null: 2 no-eval: 2 no-ex-assign: 2 no-extend-native: 2 no-extra-bind: 2 no-extra-boolean-cast: 2 no-extra-semi: 2 no-fallthrough: 2 no-floating-decimal: 2 no-func-assign: 2 # Postponed #no-implicit-coercion: [2, { "boolean": true, "number": true, "string": true } ] no-implied-eval: 2 no-inner-declarations: 2 no-invalid-regexp: 2 no-irregular-whitespace: 2 no-iterator: 2 no-label-var: 2 no-labels: 2 no-lone-blocks: 1 no-lonely-if: 2 no-loop-func: 2 no-mixed-requires: [ 1, { "grouping": true } ] no-mixed-spaces-and-tabs: 2 # Postponed #no-native-reassign: 2 no-negated-in-lhs: 2 # Postponed #no-nested-ternary: 2 no-new: 2 no-new-func: 2 no-new-object: 2 no-new-require: 2 no-new-wrappers: 2 no-obj-calls: 2 no-octal: 2 no-octal-escape: 2 no-path-concat: 2 no-proto: 2 no-redeclare: 2 # Postponed #no-regex-spaces: 2 no-return-assign: 2 no-self-compare: 2 no-sequences: 2 # Postponed #no-shadow: 2 no-shadow-restricted-names: 2 no-sparse-arrays: 2 # Postponed #no-sync: 2 no-trailing-spaces: 2 no-undef: 2 no-undef-init: 2 no-undefined: 2 no-unexpected-multiline: 2 no-unreachable: 2 no-unused-expressions: 2 no-unused-vars: 2 no-use-before-define: 2 no-void: 2 no-with: 2 object-curly-spacing: [ 2, always, { "objectsInObjects": true, "arraysInObjects": true } ] #operator-assignment: 1 # Postponed #operator-linebreak: [ 2, after ] semi: 2 semi-spacing: 2 space-before-function-paren: [ 2, { "anonymous": "always", "named": "never" } ] space-in-parens: [ 2, never ] space-infix-ops: 2 space-unary-ops: 2 # Postponed #spaced-comment: [ 1, always, { exceptions: [ '/', '=' ] } ] strict: [ 2, global ] quotes: [ 2, single, avoid-escape ] quote-props: [ 1, 'as-needed', { "keywords": true } ] radix: 2 use-isnan: 2 valid-typeof: 2 yoda: [ 2, never, { "exceptRange": true } ] svg2ttf-6.0.3/.github/000077500000000000000000000000001411005272300144525ustar00rootroot00000000000000svg2ttf-6.0.3/.github/FUNDING.yml000066400000000000000000000001001411005272300162560ustar00rootroot00000000000000open_collective: puzrin patreon: puzrin tidelift: "npm/svg2ttf" svg2ttf-6.0.3/.github/workflows/000077500000000000000000000000001411005272300165075ustar00rootroot00000000000000svg2ttf-6.0.3/.github/workflows/ci.yml000066400000000000000000000003571411005272300176320ustar00rootroot00000000000000name: CI on: push: pull_request: schedule: - cron: '0 0 * * 3' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 - run: npm install - run: npm test svg2ttf-6.0.3/.gitignore000066400000000000000000000000171411005272300151000ustar00rootroot00000000000000/node_modules/ svg2ttf-6.0.3/CHANGELOG.md000066400000000000000000000100161411005272300147210ustar00rootroot000000000000006.0.3 / 2021-08-21 ------------------ - xmldom => @xmldom/xmldom 6.0.2 / 2021-07-01 ------------------ - Glyphs that don't fit in int32 now throw an error instead of generating invalid font, #113. 6.0.1 / 2021-06-17 ------------------ - `OS/2` table version reduced (5 => 4), #110. 6.0.0 / 2021-05-27 ------------------ - Big thanks to @yisibl for provided notes & samples. Those caused all fixes below. - `OS/2` table version bumped from v1 to v5 (#106). - `usWinAscent` should include `lineGap`. - Fixed `cubic2quad` dependency for edge cases. 5.2.0 / 2021-04-07 ------------------ - Bump dependencies. - Migrate CI to github. 5.1.0 / 2020-10-29 ------------------ - Added support of `d` attr in nested `` tag (for UNSCII font source). 5.0.0 / 2020-05-20 ------------------ - Breaking: `USE_TYPO_METRICS` bit of `OS/2.fsSelection` is now set. Should fix Windows line spacing issues, #95. - Use font weight value when defined. 4.3.0 / 2019-05-24 ------------------ - Add underline thickness/position support, #80. 4.2.1 / 2019-05-23 ------------------ - Fix "new Bufer" deprecation warnings, #78. 4.2.0 / 2018-12-10 ------------------ - Added `description` and `url` options, #74 4.1.0 / 2017-06-24 ------------------ - Added font subfamily name support, #57. 4.0.3 / 2017-05-27 ------------------ - Script tags should be arranged alpabetically, #55. 4.0.2 / 2016-08-04 ------------------ - Added option to customize version string. 4.0.1 / 2016-06-03 ------------------ - Fix IE ligatures by explicitly adding the latin script to the script list, #47. 4.0.0 / 2016-03-08 ------------------ - Deps update (lodash -> 4.+). - Set HHEA lineGap to 0, #37 (second attempt :). - Cleanup, testing. 3.0.0 / 2016-02-12 ------------------ - Changed defaults to workaround IE bugs. - Set HHEA lineGap to 0, #37. - Set OS/2 fsType to 0, #45. 2.1.1 / 2015-12-22 ------------------ - Maintenance release: deps bump (svgpath with bugfixes). 2.1.0 / 2015-10-28 ------------------ - Fixed smoothness at the ends of interpolated cubic beziers. 2.0.2 / 2015-08-23 ------------------ - Fixed parse empty SVG glyphs without `d` attribute. 2.0.1 / 2015-07-17 ------------------ - Fix: TTF creation timestamp should not depende on timezone, thanks to @nfroidure. 2.0.0 / 2015-04-25 ------------------ - Added ligatures support, big thanks to @sabberworm. - Added arcs support in SVG paths. - Added `--ts` option to override default timestamp. - Fixed horisontal offset (`lsb`) when glyph exceed width. - Allow zero-width glyphs. - Better error message on attempt to convert SVG image instead of SVG font. 1.2.0 / 2014-10-05 ------------------ - Fixed usWinAscent/usWinDescent - should not go below ascent/descent. - Upgraded ByteBuffer internal lib. - Code cleanup. 1.1.2 / 2013-12-02 ------------------ - Fixed crash on SVG with empty (#8) - Fixed descent when input font has descent = 0 (@nfroidure) 1.1.1 / 2013-09-26 ------------------ - SVG parser moved to external package - Speed opts - Code refactoring/cleanup 1.1.0 / 2013-09-25 ------------------ - Rewritten svg parser to improve speed - API changed, now returns buffer as array/Uint8Array 1.0.7 / 2013-09-22 ------------------ - Improved speed x2.5 times 1.0.6 / 2013-09-12 ------------------ - Improved handling glyphs without codes or names - Fixed crash on glyphs with `v`/`h` commands - Logic cleanup 1.0.5 / 2013-08-27 ------------------ - Added CLI option `-c` to set copyright string - Fixed crash when some metrics missed in source SVG - Minor code cleanup 1.0.4 / 2013-08-09 ------------------ - Fixed importing into OSX Font Book 1.0.3 / 2013-08-02 ------------------ - Fixed maxp table max points count (solved chrome problems under windozze) 1.0.2 / 2013-07-24 ------------------ - Fixed htmx table size - Fixed loca table size for long format - Fixed glyph bounding boxes writing 1.0.1 / 2013-07-24 ------------------ - Added options support - Added `ttfinfo` utility - Multiple fixes 1.0.0 / 2013-07-19 ------------------ - First release svg2ttf-6.0.3/LICENSE000066400000000000000000000020741411005272300141220ustar00rootroot00000000000000(The MIT License) Copyright (C) 2013-2015 by Vitaly Puzrin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. svg2ttf-6.0.3/README.md000066400000000000000000000042261411005272300143750ustar00rootroot00000000000000svg2ttf ======= [![CI](https://github.com/fontello/svg2ttf/actions/workflows/ci.yml/badge.svg)](https://github.com/fontello/svg2ttf/actions/workflows/ci.yml) [![NPM version](https://img.shields.io/npm/v/svg2ttf.svg?style=flat)](https://www.npmjs.org/package/svg2ttf) > Converts SVG fonts to TTF format. It was initially written for [Fontello](http://fontello.com), but you can find it useful for your projects. __For developpers:__ Internal API is similar to FontForge's one. Since primary goal is generating iconic fonts, sources can lack some specific TTF/OTF features, like kerning and so on. Anyway, current code is a good base for development, because it will save you tons of hours to implement correct writing & optimizing TTF tables. Using from CLI ---------------- Install: ``` bash npm install -g svg2ttf ``` Usage example: ``` bash svg2ttf fontello.svg fontello.ttf ``` API --- ### svg2ttf(svgFontString, options) -> buf - `svgFontString` - SVG font content - `options` - `copyright` - copyright string (optional) - `description` - description string (optional) - `ts` - Unix timestamp (in seconds) to override creation time (optional) - `url` - manufacturer url (optional) - `version` - font version string, can be `Version x.y` or `x.y`. - `buf` - internal [byte buffer](https://github.com/fontello/microbuffer) object, similar to DataView. It's `buffer` property is `Uin8Array` or `Array` with ttf content. Example: ``` javascript var fs = require('fs'); var svg2ttf = require('svg2ttf'); var ttf = svg2ttf(fs.readFileSync('myfont.svg', 'utf8'), {}); fs.writeFileSync('myfont.ttf', new Buffer(ttf.buffer)); ``` ## svg2ttf for enterprise Available as part of the Tidelift Subscription. The maintainers of `svg2ttf` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-svg2ttf?utm_source=npm-svg2ttf&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) svg2ttf-6.0.3/index.js000066400000000000000000000155311411005272300145640ustar00rootroot00000000000000/* * Copyright: Vitaly Puzrin * Author: Sergey Batishchev * * Written for fontello.com project. */ 'use strict'; var _ = require('lodash'); var SvgPath = require('svgpath'); var ucs2 = require('./lib/ucs2'); var svg = require('./lib/svg'); var sfnt = require('./lib/sfnt'); var VERSION_RE = /^(Version )?(\d+[.]\d+)$/i; function svg2ttf(svgString, options) { var font = new sfnt.Font(); var svgFont = svg.load(svgString); options = options || {}; font.id = options.id || svgFont.id; font.familyName = options.familyname || svgFont.familyName || svgFont.id; font.copyright = options.copyright || svgFont.metadata; font.description = options.description || 'Generated by svg2ttf from Fontello project.'; font.url = options.url || 'http://fontello.com'; font.sfntNames.push({ id: 2, value: options.subfamilyname || svgFont.subfamilyName || 'Regular' }); // subfamily name font.sfntNames.push({ id: 4, value: options.fullname || svgFont.id }); // full name var versionString = options.version || 'Version 1.0'; if (typeof versionString !== 'string') { throw new Error('svg2ttf: version option should be a string'); } if (!VERSION_RE.test(versionString)) { throw new Error('svg2ttf: invalid option, version - "' + options.version + '"'); } versionString = 'Version ' + versionString.match(VERSION_RE)[2]; font.sfntNames.push({ id: 5, value: versionString }); // version ID for TTF name table font.sfntNames.push({ id: 6, value: (options.fullname || svgFont.id).replace(/[\s\(\)\[\]<>%\/]/g, '').substr(0, 62) }); // Postscript name for the font, required for OSX Font Book if (typeof options.ts !== 'undefined') { font.createdDate = font.modifiedDate = new Date(parseInt(options.ts, 10) * 1000); } // Try to fill font metrics or guess defaults // font.unitsPerEm = svgFont.unitsPerEm || 1000; font.horizOriginX = svgFont.horizOriginX || 0; font.horizOriginY = svgFont.horizOriginY || 0; font.vertOriginX = svgFont.vertOriginX || 0; font.vertOriginY = svgFont.vertOriginY || 0; font.width = svgFont.width || svgFont.unitsPerEm; font.height = svgFont.height || svgFont.unitsPerEm; font.descent = !isNaN(svgFont.descent) ? svgFont.descent : -font.vertOriginY; font.ascent = svgFont.ascent || (font.unitsPerEm - font.vertOriginY); // Values for font substitution. We're mostly working with icon fonts, so they aren't expected to be substituted. // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#sxheight font.capHeight = svgFont.capHeight || 0; // 0 is a valid value if "H" glyph doesn't exist font.xHeight = svgFont.xHeight || 0; // 0 is a valid value if "x" glyph doesn't exist if (typeof svgFont.weightClass !== 'undefined') { var wght = parseInt(svgFont.weightClass, 10); if (!isNaN(wght)) font.weightClass = wght; else { // Unknown names are silently ignored if (svgFont.weightClass === 'normal') font.weightClass = 400; if (svgFont.weightClass === 'bold') font.weightClass = 700; } } if (typeof svgFont.underlinePosition !== 'undefined') { font.underlinePosition = svgFont.underlinePosition; } if (typeof svgFont.underlineThickness !== 'undefined') { font.underlineThickness = svgFont.underlineThickness; } var glyphs = font.glyphs; var codePoints = font.codePoints; var ligatures = font.ligatures; function addCodePoint(codePoint, glyph) { if (codePoints[codePoint]) { // Ignore code points already defined return false; } codePoints[codePoint] = glyph; return true; } // add SVG glyphs to SFNT font _.forEach(svgFont.glyphs, function (svgGlyph) { var glyph = new sfnt.Glyph(); glyph.name = svgGlyph.name; glyph.codes = svgGlyph.ligatureCodes || svgGlyph.unicode; // needed for nice validator error output glyph.d = svgGlyph.d; glyph.height = !isNaN(svgGlyph.height) ? svgGlyph.height : font.height; glyph.width = !isNaN(svgGlyph.width) ? svgGlyph.width : font.width; glyphs.push(glyph); svgGlyph.sfntGlyph = glyph; _.forEach(svgGlyph.unicode, function (codePoint) { addCodePoint(codePoint, glyph); }); }); var missingGlyph; // add missing glyph to SFNT font // also, check missing glyph existance and single instance if (svgFont.missingGlyph) { missingGlyph = new sfnt.Glyph(); missingGlyph.d = svgFont.missingGlyph.d; missingGlyph.height = !isNaN(svgFont.missingGlyph.height) ? svgFont.missingGlyph.height : font.height; missingGlyph.width = !isNaN(svgFont.missingGlyph.width) ? svgFont.missingGlyph.width : font.width; } else { missingGlyph = _.find(glyphs, function (glyph) { return glyph.name === '.notdef'; }); } if (!missingGlyph) { // no missing glyph and .notdef glyph, we need to create missing glyph missingGlyph = new sfnt.Glyph(); } // Create glyphs for all characters used in ligatures _.forEach(svgFont.ligatures, function (svgLigature) { var ligature = { ligature: svgLigature.ligature, unicode: svgLigature.unicode, glyph: svgLigature.glyph.sfntGlyph }; _.forEach(ligature.unicode, function (charPoint) { // We need to have a distinct glyph for each code point so we can reference it in GSUB var glyph = new sfnt.Glyph(); var added = addCodePoint(charPoint, glyph); if (added) { glyph.name = ucs2.encode([ charPoint ]); glyphs.push(glyph); } }); ligatures.push(ligature); }); // Missing Glyph needs to have index 0 if (glyphs.indexOf(missingGlyph) !== -1) { glyphs.splice(glyphs.indexOf(missingGlyph), 1); } glyphs.unshift(missingGlyph); var nextID = 0; //add IDs _.forEach(glyphs, function (glyph) { glyph.id = nextID; nextID++; }); _.forEach(glyphs, function (glyph) { // Calculate accuracy for cubicToQuad transformation // For glyphs with height and width smaller than 500 use relative 0.06% accuracy, // for larger glyphs use fixed accuracy 0.3. var glyphSize = Math.max(glyph.width, glyph.height); var accuracy = (glyphSize > 500) ? 0.3 : glyphSize * 0.0006; //SVG transformations var svgPath = new SvgPath(glyph.d) .abs() .unshort() .unarc() .iterate(function (segment, index, x, y) { return svg.cubicToQuad(segment, index, x, y, accuracy); }); var sfntContours = svg.toSfntCoutours(svgPath); // Add contours to SFNT font glyph.contours = _.map(sfntContours, function (sfntContour) { var contour = new sfnt.Contour(); contour.points = _.map(sfntContour, function (sfntPoint) { var point = new sfnt.Point(); point.x = sfntPoint.x; point.y = sfntPoint.y; point.onCurve = sfntPoint.onCurve; return point; }); return contour; }); }); var ttf = sfnt.toTTF(font); return ttf; } module.exports = svg2ttf; svg2ttf-6.0.3/lib/000077500000000000000000000000001411005272300136605ustar00rootroot00000000000000svg2ttf-6.0.3/lib/math.js000066400000000000000000000023551411005272300151540ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/sfnt.js000066400000000000000000000245261411005272300152010ustar00rootroot00000000000000'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 = ''; // 0x40 - REGULAR - Characters are in the standard weight/style for the font // 0x80 - USE_TYPO_METRICS - use OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap as the default line spacing // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection // https://github.com/fontello/svg2ttf/issues/95 this.fsSelection = 0x40 | 0x80; // 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; this.xHeight = 0; this.capHeight = 0; //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.codes = []; // needed for nice validator error output 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; }); }); if (xMin < -32768) { throw new Error('xMin value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) + ' is out of bounds (actual ' + xMin + ', expected -32768..32767, d="' + this.d + '")'); } 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; }); }); if (xMax > 32767) { throw new Error('xMax value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) + ' is out of bounds (actual ' + xMax + ', expected -32768..32767, d="' + this.d + '")'); } 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; }); }); if (yMin < -32768) { throw new Error('yMin value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) + ' is out of bounds (actual ' + yMin + ', expected -32768..32767, d="' + this.d + '")'); } 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; }); }); if (yMax > 32767) { throw new Error('yMax value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) + ' is out of bounds (actual ' + yMax + ', expected -32768..32767, d="' + this.d + '")'); } 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'); svg2ttf-6.0.3/lib/str.js000066400000000000000000000016371411005272300150350ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/svg.js000066400000000000000000000153541411005272300150250ustar00rootroot00000000000000'use strict'; var _ = require('lodash'); var cubic2quad = require('cubic2quad'); var svgpath = require('svgpath'); var DOMParser = require('@xmldom/xmldom').DOMParser; var ucs2 = require('./ucs2'); function getGlyph(glyphElem, fontInfo) { var glyph = {}; if (glyphElem.hasAttribute('d')) { glyph.d = glyphElem.getAttribute('d').trim(); } else { // try nested var pathElem = glyphElem.getElementsByTagName('path')[0]; if (pathElem.hasAttribute('d')) { // has reversed Y axis glyph.d = svgpath(pathElem.getAttribute('d')) .scale(1, -1) .translate(0, fontInfo.ascent) .toString(); } else { throw new Error("Can't find 'd' attribute of tag."); } } 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 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 Copyright:>Fontello if (metadata && metadata.textContent) { font.metadata = metadata.textContent; } // Get 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 numeric attributes attrs = { ascent: 'ascent', descent: 'descent', unitsPerEm: 'units-per-em', capHeight: 'cap-height', xHeight: 'x-height', underlineThickness: 'underline-thickness', underlinePosition: 'underline-position' }; _.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, font); 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; svg2ttf-6.0.3/lib/ttf.js000066400000000000000000000121011411005272300150060ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/000077500000000000000000000000001411005272300144555ustar00rootroot00000000000000svg2ttf-6.0.3/lib/ttf/tables/000077500000000000000000000000001411005272300157275ustar00rootroot00000000000000svg2ttf-6.0.3/lib/ttf/tables/cmap.js000066400000000000000000000172351411005272300172150ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/glyf.js000066400000000000000000000121021411005272300172220ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/gsub.js000066400000000000000000000221301411005272300172230ustar00rootroot00000000000000'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 it’s 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; svg2ttf-6.0.3/lib/ttf/tables/head.js000066400000000000000000000027201411005272300171670ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/hhea.js000066400000000000000000000017651411005272300172030ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/hmtx.js000066400000000000000000000006671411005272300172560ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/loca.js000066400000000000000000000021061411005272300172020ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/maxp.js000066400000000000000000000027071411005272300172400ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/tables/name.js000066400000000000000000000054411411005272300172110ustar00rootroot00000000000000'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(font.description, TTF_NAMES.DESCRIPTION)); result.push.apply(result, getStrings(font.url, 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; svg2ttf-6.0.3/lib/ttf/tables/os2.js000066400000000000000000000100071411005272300167660ustar00rootroot00000000000000'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); })))); } // OpenType spec: https://docs.microsoft.com/en-us/typography/opentype/spec/os2 function createOS2Table(font) { // use at least 2 for ligatures and kerning var maxContext = font.ligatures.map(function (l) { return l.unicode.length; }).reduce(function (a, b) { return Math.max(a, b); }, 2); var buf = new ByteBuffer(96); // Version 5 is not supported in the Android 5 browser. buf.writeUint16(4); // 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 // WinAscent - WinDecent should at least be equal to TypoAscender - TypoDescender + TypoLineGap: // https://www.high-logic.com/font-editor/fontcreator/tutorials/font-metrics-vertical-line-spacing buf.writeInt16(Math.max(font.yMax, font.ascent + font.lineGap)); // usWinAscent buf.writeInt16(-Math.min(font.yMin, font.descent)); // usWinDescent buf.writeInt32(1); // ulCodePageRange1, Latin 1 buf.writeInt32(0); // ulCodePageRange2 buf.writeInt16(font.xHeight); // sxHeight buf.writeInt16(font.capHeight); // sCapHeight buf.writeUint16(0); // usDefaultChar, pointing to missing glyph (always id=0) buf.writeUint16(0); // usBreakChar, code=32 isn't guaranteed to be a space in icon fonts buf.writeUint16(maxContext); // usMaxContext, use at least 2 for ligatures and kerning return buf; } module.exports = createOS2Table; svg2ttf-6.0.3/lib/ttf/tables/post.js000066400000000000000000000037061411005272300172600ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ttf/utils.js000066400000000000000000000066661411005272300161710ustar00rootroot00000000000000'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; svg2ttf-6.0.3/lib/ucs2.js000066400000000000000000000023401411005272300150710ustar00rootroot00000000000000'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 }; svg2ttf-6.0.3/package.json000066400000000000000000000015471411005272300154070ustar00rootroot00000000000000{ "name": "svg2ttf", "version": "6.0.3", "description": "Converts SVG font to TTF font", "keywords": [ "font", "ttf", "svg", "convertor" ], "author": "Sergey Batishchev ", "license": "MIT", "repository": "fontello/svg2ttf", "bin": { "svg2ttf": "./svg2ttf.js" }, "files": [ "index.js", "svg2ttf.js", "lib/" ], "scripts": { "lint": "eslint .", "test": "npm run lint && mocha", "update_fixture": "./svg2ttf.js --ts 1457357570703 test/fixtures/test.svg test/fixtures/test.ttf" }, "dependencies": { "argparse": "^2.0.1", "cubic2quad": "^1.2.1", "lodash": "^4.17.10", "microbuffer": "^1.0.0", "svgpath": "^2.1.5", "@xmldom/xmldom": "^0.7.2" }, "devDependencies": { "eslint": "^7.0.0", "mocha": "^8.3.2", "opentype.js": "^1.3.3" } } svg2ttf-6.0.3/svg2ttf.js000077500000000000000000000035351411005272300150600ustar00rootroot00000000000000#!/usr/bin/env node /* Author: Sergey Batishchev Written for fontello.com project. */ /*eslint-disable no-console*/ 'use strict'; var fs = require('fs'); var ArgumentParser = require('argparse').ArgumentParser; var svg2ttf = require('./'); var parser = new ArgumentParser({ add_help: true, description: 'SVG to TTF font converter' }); parser.add_argument('-v', '--version', { action: 'version', version: require('./package.json').version }); parser.add_argument('-c', '--copyright', { help: 'Copyright text', required: false }); parser.add_argument('-d', '--description', { help: 'Override default description text', required: false, type: 'str' }); parser.add_argument('--ts', { help: 'Override font creation time (Unix time stamp)', required: false, type: 'int' }); parser.add_argument('-u', '--url', { help: 'Override default manufacturer url', required: false, type: 'str' }); parser.add_argument('--vs', { help: 'Override default font version string (Version 1.0), can be "x.y" or "Version x.y"', required: false, type: 'str' }); parser.add_argument('infile', { nargs: 1, help: 'Input file' }); parser.add_argument('outfile', { nargs: 1, help: 'Output file' }); var args = parser.parse_args(); var svg; var options = {}; try { svg = fs.readFileSync(args.infile[0], 'utf-8'); } catch (e) { console.error("Can't open input file (%s)", args.infile[0]); process.exit(1); } if (args.copyright) options.copyright = args.copyright; if (args.description) options.description = args.description; if (args.ts !== null) options.ts = args.ts; if (args.url) options.url = args.url; if (args.vs) options.version = args.vs; var result = Buffer.from ? Buffer.from(svg2ttf(svg, options).buffer) : new Buffer(svg2ttf(svg, options).buffer); fs.writeFileSync(args.outfile[0], result); svg2ttf-6.0.3/test/000077500000000000000000000000001411005272300140715ustar00rootroot00000000000000svg2ttf-6.0.3/test/test.js000066400000000000000000000116221411005272300154100ustar00rootroot00000000000000/*global it, describe*/ 'use strict'; const assert = require('assert'); const opentype = require('opentype.js'); const svg2ttf = require('../'); const fixture = ` Copyright (C) 2016 by original authors @ fontello.com `; describe('svg2ttf', function () { describe('version', function () { it('should throw on bad version value', function () { assert.throws(() => svg2ttf(fixture, { version: 123 })); assert.throws(() => svg2ttf(fixture, { version: 'abc' })); }); it('should set proper version', function () { let options, parsed; options = { version: '1.0' }; parsed = opentype.parse(svg2ttf(fixture, options).buffer.buffer); assert.strictEqual(parsed.tables.name.version.en, 'Version 1.0'); options = { version: 'Version 1.0' }; parsed = opentype.parse(svg2ttf(fixture, options).buffer.buffer); assert.strictEqual(parsed.tables.name.version.en, 'Version 1.0'); options = { version: 'version 2.0' }; parsed = opentype.parse(svg2ttf(fixture, options).buffer.buffer); assert.strictEqual(parsed.tables.name.version.en, 'Version 2.0'); }); }); describe('glyphs', function () { it('should return 3 glyphs', function () { let parsed = opentype.parse(svg2ttf(fixture).buffer.buffer); assert.strictEqual(parsed.glyphs.length, 3); assert.strictEqual(parsed.glyphs.glyphs[0].name, ''); // missing-glyph assert.strictEqual(parsed.glyphs.glyphs[1].name, 'duckduckgo'); assert.strictEqual(parsed.glyphs.glyphs[2].name, 'github'); }); }); describe('os/2 table', function () { let parsed = opentype.parse(svg2ttf(fixture).buffer.buffer); let os2 = parsed.tables.os2; it('winAscent + winDescent should include line gap', function () { // https://www.high-logic.com/font-editor/fontcreator/tutorials/font-metrics-vertical-line-spacing // always should be >=, but for this specific test they should be equal assert.strictEqual( os2.usWinAscent + os2.usWinDescent, os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap); }); it('os2.version = 4', function () { assert.strictEqual(os2.version, 4); }); }); it('should return an error if glyph has too large bounding box', function () { assert.throws(() => svg2ttf(` `), /xMax value .* is out of bounds/); }); }); svg2ttf-6.0.3/ttfinfo.js000077500000000000000000000034641411005272300151330ustar00rootroot00000000000000#!/usr/bin/env node /* * Internal utility qu quickly check ttf tables size */ /*eslint-disable no-console*/ 'use strict'; var fs = require('fs'); var _ = require('lodash'); var format = require('util').format; var ArgumentParser = require('argparse').ArgumentParser; var parser = new ArgumentParser({ add_help: true, description: 'Dump TTF tables info' }); parser.add_argument('infile', { nargs: 1, help: 'Input file' }); parser.add_argument('-d', '--details', { help: 'Show table dump', action: 'store_true', required: false }); var args = parser.parse_args(); var ttf; try { ttf = fs.readFileSync(args.infile[0]); } catch (e) { console.error("Can't open input file (%s)", args.infile[0]); process.exit(1); } var tablesCount = ttf.readUInt16BE(4); var i, offset, headers = []; for (i = 0; i < tablesCount; i++) { offset = 12 + i * 16; headers.push({ name: String.fromCharCode( ttf.readUInt8(offset), ttf.readUInt8(offset + 1), ttf.readUInt8(offset + 2), ttf.readUInt8(offset + 3) ), offset: ttf.readUInt32BE(offset + 8), length: ttf.readUInt32BE(offset + 12) }); } console.log(format('Tables count: %d'), tablesCount); _.forEach(_.sortBy(headers, 'offset'), function (info) { console.log('- %s: %d bytes (%d offset)', info.name, info.length, info.offset); if (args.details) { var bufTable = ttf.slice(info.offset, info.offset + info.length); var count = Math.floor(bufTable.length / 32); var offset = 0; //split buffer to the small chunks to fit the screen for (var i = 0; i < count; i++) { console.log(bufTable.slice(offset, offset + 32)); offset += 32; } //output the rest if (offset < (info.length)) { console.log(bufTable.slice(offset, info.length)); } console.log(''); } });