diff --git a/glyphs/cyrillic-basic.patel b/glyphs/cyrillic-basic.patel index b00902e..29ae6dd 100644 --- a/glyphs/cyrillic-basic.patel +++ b/glyphs/cyrillic-basic.patel @@ -48,11 +48,11 @@ define [CyrYeriShape top _left _right _fine] : glyph-construction { include : spiro { widths.lhs fine flat [left + STROKE * 0.2] 0 [heading RIGHTWARD] - curl [right - turnbottom * [right - left] / [RIGHTSB - SB]] 0 - archv + curl [right - turnbottom * [right - left] / [RIGHTSB - SB]] 0 [heading RIGHTWARD] + archv 8 g4 [right - O] turnbottom - arcvh - flat [right - turnbottom * [right - left] / [RIGHTSB - SB]] bowl + arcvh 8 + flat [right - turnbottom * [right - left] / [RIGHTSB - SB]] bowl [heading LEFTWARD] curl [left + STROKE * 0.2] bowl [heading LEFTWARD] } include : VBarLeft left 0 top fine diff --git a/glyphs/latin-extend-basis.patel b/glyphs/latin-extend-basis.patel index 0172823..514185a 100644 --- a/glyphs/latin-extend-basis.patel +++ b/glyphs/latin-extend-basis.patel @@ -100,16 +100,13 @@ create-glyph 'aeapart' : glyph-construction { local sma : SMALLSMOOTHA * 0.6 local smb : SMALLSMOOTHB * 0.6 - - include : spiro { - widths.lhs MVERTSTROKE - g4 abarRight [XH - smb] - hookend XO 0.5 - g4 [SB + O] [XH - SHOOK] - } + include : spiro { widths.rhs MVERTSTROKE - g4 abarRight smb + g4 [SB + O] [XH - SHOOK] + hookstart XO 0.5 + flat abarRight [XH - smb] + curl abarRight smb arcvh g4 lowmiddle O archv diff --git a/makefile b/makefile index 881560b..d29ee97 100644 --- a/makefile +++ b/makefile @@ -13,6 +13,8 @@ PASS0 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS)) ABFEAT = $(subst .ttf,.ab.fea,$(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS))) FEATURE = $(subst .ttf,.fea,$(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS))) PASS1 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass1-,$(TARGETS)) +PASS2 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass2-,$(TARGETS)) +PASS3 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass3-,$(TARGETS)) FILES = $(SUPPORT_FILES) buildglyphs.js @@ -45,7 +47,11 @@ $(FEATURE) : $(OBJDIR)/.pass0-%.fea : $(OBJDIR)/.pass0-%.ab.fea features/common. # Pass 1 : Outline cleanup and merge $(PASS1) : $(OBJDIR)/.pass1-%.ttf : $(OBJDIR)/.pass0-%.ttf $(OBJDIR)/.pass0-%.fea fontforge -quiet -script pass1-cleanup.py $^ $@ $(SUPPRESS_ERRORS) -$(TARGETS) : $(OBJDIR)/%.ttf : $(OBJDIR)/.pass1-%.ttf +$(PASS2) : $(OBJDIR)/.pass2-%.ttf : $(OBJDIR)/.pass1-%.ttf + node pass2-smartround.js $^ $@ --upm $(TARGETUPM) +$(PASS3) : $(OBJDIR)/.pass3-%.ttf : $(OBJDIR)/.pass2-%.ttf + fontforge -quiet -script pass3-finalize.py $^ $@ $(TARGETUPM) +$(TARGETS) : $(OBJDIR)/%.ttf : $(OBJDIR)/.pass3-%.ttf ttfautohint $< $@ update : $(FILES) diff --git a/pass1-cleanup.py b/pass1-cleanup.py index 88893ef..94f0564 100644 --- a/pass1-cleanup.py +++ b/pass1-cleanup.py @@ -5,8 +5,9 @@ import sys source = sys.argv[1] font = fontforge.open(source) -font.mergeFeature(sys.argv[2]) + # Replace accented characters into references +print "Reference finding: ", font.fontname font.selection.select(("ranges", "unicode", None), 0x1FCD, 0x1FCF, 0x1FDD, 0x1FDF) font.replaceWithReference(4) font.selection.none() @@ -16,6 +17,9 @@ font.selection.none() font.selection.select(("ranges", "unicode", None), 0x0000, 0xFFFF) font.replaceWithReference(4) font.selection.none() + +# Remove overlapped area +print "Overlap Removal: ", font.fontname font.selection.all() font.removeOverlap() font.round() @@ -26,18 +30,28 @@ for i in font: glyph.unlinkRef() glyph.removeOverlap() +# Outline simplify +print "Simplify, pass 1: ", font.fontname +font.simplify(1) font.layers["Fore"].is_quadratic = False font.selection.all() -font.simplify(font.em / 1000.0, ("smoothcurves"), 0.05) -font.transform(psMat.skew(-font.italicangle / 180 * math.pi)) -# convert cubic outlines to quadratic under 1000upm +font.simplify(font.em / 1000.0 * 0.5, ("smoothcurves", "choosehv"), 0.1); + +print "Simplify, pass 2: ", font.fontname oldem = font.em font.em = 1000 +font.round() +font.simplify(0.25) +font.transform(psMat.skew(-font.italicangle / 180 * math.pi)) for i in font: font[i].addExtrema(("all")) +font.simplify(1, ("smoothcurves"), 0.05) font.layers["Fore"].is_quadratic = True -font.round() -font.simplify(1) + +print "Finalize: ", font.fontname +font.em = oldem +font.mergeFeature(sys.argv[2]) + font.canonicalContours() font.canonicalStart() font.generate(sys.argv[3], flags = ("short-post", "opentype")) \ No newline at end of file diff --git a/pass2-smartround.js b/pass2-smartround.js new file mode 100644 index 0000000..50c879d --- /dev/null +++ b/pass2-smartround.js @@ -0,0 +1,74 @@ +var fs = require('fs'); +var TTFReader = require('node-sfnt').TTFReader; +var TTFWriter = require('node-sfnt').TTFWriter; + +var argv = require('yargs').argv; + +function toArrayBuffer(buffer) { + var length = buffer.length; + var view = new DataView(new ArrayBuffer(length), 0, length); + for (var i = 0, l = length; i < l; i++) { + view.setUint8(i, buffer[i], false); + } + return view.buffer; +} +function toBuffer(arrayBuffer) { + var length = arrayBuffer.byteLength; + var view = new DataView(arrayBuffer, 0, length); + var buffer = new Buffer(length); + for (var i = 0, l = length; i < l; i++) { + buffer[i] = view.getUint8(i, false); + } + return buffer; +} + +var options = {preserveOS2Version: true, preserveXAvgCharWidth: true, writeUnknownTables: true, hinting: true} + +function readttf(file) { + var data = fs.readFileSync(file); + var buffer = toArrayBuffer(data); + var ttf = (new TTFReader(options)).read(buffer); + return ttf; +} + +function writettf(ttf, file){ + var buffer = new TTFWriter(options).write(ttf); + fs.writeFileSync(file, toBuffer(buffer)); +} +function mix(a, b, p){ return a + (b - a) * p } +function ratio(l, m, r){ + if(l === r) return 0; + return (m - l) / (r - l); +} +function colinear(a, b, c){ + return Math.abs(((c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y))) <= 1e-5 +} +var ttf = readttf(argv._[0]); +var targetupm = argv.upm - 0 || 1000; +var upm = ttf.head.unitsPerEm - 0; +var skew = Math.tan(ttf.post.italicAngle / 180 * Math.PI); +for(var j = 0; j < ttf.glyf.length; j++){ + var glyph = ttf.glyf[j]; + if(glyph.contours && glyph.contours.length > 0) for(var k = 0; k < glyph.contours.length; k++) if(glyph.contours[k].length > 0) { + var c = glyph.contours[k]; + for(var l = 0; l < c.length; l++){ + c[l].x += skew * c[l].y + } + var xs = c.map(function(p){ return p.x }); + var ys = c.map(function(p){ return p.y }); + var xmin = Math.min.apply(null, xs); + var xmax = Math.max.apply(null, xs); + var ymin = Math.min.apply(null, ys); + var ymax = Math.max.apply(null, ys); + var rxmin = (upm / targetupm) * Math.round(xmin * targetupm / upm); + var rxmax = (upm / targetupm) * Math.round(xmax * targetupm / upm); + var rymin = (upm / targetupm) * Math.round(ymin * targetupm / upm); + var rymax = (upm / targetupm) * Math.round(ymax * targetupm / upm); + for(var l = 0; l < c.length; l++){ + c[l].y = (upm / targetupm) * Math.round(mix(rymin, rymax, ratio(ymin, c[l].y, ymax)) * targetupm / upm) + c[l].x = (upm / targetupm) * Math.round((mix(rxmin, rxmax, ratio(xmin, c[l].x, xmax)) - c[l].y * skew) * targetupm / upm) + } + glyph.contours[k] = c.filter(function(p){ return !p.removable }) + } +} +fs.writeFileSync(argv._[1], toBuffer(new TTFWriter(options).write(ttf))); \ No newline at end of file diff --git a/pass3-finalize.py b/pass3-finalize.py new file mode 100644 index 0000000..ce733dc --- /dev/null +++ b/pass3-finalize.py @@ -0,0 +1,12 @@ +import fontforge +import psMat +import sys +import math + +source = sys.argv[1] +font = fontforge.open(source) +font.em = int(sys.argv[3]) +font.selection.all() +font.round() +font.simplify(0.1) +font.generate(sys.argv[2], flags = ("short-post", "opentype")) \ No newline at end of file diff --git a/support/glyph.patel b/support/glyph.patel index 9870d0c..2263563 100644 --- a/support/glyph.patel +++ b/support/glyph.patel @@ -80,9 +80,7 @@ define [Glyph.prototype.cubic-to x1 y1 x2 y2 x y] : begin { local lastContour this.contours`[this.contours.length - 1] local lastPoint lastContour`[lastContour.length - 1] local segments : bezierCubic2Q2 lastPoint [tp this.gizmo (.x x1 .y y1)] [tp this.gizmo (.x x2 .y y2)] [tp this.gizmo (.x x .y y)] - foreach (p0 (.x xc .y yc) (.x xf .y yf)) [items-of segments] : lastContour.push (.x xc .y yc) #(.x xf .y yf .onCurve true) - local final segments.[segments.length - 1].2 - lastContour.push (.x final.x .y final.y .onCurve true) + foreach (p0 (.x xc .y yc) (.x xf .y yf)) [items-of segments] : lastContour.push (.x xc .y yc) (.x xf .y yf .onCurve true) return this } Glyph.prototype.cubicTo = Glyph.prototype.cubic-to @@ -196,6 +194,7 @@ define [oncurveRemovable a b c] : begin { return : [not a.onCurve] && b.onCurve && [not c.onCurve] && [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]] <= 0.5 && [Math.abs [b.y - ym]] <= 0.5 } define [Glyph.prototype.cleanup] : begin { + return this foreach c [range 0 this.contours.length] : begin { local contour this.contours.(c) local cleanedContour () diff --git a/support/toquad.js b/support/toquad.js index 63bbbc4..4da86ec 100644 --- a/support/toquad.js +++ b/support/toquad.js @@ -45,7 +45,7 @@ function bezierCubic2Q2(p1, c1, c2, p2, level) { var my = p2.y - 3 * c2.y + 3 * c1.y - p1.y; // control points near - if (mx * mx + my * my <= 1 || level > 5) { + if (mx * mx + my * my <= 4 || level > 5) { return [ toQuad(p1, c1, c2, p2) ];