use a "smart rounding" algorithm to handle rounding when reducing upm.

This commit is contained in:
be5invis 2015-08-28 17:41:13 +08:00
parent 7c77c8182d
commit f3ae40b75d
8 changed files with 125 additions and 23 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
View 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
View 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"))

View File

@ -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 ()

View File

@ -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)
];