diff --git a/.esformatter b/.esformatter new file mode 100644 index 0000000..5198072 --- /dev/null +++ b/.esformatter @@ -0,0 +1,461 @@ +{ + "indent" : { + "ReturnStatement": ">=1", + "value": "\t", + "alignComments": true, + "ArrayExpression": 1, + "ArrayPattern": 1, + "ArrowFunctionExpression": 1, + "AssignmentExpression": 1, + "AssignmentExpression.BinaryExpression": 1, + "AssignmentExpression.LogicalExpression": 1, + "AssignmentExpression.UnaryExpression": 1, + "CallExpression": 1, + "CallExpression.BinaryExpression": 1, + "CallExpression.LogicalExpression": 1, + "CallExpression.UnaryExpression": 1, + "CatchClause": 1, + "ConditionalExpression": 1, + "CommentInsideEmptyBlock": 1, + "ClassDeclaration": 1, + "DoWhileStatement": 1, + "ForInStatement": 1, + "ForOfStatement": 1, + "ForStatement": 1, + "FunctionDeclaration": 1, + "FunctionExpression": 1, + "IfStatement": 1, + "MemberExpression": 1, + "MultipleVariableDeclaration": 1, + "NewExpression": 1, + "ObjectExpression": 1, + "ObjectExpression.BinaryExpression": 1, + "ObjectExpression.LogicalExpression": 1, + "ObjectExpression.UnaryExpression": 1, + "ObjectPattern": 1, + "ParameterList": 1, + "SingleVariableDeclaration": 0, + "SwitchCase": 1, + "SwitchStatement": 1, + "TopLevelFunctionBlock": 1, + "TryStatement": 1, + "VariableDeclaration.BinaryExpression": 1, + "VariableDeclaration.LogicalExpression": 1, + "VariableDeclaration.UnaryExpression": 1, + "WhileStatement": 1 + }, + + "lineBreak" : { + "value" : "\n", + + "before" : { + "AssignmentExpression" : -1, + "AssignmentOperator": -1, + "AssignmentPattern" : 0, + "ArrayPatternOpening": 0, + "ArrayPatternClosing": 0, + "ArrayPatternComma": 0, + "ArrowFunctionExpressionArrow": 0, + "ArrowFunctionExpressionOpeningBrace": 0, + "ArrowFunctionExpressionClosingBrace": "<=1", + "BlockStatement" : -1, + "BreakKeyword": ">=1", + "CallExpression" : -1, + "CallExpressionOpeningParentheses" : 0, + "CallExpressionClosingParentheses" : -1, + "ClassDeclaration" : ">=1", + "ClassDeclarationOpeningBrace" : 0, + "ClassDeclarationClosingBrace" : "<=1", + "ConditionalExpression" : ">=1", + "CatchOpeningBrace" : 0, + "CatchClosingBrace" : "<=1", + "CatchKeyword": 0, + "DeleteOperator" : -1, + "DoWhileStatement" : -1, + "DoWhileStatementOpeningBrace" : 0, + "DoWhileStatementClosingBrace" : "<=1", + "EndOfFile" : 1, + "EmptyStatement" : -1, + "FinallyKeyword" : -1, + "FinallyOpeningBrace" : 0, + "FinallyClosingBrace" : "<=1", + "ForInStatement" : -1, + "ForInStatementExpressionOpening" : -1, + "ForInStatementExpressionClosing" : -1, + "ForInStatementOpeningBrace" : 0, + "ForInStatementClosingBrace" : "<=1", + "ForOfStatement" : ">=1", + "ForOfStatementExpressionOpening" : 0, + "ForOfStatementExpressionClosing" : 0, + "ForOfStatementOpeningBrace" : 0, + "ForOfStatementClosingBrace" : "<=1", + "ForStatement" : -1, + "ForStatementExpressionOpening" : -1, + "ForStatementExpressionClosing" : -1, + "ForStatementOpeningBrace" : 0, + "ForStatementClosingBrace" : "<=1", + "FunctionExpression" : -1, + "FunctionExpressionOpeningBrace" : 0, + "FunctionExpressionClosingBrace" : "<=1", + "FunctionDeclaration" : -1, + "FunctionDeclarationOpeningBrace" : 0, + "FunctionDeclarationClosingBrace" : "<=1", + "IIFEClosingParentheses" : 0, + "IfStatement" : -1, + "IfStatementOpeningBrace" : 0, + "IfStatementClosingBrace" : "<=1", + "ElseIfStatement" : -1, + "ElseIfStatementOpeningBrace" : 0, + "ElseIfStatementClosingBrace" : "<=1", + "ElseStatement" : 0, + "ElseStatementOpeningBrace" : 0, + "ElseStatementClosingBrace" : "<=1", + "LogicalExpression" : -1, + "MethodDefinition": ">=1", + "MemberExpressionOpening": 0, + "MemberExpressionClosing": 0, + "MemberExpressionPeriod": -1, + "ObjectExpressionClosingBrace" : -1, + "ObjectPatternOpeningBrace": 0, + "ObjectPatternClosingBrace": 0, + "ObjectPatternComma": 0, + "Property" : -1, + "PropertyValue" : 0, + "ReturnStatement" : -1, + "SwitchOpeningBrace" : 0, + "SwitchClosingBrace" : -1, + "ThisExpression" : -1, + "ThrowStatement" : -1, + "TryKeyword": -1, + "TryOpeningBrace" : 0, + "TryClosingBrace" : "<=1", + "VariableName" : -1, + "VariableValue" : -1, + "VariableDeclaration" : -1, + "VariableDeclarationSemiColon": -1, + "VariableDeclarationWithoutInit" : -1, + "WhileStatement" : -1, + "WhileStatementOpeningBrace" : 0, + "WhileStatementClosingBrace" : "<=1" + }, + + "after" : { + "AssignmentExpression" : -1, + "AssignmentOperator" : -1, + "AssignmentPattern" : 0, + "ArrayPatternOpening": 0, + "ArrayPatternClosing": 0, + "ArrayPatternComma": 0, + "ArrowFunctionExpressionArrow": 0, + "ArrowFunctionExpressionOpeningBrace": ">=1", + "ArrowFunctionExpressionClosingBrace": -1, + "BlockStatement" : -1, + "BreakKeyword": -1, + "CallExpression" : -1, + "CallExpressionOpeningParentheses" : -1, + "CallExpressionClosingParentheses" : -1, + "ClassDeclaration" : ">=1", + "ClassDeclarationOpeningBrace" : ">=1", + "ClassDeclarationClosingBrace" : ">=1", + "CatchOpeningBrace" : "<=1", + "CatchClosingBrace" : -1, + "CatchKeyword": 0, + "ConditionalExpression" : -1, + "DeleteOperator" : -1, + "DoWhileStatement" : -1, + "DoWhileStatementOpeningBrace" : "<=1", + "DoWhileStatementClosingBrace" : -1, + "EmptyStatement" : 1, + "FinallyKeyword" : -1, + "FinallyOpeningBrace" : "<=2", + "FinallyClosingBrace" : -1, + "ForInStatement" : -1, + "ForInStatementExpressionOpening" : -1, + "ForInStatementExpressionClosing" : -1, + "ForInStatementOpeningBrace" : "<=1", + "ForInStatementClosingBrace" : -1, + "ForOfStatement" : -1, + "ForOfStatementExpressionOpening" : "<2", + "ForOfStatementExpressionClosing" : -1, + "ForOfStatementOpeningBrace" : ">=1", + "ForOfStatementClosingBrace" : ">=1", + "ForStatement" : -1, + "ForStatementExpressionOpening" : -1, + "ForStatementExpressionClosing" : -1, + "ForStatementOpeningBrace" : "<=1", + "ForStatementClosingBrace" : -1, + "FunctionExpression" : -1, + "FunctionExpressionOpeningBrace" : "<=1", + "FunctionExpressionClosingBrace" : -1, + "FunctionDeclaration" : -1, + "FunctionDeclarationOpeningBrace" : "<=1", + "FunctionDeclarationClosingBrace" : -1, + "IIFEOpeningParentheses" : 0, + "IfStatement" : -1, + "IfStatementOpeningBrace" : "<=1", + "IfStatementClosingBrace" : -1, + "ElseIfStatement" : -1, + "ElseIfStatementOpeningBrace" : "<=1", + "ElseIfStatementClosingBrace" : -1, + "ElseStatement" : -1, + "ElseStatementOpeningBrace" : "<=1", + "ElseStatementClosingBrace" : -1, + "LogicalExpression" : -1, + "MethodDefinition": ">=1", + "MemberExpressionOpening": 0, + "MemberExpressionClosing" : ">=0", + "MemberExpressionPeriod": 0, + "ObjectExpressionOpeningBrace" : "<=1", + "ObjectPatternOpeningBrace": 0, + "ObjectPatternClosingBrace": 0, + "ObjectPatternComma": 0, + "Property" : -1, + "PropertyName" : 0, + "ReturnStatement" : -1, + "SwitchOpeningBrace" : "<=1", + "SwitchClosingBrace" : -1, + "SwitchCaseColon": ">=1", + "ThisExpression" : -1, + "ThrowStatement" : -1, + "TryKeyword": -1, + "TryOpeningBrace" : "<=1", + "TryClosingBrace" : -1, + "VariableValue" : -1, + "VariableDeclaration" : -1, + "VariableDeclarationSemiColon" : ">=1", + "WhileStatement" : -1, + "WhileStatementOpeningBrace" : "<=1", + "WhileStatementClosingBrace" : -1 + } + }, + + + "whiteSpace" : { + "value" : " ", + "removeTrailing" : 1, + + "before" : { + "AssignmentPattern" : 1, + "ArrayExpressionOpening" : -1, + "ArrayExpressionClosing" : -1, + "ArrayExpressionComma" : -1, + "ArrayPatternOpening": 1, + "ArrayPatternClosing": 0, + "ArrayPatternComma": 0, + "ArrowFunctionExpressionArrow": 1, + "ArrowFunctionExpressionOpeningBrace": 1, + "ArrowFunctionExpressionClosingBrace": 0, + "ArgumentComma" : -1, + "ArgumentList" : 0, + "AssignmentOperator" : 1, + "BinaryExpression": -1, + "BinaryExpressionOperator" : 1, + "BlockComment" : 1, + "CallExpression" : -1, + "CallExpressionOpeningParentheses" : 0, + "CallExpressionClosingParentheses" : -1, + "CatchParameterList" : 0, + "CatchOpeningBrace" : 1, + "CatchClosingBrace" : 1, + "CatchKeyword" : 1, + "CommaOperator" : 0, + "ClassDeclarationOpeningBrace" : 1, + "ClassDeclarationClosingBrace" : 1, + "ConditionalExpressionConsequent" : 1, + "ConditionalExpressionAlternate" : 1, + "DoWhileStatementOpeningBrace" : 1, + "DoWhileStatementClosingBrace" : 1, + "DoWhileStatementConditional" : 1, + "EmptyStatement" : 0, + "ExpressionClosingParentheses": 0, + "FinallyKeyword" : -1, + "FinallyOpeningBrace" : 1, + "FinallyClosingBrace" : 1, + "ForInStatement" : 1, + "ForInStatementExpressionOpening" : 1, + "ForInStatementExpressionClosing" : 0, + "ForInStatementOpeningBrace" : 1, + "ForInStatementClosingBrace" : 1, + "ForOfStatement" : 1, + "ForOfStatementExpressionOpening" : 1, + "ForOfStatementExpressionClosing" : 0, + "ForOfStatementOpeningBrace" : 1, + "ForOfStatementClosingBrace" : 1, + "ForStatement" : 1, + "ForStatementExpressionOpening" : 1, + "ForStatementExpressionClosing" : 0, + "ForStatementOpeningBrace" : 1, + "ForStatementClosingBrace" : -1, + "ForStatementSemicolon" : 0, + "FunctionDeclarationOpeningBrace" : 1, + "FunctionDeclarationClosingBrace" : 1, + "FunctionExpressionOpeningBrace" : 1, + "FunctionExpressionClosingBrace" : 1, + "FunctionGeneratorAsterisk": 1, + "FunctionName" : 1, + "IIFEClosingParentheses" : 0, + "IfStatementConditionalOpening" : 1, + "IfStatementConditionalClosing" : 0, + "IfStatementOpeningBrace" : 1, + "IfStatementClosingBrace" : -1, + "ModuleSpecifierClosingBrace": 1, + "ElseStatementOpeningBrace" : 1, + "ElseStatementClosingBrace" : -1, + "ElseIfStatementOpeningBrace" : 1, + "ElseIfStatementClosingBrace" : -1, + "LineComment" : 1, + "LogicalExpressionOperator" : 1, + "MemberExpressionOpening": 0, + "MemberExpressionClosing" : -1, + "MemberExpressionPeriod": 0, + "ObjectExpressionOpeningBrace": -1, + "ObjectExpressionClosingBrace": -1, + "ObjectPatternOpeningBrace": -1, + "ObjectPatternClosingBrace": -1, + "ObjectPatternComma": 0, + "Property" : -1, + "PropertyValue" : 1, + "ParameterComma" : -1, + "ParameterList" : 0, + "SwitchDiscriminantOpening" : 1, + "SwitchDiscriminantClosing" : -1, + "SwitchCaseColon": 0, + "ThrowKeyword": -1, + "TryKeyword": -1, + "TryOpeningBrace" : 1, + "TryClosingBrace" : -1, + "UnaryExpressionOperator": -1, + "VariableName" : -1, + "VariableValue" : 1, + "VariableDeclarationSemiColon" : 0, + "WhileStatementConditionalOpening" : -1, + "WhileStatementConditionalClosing" : -1, + "WhileStatementOpeningBrace" : -1, + "WhileStatementClosingBrace" : -1 + }, + + "after" : { + "AssignmentPattern" : 1, + "ArrayExpressionOpening" : -1, + "ArrayExpressionClosing" : -1, + "ArrayExpressionComma" : 1, + "ArrayPatternOpening": 0, + "ArrayPatternClosing": 1, + "ArrayPatternComma": 1, + "ArrowFunctionExpressionArrow": 1, + "ArrowFunctionExpressionOpeningBrace": 0, + "ArrowFunctionExpressionClosingBrace": 0, + "ArgumentComma" : 1, + "ArgumentList" : 0, + "AssignmentOperator" : 1, + "BinaryExpression": -1, + "BinaryExpressionOperator" : 1, + "BlockComment" : -1, + "CallExpression" : -1, + "CallExpressionOpeningParentheses" : -1, + "CallExpressionClosingParentheses" : -1, + "CatchParameterList" : -1, + "CatchOpeningBrace" : -1, + "CatchClosingBrace" : -1, + "CatchKeyword" : -1, + "ClassDeclarationOpeningBrace" : 1, + "ClassDeclarationClosingBrace" : 1, + "CommaOperator" : 1, + "ConditionalExpressionConsequent" : 1, + "ConditionalExpressionTest" : 1, + "DoWhileStatementOpeningBrace" : -1, + "DoWhileStatementClosingBrace" : -1, + "DoWhileStatementBody" : -1, + "EmptyStatement" : -1, + "ExpressionOpeningParentheses" : 0, + "FinallyKeyword" : -1, + "FinallyOpeningBrace" : -1, + "FinallyClosingBrace" : -1, + "ForInStatement" : -1, + "ForInStatementExpressionOpening" : -1, + "ForInStatementExpressionClosing" : -1, + "ForInStatementOpeningBrace" : -1, + "ForInStatementClosingBrace" : -1, + "ForStatement" : -1, + "ForStatementExpressionOpening" : -1, + "ForStatementExpressionClosing" : -1, + "ForStatementClosingBrace" : -1, + "ForStatementOpeningBrace" : -1, + "ForStatementSemicolon" : -1, + "FunctionReservedWord": 1, + "FunctionName" : 0, + "FunctionExpressionOpeningBrace" : 1, + "FunctionExpressionClosingBrace" : -1, + "FunctionDeclarationOpeningBrace" : 1, + "FunctionDeclarationClosingBrace" : -1, + "IIFEOpeningParentheses" : 0, + "IfStatementConditionalOpening" : 0, + "IfStatementConditionalClosing" : -1, + "IfStatementOpeningBrace" : -1, + "IfStatementClosingBrace" : 1, + "ModuleSpecifierOpeningBrace": 1, + "ElseStatementOpeningBrace" : -1, + "ElseStatementClosingBrace" : -1, + "ElseIfStatementOpeningBrace" : -1, + "ElseIfStatementClosingBrace" : -1, + "MemberExpressionOpening" : -1, + "MemberExpressionClosing": 0, + "MemberExpressionPeriod": 0, + "MethodDefinitionName" : 0, + "LogicalExpressionOperator" : 1, + "ObjectExpressionOpeningBrace": -1, + "ObjectExpressionClosingBrace": -1, + "ObjectPatternOpeningBrace": -1, + "ObjectPatternClosingBrace": -1, + "ObjectPatternComma": 1, + "PropertyName" : 0, + "PropertyValue" : -1, + "ParameterComma" : 1, + "ParameterList" : 0, + "SwitchDiscriminantOpening" : -1, + "SwitchDiscriminantClosing" : 1, + "ThrowKeyword": -1, + "TryKeyword": -1, + "TryOpeningBrace" : -1, + "TryClosingBrace" : -1, + "UnaryExpressionOperator": -1, + "VariableName" : 1, + "VariableValue" : 0, + "VariableDeclarationSemiColon": -1, + "WhileStatementConditionalOpening" : -1, + "WhileStatementConditionalClosing" : -1, + "WhileStatementOpeningBrace" : -1, + "WhileStatementClosingBrace" : -1 + } + }, + + "jsx": { + "formatJSX": true, + "attrsOnSameLineAsTag": false, + "maxAttrsOnTag": 3, + "firstAttributeOnSameLine": false, + "alignWithFirstAttribute": false, + "spaceInJSXExpressionContainers": "", + "htmlOptions": { + "brace_style": "collapse", + "indent_char": " ", + "indent_size": 2, + "max_preserve_newlines": 2, + "preserve_newlines": true + } + }, + + "plugins": [ + "esformatter-quotes", + "esformatter-literal-notation", + "esformatter-spaced-lined-comment", + "esformatter-semicolons", + "esformatter-semicolon-first", + "esformatter-jsx" + ], + + "quotes": { + "type": "double", + "avoidEscape": true + } +} \ No newline at end of file diff --git a/README.md b/README.md index 58915b4..700584d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Iosevka’s default ligation set is assigned to `calt` feature, though not all o To build Iosevka you should: -1. Ensure that [`node`](http://nodejs.org) (≥ 6.0), [`FontForge`](http://fontforge.org) (with Python scripting support, ≥ Aug. 2015 release), [`ttfautohint`](http://www.freetype.org/ttfautohint/), [`otfcc`](https://github.com/caryll/otfcc) (≥ 0.4.4) and `make` are runnable in your terminal. +1. Ensure that [`node`](http://nodejs.org) (≥ 6.0), [`ttfautohint`](http://www.freetype.org/ttfautohint/), [`otfcc`](https://github.com/caryll/otfcc) (≥ 0.4.4) and `make` are runnable in your terminal. - Windows users may need to install MinGW and make \*nix utilities accessible (`mkdir.exe`, `cp.exe`, `cat.exe` and `rm.exe`, in particular) from Command Prompt. Utilities provided by [Git for Windows](https://git-for-windows.github.io/) works fine. 2. Install necessary libs by `npm install`. If you’ve installed them, upgrade to the latest. 3. `make`. diff --git a/generator.js b/generator.js index 2385eef..ba95cd5 100644 --- a/generator.js +++ b/generator.js @@ -8,10 +8,15 @@ var parameters = require("./parameters"); var toml = require("toml"); var Glyph = require("./support/glyph"); +var autoref = require("./support/autoref"); + +var caryllShapeOps = require("caryll-shapeops"); +var c2q = require("otfcc-c2q"); function hasv(obj) { if (!obj) return false; - for (var k in obj) if (obj[k]) return true; + for (var k in obj) + if (obj[k]) return true; return false; } @@ -29,7 +34,13 @@ var font = function () { font.glyf = font.glyf.sort(function (a, b) { var pri1 = a.cmpPriority || 0; var pri2 = b.cmpPriority || 0; - return (pri2 !== pri1 ? pri2 - pri1 : a.contours.length !== b.contours.length ? a.contours.length - b.contours.length : (a.unicode && b.unicode && a.unicode[0] !== b.unicode[0]) ? a.unicode[0] - b.unicode[0] : (a.name < b.name) ? (-1) : (a.name > b.name) ? 1 : 0); + if (a.contours && b.contours && a.contours.length < b.contours.length) return 1; + if (a.contours && b.contours && a.contours.length > b.contours.length) return (-1); + if (a.unicode && a.unicode[0] && !b.unicode || !b.unicode[0]) return (-1); + if (b.unicode && b.unicode[0] && !a.unicode || !a.unicode[0]) return (+1); + if (a.unicode && a.unicode[0] && b.unicode && b.unicode[0] && a.unicode[0] < b.unicode[0]) return (-1); + if (a.unicode && a.unicode[0] && b.unicode && b.unicode[0] && a.unicode[0] > b.unicode[0]) return (+1); + return (a.name < b.name) ? (-1) : (a.name > b.name) ? 1 : 0; }); return font; }(); @@ -41,7 +52,7 @@ if (argv.charmap) { return [ glyph.name, glyph.unicode, - glyph.advanceWidth === 0 ? hasv(glyph.anchors) ? 1 : (glyph.contours && glyph.contours.length) ? 2 : 0 : 0 + glyph.advanceWidth === 0 ? (hasv(glyph.anchors) ? 1 : (glyph.contours && glyph.contours.length) ? 2 : 0) : 0 ]; })), "utf8"); })(); @@ -53,13 +64,16 @@ if (argv.svg) { var foundNaN = false; var glyfname = ""; - function mix(a, b, p) { return a + (b - a) * p; } + function mix(a, b, p) { + return a + (b - a) * p; + } function toSVGPath(glyph) { var buf = ""; foundNaN = false; glyfname = glyph.name; - if (glyph.contours) for (var j = 0; j < glyph.contours.length; j++) { + if (glyph.contours) + for (var j = 0; j < glyph.contours.length; j++) { buf += Glyph.contourToSVGPath(glyph.contours[j], false); } return buf; @@ -103,6 +117,110 @@ if (argv.svg) { })(); } +var StatusBar = function (message, n) { + this.message = message; + this.total = n; + this.progress = 0; + this.lastUpdate = new Date(); +}; +StatusBar.prototype.update = function (j) { + var j0 = this.progress; + var LEN = 25; + this.progress = j; + if (Math.round(this.progress / this.total * LEN) !== Math.round(j0 / this.total * LEN)) { + var filled = Math.round(this.progress / this.total * LEN); + var remain = LEN - filled; + var pg = Array(filled + 1).join("#") + Array(remain + 1).join(" "); + var d = new Date(); + if (!remain || d - this.lastUpdate > 500) { + process.stderr.write(" [" + pg + "] " + this.message + "\n"); + this.lastUpdate = d; + } + } +}; + +StatusBar.each = function (message, a, f) { + var bar = new StatusBar(message, a.length); + a.forEach(function (x, j) { + var r = f.apply(this, arguments); + bar.update(j + 1); + return r; + }); +}; + +if (argv.o) { + console.log(" Writing output -> " + argv.o); + var o_glyf = {}; + var cmap = {}; + var skew = (argv.uprightify ? 1 : 0) * Math.tan((font.post.italicAngle || 0) / 180 * Math.PI); + // autoref + autoref(font.glyf); + // regulate + StatusBar.each("Regulate", font.glyf, (g) => { + if (g.contours) { + for (var k = 0; k < g.contours.length; k++) { + var contour = g.contours[k]; + for (var p = 0; p < contour.length; p++) { + contour[p].x += contour[p].y * skew; + if (contour[p].on) { + contour[p].x = Math.round(contour[p].x); + } + } + var offJ = null, mx = null; + for (var p = 0; p < contour.length; p++) { + if (contour[p].on) { + if (offJ) { + var origx = contour[p].x; + var rx = Math.round(contour[p].x * 4) / 4; + var origx0 = mx; + var rx0 = contour[offJ - 1].x; + if (origx != origx0) { + for (var poff = offJ; poff < p; poff++) { + contour[poff].x = (contour[poff].x - origx0) / (origx - origx0) * (rx - rx0) + rx0; + } + } + } + mx = contour[p].x; + contour[p].x = Math.round(contour[p].x * 4) / 4; + offJ = p + 1; + } + } + } + var c1 = []; + for (var k = 0; k < g.contours.length; k++) { + c1.push(Glyph.contourToStandardCubic(g.contours[k])); + } + g.contours = c1; + } + }); + StatusBar.each("Remove Overlap", font.glyf, (g) => { + if (g.contours) { + g.contours = caryllShapeOps.removeOverlap(g.contours, 1, 2048, true); + } + }); + StatusBar.each("Finalize", font.glyf, (g) => { + if (g.contours) { + Glyph.prototype.cleanup.call(g, 0.25); + g.contours = c2q.contours(g.contours); + for (var k = 0; k < g.contours.length; k++) { + var contour = g.contours[k]; + for (var p = 0; p < contour.length; p++) { + contour[p].x -= contour[p].y * skew; + } + } + } + o_glyf[g.name] = g; + if (g.unicode && g.unicode.length) { + cmap[g.unicode[0]] = g.name; + } + }); + + font.glyf = o_glyf; + font.cmap = cmap; + font.glyfMap = null; + fs.writeFileSync(argv.o, JSON.stringify(font)); +} + if (argv.meta) { (function () { console.log(" Writing metadata as JSON -> " + argv.meta); diff --git a/glyphs/common-shapes.ptl b/glyphs/common-shapes.ptl index bb219f7..80256e5 100644 --- a/glyphs/common-shapes.ptl +++ b/glyphs/common-shapes.ptl @@ -193,10 +193,7 @@ export : define [apply] : begin local mt : [mix left RIGHTSB 0.5] + (st - CORRECTION_OMIDX) * STROKE local mb : [mix left RIGHTSB 0.5] + (sb + CORRECTION_OMIDX) * STROKE include : dispiro - widths.lhs - g4 (mt) (top - O) [heading {.y (-1) .x (-st)}] - archv - flat (left + (STROKE - fine) * HVCONTRAST) (top - SMALLSMOOTHA) [widths fine 0] + flat (left + (STROKE - fine) * HVCONTRAST) (top - SMALLSMOOTHA - 0.01) [widths fine 0] curl (left + (STROKE - fine) * HVCONTRAST) (0 + SMALLSMOOTHB) [widths fine 0] arcvh g4 (mb) O [widths.heading STROKE 0 {.y (1) .x (-sb)}] @@ -204,7 +201,10 @@ export : define [apply] : begin [if ((SMALLSMOOTHA + SMALLSMOOTHB) / top > 0.75) flat.ai flat] (RIGHTSB - OX) (0 + SMALLSMOOTHA) [if ((SMALLSMOOTHA + SMALLSMOOTHB) / top > 0.75) curl.ai curl] (RIGHTSB - OX) (top - SMALLSMOOTHB) arcvh - close + g4 (mt) (top - O) [widths.heading STROKE 0 {.y (-1) .x (-st)}] + archv + flat (left + (STROKE - fine) * HVCONTRAST) (top - SMALLSMOOTHA) [widths fine 0] + curl (left + (STROKE - fine) * HVCONTRAST) (top - SMALLSMOOTHA - 0.01) [widths fine 0] define [OBarRightShape top right] : glyph-construction include : create-glyph [OBarLeftShape top (WIDTH - [fallback right RIGHTSB])] diff --git a/glyphs/letters-unified-basic.ptl b/glyphs/letters-unified-basic.ptl index bd3cf82..c044f5e 100644 --- a/glyphs/letters-unified-basic.ptl +++ b/glyphs/letters-unified-basic.ptl @@ -2564,11 +2564,11 @@ export : define [apply] : begin save 'cyrtshe' 0x45B ### F - define [FShape top y] : glyph-construction + define [FShape top y noserif] : glyph-construction include : VBarLeft (SB * 1.5) 0 [fallback top CAP] include : HBarTop (SB * 1.5 - O) RIGHTSB [fallback top CAP] include : HBar (SB * 1.5 - O) (RIGHTSB - [Math.max HALFSTROKE ((RIGHTSB - SB) * 0.15)] - [if SLAB (STROKE * 0.25) 0]) ([fallback top CAP] * [fallback y [if SLAB 0.52 0.54]]) - if SLAB : begin + if (SLAB && !noserif) : begin include : LeftwardTopSerif (SB * 1.5) [fallback top CAP] SIDEJUT include : CenterBottomSerif (SB * 1.5 + HALFSTROKE * HVCONTRAST) 0 JUT tag-contour 'serifBottom' @@ -2778,9 +2778,13 @@ export : define [apply] : begin ### E define [EShape top] : glyph-construction - include : FShape top + include : FShape top nothing true include : HBarBottom (SB * 1.5 - O) RIGHTSB 0 if SLAB : begin + include : LeftwardTopSerif (SB * 1.5) [fallback top CAP] SIDEJUT + include : CenterBottomSerif (SB * 1.5 + HALFSTROKE * HVCONTRAST) 0 JUT + tag-contour 'serifBottom' + include : DownwardRightSerif RIGHTSB [fallback top CAP] VJUT include : LeftwardBottomSerif (SB * 1.5) 0 SIDEJUT include : UpwardRightSerif RIGHTSB 0 VJUT diff --git a/glyphs/numbers.ptl b/glyphs/numbers.ptl index f920f83..1e46503 100644 --- a/glyphs/numbers.ptl +++ b/glyphs/numbers.ptl @@ -138,12 +138,20 @@ export : define [apply] : begin sketch # six include markset.capital - - include : OShape (CAP * 0.6) 0 SB RIGHTSB - local ymiddlea : (CAP * 0.6 - SMALLSMOOTHA + SMALLSMOOTHB) / 2 + local top (CAP * 0.6) + local ymiddlea : (top - SMALLSMOOTHA + SMALLSMOOTHB) / 2 + local ymiddleb : (top - SMALLSMOOTHB + SMALLSMOOTHA) / 2 include : dispiro widths.rhs - g4.up.start (SB + OX) ymiddlea + g4.up.start (SB + OX + STROKE * 0.5 * HVCONTRAST) ymiddlea [widths.heading 0 (STROKE * 0.5) UPWARD] + arcvh + g4 (MIDDLE - CORRECTION_OMIDS) (top - O) [widths.rhs] + archv + g4.down.mid (RIGHTSB - OX) ymiddleb + arcvh + g4 (MIDDLE + CORRECTION_OMIDS) (O) + archv + g4.up.mid (SB + OX) ymiddlea quadcontrols 0 0.8 g4 ([mix SB RIGHTSB 0.85] - HALFSTROKE * HVCONTRAST) CAP save 'six' '6' diff --git a/makesupport.mk b/makesupport.mk index 5d76d3b..e8e1a08 100644 --- a/makesupport.mk +++ b/makesupport.mk @@ -11,7 +11,7 @@ PATELC = node ./node_modules/patel/bin/patel-c GLYPH_SEGMENTS = glyphs/common-shapes.js glyphs/overmarks.js glyphs/letters-unified-basic.js glyphs/letters-unified-extended.js glyphs/numbers.js glyphs/symbol-punctuation.js glyphs/symbol-math.js glyphs/symbol-letter.js glyphs/symbol-geometric.js glyphs/symbol-other.js glyphs/symbol-braille.js glyphs/autobuilds.js buildglyphs.js SUPPORT_FILES_FROM_PTL = support/glyph.js support/spiroexpand.js support/spirokit.js parameters.js support/anchor.js support/point.js support/transform.js support/utils.js meta/aesthetics.js meta/naming.js meta/features.js -SUPPORT_FILES_JS = generator.js emptyfont.toml parameters.toml support/fairify.js +SUPPORT_FILES_JS = generator.js emptyfont.toml parameters.toml support/fairify.js support/autoref.js SUPPORT_FILES = $(SUPPORT_FILES_FROM_PTL) $(SUPPORT_FILES_JS) SCRIPTS = $(SUPPORT_FILES) $(GLYPH_SEGMENTS) SCRIPTS_FROM_PTL = $(SUPPORT_FILES_FROM_PTL) $(GLYPH_SEGMENTS) diff --git a/onegroup.mk b/onegroup.mk index cf3ace7..89c6269 100644 --- a/onegroup.mk +++ b/onegroup.mk @@ -58,7 +58,7 @@ fonts : $(DISTTARGETS) svgs : $(SVG0) # Pass 0 : file construction -OUTPUTS = --meta $@ --svg $(subst .fdt,.svg,$@) +OUTPUTS = -o $@ OUTPUT_CM = $(if $(NOCHARMAP),,--charmap $(subst .fdt,.charmap,$(subst $(OBJDIR)/.pass0-,$(OBJDIR)/,$@))) $(OBJDIR)/.pass0-$(PREFIX)-thin.fdt : $(SCRIPTS) | $(OBJDIR) $(DISTDIR) $(NODE_FDT) generator iosevka $(STYLE_COMMON) w-thin s-upright $(STYLE_UPRIGHT) $(STYLE_SUFFIX) $(OUTPUTS) @@ -111,14 +111,14 @@ $(MAPS) : $(OBJDIR)/%.charmap : $(OBJDIR)/.pass0-%.fdt $(PASS) # Pass 1 : Outline cleanup and merge features -$(PASS1) : $(OBJDIR)/.pass1-%.ttf : pass1-cleanup.py $(OBJDIR)/.pass0-%.svg - @echo Fontforge $< --> $@ - @fontforge -quiet -script $^ $@.a.ttf $(if $(findstring italic,$@),10,$(if $(findstring oblique,$@),10,0)) $(_DONTREF) $(SUPPRESS_ERRORS) - @$(HINT) $@.a.ttf $@ - @-rm $@.a.ttf +$(TARGETS) : $(OBJDIR)/%.ttf : $(OBJDIR)/.pass0-%.fdt + @otfccbuild $< -o $@.a.ttf + @$(HINT) $@.a.ttf $@.b.ttf + @otfccdump $@.b.ttf | otfccbuild -O3 -s -o $@ + @-rm $@.a.ttf $@.b.ttf # Pass 2 : add metadata -$(TARGETS) : $(OBJDIR)/%.ttf : pass2-finalize.js $(OBJDIR)/.pass1-%.ttf $(OBJDIR)/.pass0-%.fdt - @otfccdump $(word 2,$^) | $(NODE) $< $(word 3,$^) | otfccbuild -s -O3 -o $@ --keep-average-char-width $(HINT_SUFFIX) +#$(TARGETS) : $(OBJDIR)/%.ttf : pass2-finalize.js $(OBJDIR)/.pass1-%.ttf $(OBJDIR)/.pass0-%.fdt +# @otfccdump $(word 2,$^) | $(NODE) $< $(word 3,$^) | otfccbuild -s -O3 -o $@ --keep-average-char-width $(HINT_SUFFIX) $(DISTTARGETS) : $(DISTDIR)/%.ttf : $(OBJDIR)/%.ttf @cp $< $@ diff --git a/package.json b/package.json index 95fbe8a..ed0b2c6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "dependencies": { "bezier-js": "^2.0.0", "cubic2quad": "^1.0.0", + "otfcc-c2q": "^0.5.0", + "caryll-shapeops": "^0.2.0", "libspiro-js": ">=0.3.0", "patel": ">=0.32.0", "toml": ">=2.3.0", diff --git a/support/autoref.js b/support/autoref.js new file mode 100644 index 0000000..5c71283 --- /dev/null +++ b/support/autoref.js @@ -0,0 +1,99 @@ +var Point = require("./point"); + +function delta(a, b) { + return Math.round((a - b) * 32); +} + +function contourHash(c) { + if (!c || c.length < 2) return "."; + var lx = c[0].x, ly = c[0].y; + var buf = ""; + for (var j = 1; j < c.length; j++) { + var z = c[j]; + buf += `${z.on ? 'o':'f'}${z.cubic?'c':'q'}${delta(z.x, lx)},${delta(z.y, ly)};`; + lx = z.x, ly = z.y; + } + return buf; +} + +function match(g1, g2, _n) { + for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) { + var found = true; + for (var k = j; k < g2.contours.length && k - j < g1.contours.length; k++) { + if (g1.contours[k - j].hash !== g2.contours[k].hash + || !(k <= j || delta(g1.contours[k - j][0].x, g1.contours[k - j - 1][0].x) === delta(g2.contours[k][0].x, g2.contours[k - 1][0].x) + && delta(g1.contours[k - j][0].y, g1.contours[k - j - 1][0].y) === delta(g2.contours[k][0].y, g2.contours[k - 1][0].y))) { + found = false; + break; + } + } + if (found) { + if (!g2.references) g2.references = []; + g2.references.push({ + glyph: g1.name, + _n: _n, + x: g2.contours[j][0].x - g1.contours[0][0].x, + y: g2.contours[j][0].y - g1.contours[0][0].y + }); + g2.contours.splice(j, g1.contours.length); + return true; + } + } + return false; +} + +function unlinkRef(g, dx, dy, glyf) { + var cntrs = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic))); + if (g.references) + for (let r of g.references) { + cntrs = cntrs.concat(unlinkRef(glyf[r._n], r.x + dx, r.y + dy, glyf)); + } + return cntrs; +} + +function autoref(glyf) { + for (var j = 0; j < glyf.length; j++) { + var g = glyf[j]; + if (g.contours) { + for (var k = 0; k < g.contours.length; k++) { + var contour = g.contours[k]; + contour.hash = contourHash(contour); + } + } + } + + + // Refl-referencify, forward. + for (var j = 0; j < glyf.length; j++) { + if (!glyf[j].contours.length || glyf[j].references && glyf[j].references.length) continue; + for (var k = j + 1; k < glyf.length; k++) { + if (glyf[j].contours.length === glyf[k].contours.length) { + if (match(glyf[j], glyf[k], j)) { + // console.log("Refl", glyf[j].name, glyf[j].unicode, "->", glyf[k].name, glyf[k].unicode); + } + } + } + } + + // referencify, backward + for (var j = 0; j < glyf.length; j++) { + if (glyf[j].cmpPriority < 0 || !glyf[j].contours.length || glyf[j].references && glyf[j].references.length) continue; + for (var k = j - 1; k >= 0; k--) { + if (glyf[j].contours.length > glyf[k].contours.length) continue; + while(match(glyf[j], glyf[k], j)){ + // console.log("Part", glyf[j].name, "->", glyf[k].name); + } + } + } + + // unlink composite + for (var j = 0; j < glyf.length; j++) { + if (glyf[j].contours.length === 0 || !glyf[j].references || glyf[j].references.length === 0) continue; + // console.log("Unlink", glyf[j].name); + var cs = unlinkRef(glyf[j], 0, 0, glyf); + glyf[j].contours = g.contours.concat(cs); + glyf[j].references = []; + } +} + +module.exports = autoref; diff --git a/support/fairify.js b/support/fairify.js index 2b5e971..a44f39e 100644 --- a/support/fairify.js +++ b/support/fairify.js @@ -122,9 +122,9 @@ function splitAtExtrema(z1, z2, z3, z4, angles, splitpoints) { var t2 = ts[k]; var bef = splitBefore(z1, z2, z3, z4, t2); var seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2); - seg[1].onCurve = seg[2].onCurve = false; + seg[1].on = seg[2].on = false; seg[1].cubic = seg[2].cubic = true; - seg[3].onCurve = true; + seg[3].on = true; splitpoints.push(seg[1], seg[2], seg[3]); } } @@ -211,7 +211,7 @@ function fairify(scurve, gizmo, denseQ, cleanMore) { var splitpoints = [scurve[0]]; var last = scurve[0]; for (var j = 1; j < scurve.length; j++) { - if (scurve[j].onCurve) { + if (scurve[j].on) { splitpoints.push(last = scurve[j]); } else if (scurve[j].cubic) { var z1 = last, @@ -238,13 +238,13 @@ function fairify(scurve, gizmo, denseQ, cleanMore) { } // Mark corners and extrema for (var j = 1; j < splitpoints.length - 1; j++) { - if (splitpoints[j].onCurve && !splitpoints[j - 1].onCurve) { + if (splitpoints[j].on && !splitpoints[j - 1].on) { splitpoints[j].prev = splitpoints[j - 1]; } - if (splitpoints[j].onCurve && !splitpoints[j + 1].onCurve) { + if (splitpoints[j].on && !splitpoints[j + 1].on) { splitpoints[j].next = splitpoints[j + 1]; } - if (splitpoints[j].onCurve && !splitpoints[j - 1].onCurve && !splitpoints[j + 1].onCurve) { + if (splitpoints[j].on && !splitpoints[j - 1].on && !splitpoints[j + 1].on) { var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1]; @@ -272,7 +272,7 @@ function fairify(scurve, gizmo, denseQ, cleanMore) { } else { z1.mark = true; // also corner } - } else if (splitpoints[j].onCurve) { + } else if (splitpoints[j].on) { splitpoints[j].mark = true; // corner } } @@ -312,7 +312,7 @@ function fairify(scurve, gizmo, denseQ, cleanMore) { nextmark = splitpoints[k]; segments = estimateSegments(lastmark, nextmark); } - if (splitpoints[j].onCurve && !splitpoints[j].mark) { + if (splitpoints[j].on && !splitpoints[j].mark) { var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1]; @@ -327,12 +327,12 @@ function fairify(scurve, gizmo, denseQ, cleanMore) { } } // Rebuild curve - for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].onCurve && !splitpoints[j].remove && splitpoints[j + 1] && !splitpoints[j + 1].onCurve) { + for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].on && !splitpoints[j].remove && splitpoints[j + 1] && !splitpoints[j + 1].on) { for (var k = j + 2; k < splitpoints.length && splitpoints[k].remove; k++); if (k - j > 2) { var zs = fitpts(splitpoints[j], splitpoints[j + 1], splitpoints[k], splitpoints[k + 1]); if (zs) { - zs[0].onCurve = zs[1].onCurve = false; + zs[0].on = zs[1].on = false; zs[0].cubic = zs[1].cubic = true; splitpoints[j + 1] = zs[0]; splitpoints[k] = zs[1]; diff --git a/support/glyph.ptl b/support/glyph.ptl index 9514e14..870e067 100644 --- a/support/glyph.ptl +++ b/support/glyph.ptl @@ -25,7 +25,7 @@ define [closepoint p q t] : begin define [oncurveRemovable a b c t] : begin local xm : (a.x + c.x) / 2 local ym : (a.y + c.y) / 2 - return : [not a.onCurve] && b.onCurve && [not c.onCurve] && [not a.cubic] && [not c.cubic] && (a.x <= b.x && b.x <= c.x || a.x >= b.x && b.x >= c.x) && (a.y <= b.y && b.y <= c.y || a.y >= b.y && b.y >= c.y) && [Math.abs (b.x - xm)] <= (t / 2) && [Math.abs (b.y - ym)] <= (t / 2) + return : [not a.on] && b.on && [not c.on] && [not a.cubic] && [not c.cubic] && (a.x <= b.x && b.x <= c.x || a.x >= b.x && b.x >= c.x) && (a.y <= b.y && b.y <= c.y || a.y >= b.y && b.y >= c.y) && [Math.abs (b.x - xm)] <= (t / 2) && [Math.abs (b.y - ym)] <= (t / 2) define PRECISION 1000 define [cov x] : piecewise @@ -154,7 +154,7 @@ export all : class Glyph local c {} set c.tag : contour.tag || component.tag || t.defaultTag foreach [point : items-of contour] : begin - c.push : new Point (point.x + shiftx) (point.y + shifty) point.onCurve point.cubic point.subdivided + c.push : new Point (point.x + shiftx) (point.y + shifty) point.on point.cubic point.subdivided newcontours.push c set this.contours : this.contours.concat newcontours if (([not contours] || copyAnchors) && glyph.anchors) : begin @@ -187,7 +187,7 @@ export all : class Glyph for [local j 1] (j < contour.length) [inc j] : begin local point contour.(j) piecewise - point.onCurve : begin + point.on : begin if delta : then : set buf : buf + "l \[cov : point.x - lx] \[cov : point.y - ly]" : else : set buf : buf + "L \[cov point.x] \[cov point.y]" @@ -215,16 +215,52 @@ export all : class Glyph inc j set buf : buf + " Z\n" return buf + + static [contourToStandardCubic contour] : begin + local c {} + if (!contour || !contour.length) : return c + + local lx contour.0.x + local ly contour.0.y + c.push {.x lx .y ly .on true} + for [local j 1] (j < contour.length) [inc j] : begin + local point contour.(j) + piecewise + point.on : begin + c.push {.x point.x .y point.y .on true} + set {.x lx .y ly} point + point.cubic : begin + local z1 point + local z2 contour.(j + 1) + local z3 contour.(j + 2) + c.push {.x z1.x .y z1.y .on false} + c.push {.x z2.x .y z2.y .on false} + c.push {.x z3.x .y z3.y .on true} + set {.x lx .y ly} z3 + set j : j + 2 + true : begin + local zc point + local zf : if contour.(j + 1) contour.(j + 1) contour.0 + local x1 : mix lx zc.x (2 / 3) + local y1 : mix ly zc.y (2 / 3) + local x2 : mix zf.x zc.x (2 / 3) + local y2 : mix zf.y zc.y (2 / 3) + c.push {.x x1 .y y1 .on false} + c.push {.x x2 .y y2 .on false} + c.push {.x zf.x .y zf.y .on true} + set {.x lx .y ly} zf + inc j + return c public [cleanup t] : begin foreach c [range 0 this.contours.length] : begin local ocontour this.contours.(c) # add infections - local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.onCurve]} + local contour {[new Point ocontour.0.x ocontour.0.y ocontour.0.on]} local flag 0 foreach [j : range 1 (ocontour.length - 1)] : piecewise flag : dec flag - ocontour.(j).onCurve : contour.push ocontour.(j) + ocontour.(j).on : contour.push ocontour.(j) ocontour.(j).cubic : begin local p0 contour.(contour.length - 1) local p1 ocontour.(j) @@ -249,7 +285,7 @@ export all : class Glyph local p0 contour.(contour.length - 1) local p1 ocontour.(j) local p2 ocontour.(j + 1) - if [not p2.onCurve] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true + if [not p2.on] : set p2 : new Point [mix p1.x p2.x 0.5] [mix p1.y p2.y 0.5] true local strand : new Bezier p0.x p0.y p1.x p1.y p2.x p2.y local ts [strand.extrema].y piecewise @@ -265,7 +301,7 @@ export all : class Glyph new Point s.points.2.x s.points.2.y true set flag 1 - contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).onCurve] + contour.push [new Point ocontour.(ocontour.length - 1).x ocontour.(ocontour.length - 1).y ocontour.(ocontour.length - 1).on] # cleanup local cleanedContour {} @@ -296,7 +332,7 @@ export all : class Glyph set contour.(k + 2).unimportant true set k : k + 2 if found : begin - set contour.(j).onCurve true + set contour.(j).on true set j (k - 1) foreach point [items-of contour] : if [not point.unimportant] : cleanedContour.push point this.contours.(c) = cleanedContour diff --git a/support/point.ptl b/support/point.ptl index 95acae9..df9e445 100644 --- a/support/point.ptl +++ b/support/point.ptl @@ -1,14 +1,14 @@ export all : class Point - public [new x y onCurve cubic subdivided] : begin + public [new x y on cubic subdivided] : begin this.x = x this.y = y - this.onCurve = onCurve || false + this.on = on || false this.subdivided = subdivided || false this.cubic = cubic || false - static [transformed tfm x y onCurve cubic subdivided] : new Point + static [transformed tfm x y on cubic subdivided] : new Point * x * tfm.xx + y * tfm.yx + tfm.x * x * tfm.xy + y * tfm.yy + tfm.y - * onCurve + * on * cubic * subdivided \ No newline at end of file diff --git a/support/transform.ptl b/support/transform.ptl index 82785d2..89d6691 100644 --- a/support/transform.ptl +++ b/support/transform.ptl @@ -14,7 +14,7 @@ export all : class Transform static [transformPoint tfm pt] : new Point * pt.x * tfm.xx + pt.y * tfm.yx + tfm.x * pt.x * tfm.xy + pt.y * tfm.yy + tfm.y - * pt.onCurve + * pt.on * pt.cubic * pt.subdivided static [inverse tfm] : begin @@ -31,6 +31,6 @@ export all : class Transform return : new Point * (xx * tfm.yy - yy * tfm.yx) / denom * (yy * tfm.xx - xx * tfm.xy) / denom - * pt.onCurve + * pt.on * pt.cubic * pt.subdivided \ No newline at end of file