use a "smart rounding" algorithm to handle rounding when reducing upm.
This commit is contained in:
parent
7c77c8182d
commit
f3ae40b75d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
8
makefile
8
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)
|
||||
|
|
|
@ -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"))
|
74
pass2-smartround.js
Normal file
74
pass2-smartround.js
Normal file
|
@ -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)));
|
12
pass3-finalize.py
Normal file
12
pass3-finalize.py
Normal file
|
@ -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"))
|
|
@ -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 ()
|
||||
|
|
|
@ -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)
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue
Block a user