OpenType / opentype-variations

OpenType Font Variations proposals
5 stars 0 forks source link

Duplicate axes in one region: Make variable quantities (coordinates, metrics) a ring #3

Closed be5invis closed 5 years ago

be5invis commented 5 years ago

NOTE: To understand this you need basic abstract algebra knowledge.

Currently, point coordinates or metrics in a variable font (we call these values "Variable Quantity", abbr. VQ) can be proved forming a module, where you can define addition and scalar multiplication of VQs.

However, currently you cannot define multiplication of VQs (which means that VQs do not form a ring), which may cause problem when manipulating complex fonts.

However, by extending the current definition of variation region, we can achieve this and enable non-linear variations with minimal changes of the interpolation logic.

Status Quo

Mathematically, a VQ could be defined in this form:

where:

From the mathematical form we can easily prove that VQs form a module and we can define the addition and scalar multiplication as follows:

However we cannot define multiplication of VQs, since we may produce terms like in the product, and the old definiton of W does not support factors like this: in the original W definition, each region can only assign one "tent" to one axis.

Purposed change

We will change the definition of variation regions: each region will be able to associate multiple "tent"s to one region, the W function will be redefined as follows:

where:

Allowing multiple tents associated to one axis gives us the ability to define the product of two VQs:

where

DISCLAIMER: This is NOT a proposal from Microsoft, just from me.

be5invis commented 5 years ago

Purposed data type:

interface VQ {
    neutral: number;
    variant: Array<[Region, number]>;
}
type Region = Array<[AxisTag, Tent]>;
interface Tent {
    min: number;
    peak: number;
    max: number;
}

Old Region type definition

type RegionOld = Map<AxisTag, Tent>;

Evaluation algorithm

function evalVQ(vq: VQ, instance: Map<AxisTag, number>){
    let result = vq.neutral;
    for(const [region, delta] of vq.variant) {
        result += delta * weightRegion(region, instance);
    }
    return result;
}
function weightRegion(region: Region, instance: Map<AxisTag, number>) {
    let weight = 1;
    for(const [axis, tent] of region) {
        weight *= weightTent(tent, instance.get(axis) || 0);
    }
    return weight;
}
function weightTent(t: Tent, coord: number) { ... }

Scaling algorithm.

function scale(scalar: number, vq: VQ) {
    return {
        neutral: vq.neutral * scalar,
        variant: vq.variant.map(([region, delta]) => [region, delta * scalar])
    };
}

Sum algorithm.

function add(a: VQ, b: VQ) {
    return { neutral: a.neutral + b.neutral, variant: [...a.variant, ...b.variant] };
}

Multiplication algorithm.

function multiply(a: VQ, b: VQ) {
    let variant : [Region, number][] = [];
    for(const [r, d] of a.variant) variant.push([r, d * b.neutral]);
    for(const [r, d] of b.variant) variant.push([r, d * a.neutral]);
    for(const [ra, da] of a.variant) for(const [rb, db] of b.variant) {
        variant.push([[...ra, ...rb], da * db]);
    }
    return { neutral: a.neutral * b.neutral, variant };
}
be5invis commented 5 years ago

Close as AVAR plus "duplicate axes" may partially solve this, and we may not need a real multiplication.