blob: 59fd2e69764b38fd241cd655d56f0119c48e7545 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
Generates arrays for non-linear font scaling, to be pasted into
frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java
To use:
`node font-scaling-array-generator.js`
or just open a browser, open DevTools, and paste into the Console.
*/
/**
* Modify this to match your
* frameworks/base/packages/SettingsLib/res/values/arrays.xml#entryvalues_font_size
* array so that all possible scales are generated.
*/
const scales = [1.15, 1.30, 1.5, 1.8, 2];
const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100];
/**
* Enum for GENERATION_STYLE which determines how to generate the arrays.
*/
const GenerationStyle = {
/**
* Interpolates between hand-tweaked curves. This is the best option and
* shouldn't require any additional tweaking.
*/
CUSTOM_TWEAKED: 'CUSTOM_TWEAKED',
/**
* Uses a curve equation that is mostly correct, but will need manual tweaking
* at some scales.
*/
CURVE: 'CURVE',
/**
* Uses straight linear multiplication. Good starting point for manual
* tweaking.
*/
LINEAR: 'LINEAR'
}
/**
* Determines how arrays are generated. Must be one of the GenerationStyle
* values.
*/
const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED;
// These are hand-tweaked curves from which we will derive the other
// interstitial curves using linear interpolation, in the case of using
// GenerationStyle.CUSTOM_TWEAKED.
const interpolationTargets = {
1.0: commonSpSizes,
1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100],
2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100]
};
/**
* Interpolate a value with specified extrema, to a new value between new
* extrema.
*
* @param value the current value
* @param inputMin minimum the input value can reach
* @param inputMax maximum the input value can reach
* @param outputMin minimum the output value can reach
* @param outputMax maximum the output value can reach
*/
function map(value, inputMin, inputMax, outputMin, outputMax) {
return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin));
}
/***
* Interpolate between values a and b.
*/
function lerp(a, b, fraction) {
return (a * (1.0 - fraction)) + (b * fraction);
}
function generateRatios(scale) {
// Find the best two arrays to interpolate between.
let startTarget, endTarget;
let startTargetScale, endTargetScale;
const targetScales = Object.keys(interpolationTargets).sort();
for (let i = 0; i < targetScales.length - 1; i++) {
const targetScaleKey = targetScales[i];
const targetScale = parseFloat(targetScaleKey, 10);
const startTargetScaleKey = targetScaleKey;
const endTargetScaleKey = targetScales[i + 1];
if (scale < parseFloat(startTargetScaleKey, 10)) {
break;
}
startTargetScale = parseFloat(startTargetScaleKey, 10);
endTargetScale = parseFloat(endTargetScaleKey, 10);
startTarget = interpolationTargets[startTargetScaleKey];
endTarget = interpolationTargets[endTargetScaleKey];
}
const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1);
return commonSpSizes.map((sp, i) => {
const originalSizeDp = sp;
let newSizeDp;
switch (GENERATION_STYLE) {
case GenerationStyle.CUSTOM_TWEAKED:
newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress);
break;
case GenerationStyle.CURVE: {
let coeff1;
let coeff2;
if (scale < 1) {
// \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x
coeff1 = -5;
coeff2 = scale;
} else {
// (1.22^{-\left(x-10\right)}+1\right)\cdot x
coeff1 = map(scale, 1, 2, 2, 8);
coeff2 = 1;
}
newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp);
break;
}
case GenerationStyle.LINEAR:
newSizeDp = originalSizeDp * scale;
break;
default:
throw new Error('Invalid GENERATION_STYLE');
}
return {
fromSp: sp,
toDp: newSizeDp
}
});
}
const scaleArrays =
scales
.map(scale => {
const scaleString = (scale * 100).toFixed(0);
return {
scale,
name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent`
}
})
.map(scaleArray => {
const items = generateRatios(scaleArray.scale);
return {
...scaleArray,
items
}
});
function formatDigit(d) {
const twoSignificantDigits = Math.round(d * 100) / 100;
return String(twoSignificantDigits).padStart(4, ' ');
}
console.log(
'' +
scaleArrays.reduce(
(previousScaleArray, currentScaleArray) => {
const itemsFromSp = currentScaleArray.items.map(d => d.fromSp)
.map(formatDigit)
.join('f, ');
const itemsToDp = currentScaleArray.items.map(d => d.toDp)
.map(formatDigit)
.join('f, ');
return previousScaleArray + `
put(
/* scaleKey= */ ${currentScaleArray.scale}f,
new FontScaleConverter(
/* fromSp= */
new float[] {${itemsFromSp}},
/* toDp= */
new float[] {${itemsToDp}})
);
`;
},
''));