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 {
|
include : spiro {
|
||||||
widths.lhs fine
|
widths.lhs fine
|
||||||
flat [left + STROKE * 0.2] 0 [heading RIGHTWARD]
|
flat [left + STROKE * 0.2] 0 [heading RIGHTWARD]
|
||||||
curl [right - turnbottom * [right - left] / [RIGHTSB - SB]] 0
|
curl [right - turnbottom * [right - left] / [RIGHTSB - SB]] 0 [heading RIGHTWARD]
|
||||||
archv
|
archv 8
|
||||||
g4 [right - O] turnbottom
|
g4 [right - O] turnbottom
|
||||||
arcvh
|
arcvh 8
|
||||||
flat [right - turnbottom * [right - left] / [RIGHTSB - SB]] bowl
|
flat [right - turnbottom * [right - left] / [RIGHTSB - SB]] bowl [heading LEFTWARD]
|
||||||
curl [left + STROKE * 0.2] bowl [heading LEFTWARD]
|
curl [left + STROKE * 0.2] bowl [heading LEFTWARD]
|
||||||
}
|
}
|
||||||
include : VBarLeft left 0 top fine
|
include : VBarLeft left 0 top fine
|
||||||
|
|
|
@ -101,15 +101,12 @@ create-glyph 'aeapart' : glyph-construction {
|
||||||
local sma : SMALLSMOOTHA * 0.6
|
local sma : SMALLSMOOTHA * 0.6
|
||||||
local smb : SMALLSMOOTHB * 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 {
|
include : spiro {
|
||||||
widths.rhs MVERTSTROKE
|
widths.rhs MVERTSTROKE
|
||||||
g4 abarRight smb
|
g4 [SB + O] [XH - SHOOK]
|
||||||
|
hookstart XO 0.5
|
||||||
|
flat abarRight [XH - smb]
|
||||||
|
curl abarRight smb
|
||||||
arcvh
|
arcvh
|
||||||
g4 lowmiddle O
|
g4 lowmiddle O
|
||||||
archv
|
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)))
|
ABFEAT = $(subst .ttf,.ab.fea,$(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS)))
|
||||||
FEATURE = $(subst .ttf,.fea,$(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS)))
|
FEATURE = $(subst .ttf,.fea,$(subst $(OBJDIR)/,$(OBJDIR)/.pass0-,$(TARGETS)))
|
||||||
PASS1 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass1-,$(TARGETS))
|
PASS1 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass1-,$(TARGETS))
|
||||||
|
PASS2 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass2-,$(TARGETS))
|
||||||
|
PASS3 = $(subst $(OBJDIR)/,$(OBJDIR)/.pass3-,$(TARGETS))
|
||||||
|
|
||||||
FILES = $(SUPPORT_FILES) buildglyphs.js
|
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
|
# Pass 1 : Outline cleanup and merge
|
||||||
$(PASS1) : $(OBJDIR)/.pass1-%.ttf : $(OBJDIR)/.pass0-%.ttf $(OBJDIR)/.pass0-%.fea
|
$(PASS1) : $(OBJDIR)/.pass1-%.ttf : $(OBJDIR)/.pass0-%.ttf $(OBJDIR)/.pass0-%.fea
|
||||||
fontforge -quiet -script pass1-cleanup.py $^ $@ $(SUPPRESS_ERRORS)
|
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 $< $@
|
ttfautohint $< $@
|
||||||
|
|
||||||
update : $(FILES)
|
update : $(FILES)
|
||||||
|
|
|
@ -5,8 +5,9 @@ import sys
|
||||||
|
|
||||||
source = sys.argv[1]
|
source = sys.argv[1]
|
||||||
font = fontforge.open(source)
|
font = fontforge.open(source)
|
||||||
font.mergeFeature(sys.argv[2])
|
|
||||||
# Replace accented characters into references
|
# Replace accented characters into references
|
||||||
|
print "Reference finding: ", font.fontname
|
||||||
font.selection.select(("ranges", "unicode", None), 0x1FCD, 0x1FCF, 0x1FDD, 0x1FDF)
|
font.selection.select(("ranges", "unicode", None), 0x1FCD, 0x1FCF, 0x1FDD, 0x1FDF)
|
||||||
font.replaceWithReference(4)
|
font.replaceWithReference(4)
|
||||||
font.selection.none()
|
font.selection.none()
|
||||||
|
@ -16,6 +17,9 @@ font.selection.none()
|
||||||
font.selection.select(("ranges", "unicode", None), 0x0000, 0xFFFF)
|
font.selection.select(("ranges", "unicode", None), 0x0000, 0xFFFF)
|
||||||
font.replaceWithReference(4)
|
font.replaceWithReference(4)
|
||||||
font.selection.none()
|
font.selection.none()
|
||||||
|
|
||||||
|
# Remove overlapped area
|
||||||
|
print "Overlap Removal: ", font.fontname
|
||||||
font.selection.all()
|
font.selection.all()
|
||||||
font.removeOverlap()
|
font.removeOverlap()
|
||||||
font.round()
|
font.round()
|
||||||
|
@ -26,18 +30,28 @@ for i in font:
|
||||||
glyph.unlinkRef()
|
glyph.unlinkRef()
|
||||||
glyph.removeOverlap()
|
glyph.removeOverlap()
|
||||||
|
|
||||||
|
# Outline simplify
|
||||||
|
print "Simplify, pass 1: ", font.fontname
|
||||||
|
font.simplify(1)
|
||||||
font.layers["Fore"].is_quadratic = False
|
font.layers["Fore"].is_quadratic = False
|
||||||
font.selection.all()
|
font.selection.all()
|
||||||
font.simplify(font.em / 1000.0, ("smoothcurves"), 0.05)
|
font.simplify(font.em / 1000.0 * 0.5, ("smoothcurves", "choosehv"), 0.1);
|
||||||
font.transform(psMat.skew(-font.italicangle / 180 * math.pi))
|
|
||||||
# convert cubic outlines to quadratic under 1000upm
|
print "Simplify, pass 2: ", font.fontname
|
||||||
oldem = font.em
|
oldem = font.em
|
||||||
font.em = 1000
|
font.em = 1000
|
||||||
|
font.round()
|
||||||
|
font.simplify(0.25)
|
||||||
|
font.transform(psMat.skew(-font.italicangle / 180 * math.pi))
|
||||||
for i in font:
|
for i in font:
|
||||||
font[i].addExtrema(("all"))
|
font[i].addExtrema(("all"))
|
||||||
|
font.simplify(1, ("smoothcurves"), 0.05)
|
||||||
font.layers["Fore"].is_quadratic = True
|
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.canonicalContours()
|
||||||
font.canonicalStart()
|
font.canonicalStart()
|
||||||
font.generate(sys.argv[3], flags = ("short-post", "opentype"))
|
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 lastContour this.contours`[this.contours.length - 1]
|
||||||
local lastPoint lastContour`[lastContour.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)]
|
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)
|
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)
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
Glyph.prototype.cubicTo = Glyph.prototype.cubic-to
|
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
|
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 {
|
define [Glyph.prototype.cleanup] : begin {
|
||||||
|
return this
|
||||||
foreach c [range 0 this.contours.length] : begin {
|
foreach c [range 0 this.contours.length] : begin {
|
||||||
local contour this.contours.(c)
|
local contour this.contours.(c)
|
||||||
local cleanedContour ()
|
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;
|
var my = p2.y - 3 * c2.y + 3 * c1.y - p1.y;
|
||||||
|
|
||||||
// control points near
|
// control points near
|
||||||
if (mx * mx + my * my <= 1 || level > 5) {
|
if (mx * mx + my * my <= 4 || level > 5) {
|
||||||
return [
|
return [
|
||||||
toQuad(p1, c1, c2, p2)
|
toQuad(p1, c1, c2, p2)
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user