197 lines
5.9 KiB
JavaScript
197 lines
5.9 KiB
JavaScript
/*
|
|
* 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}})
|
|
);
|
|
`;
|
|
},
|
|
''));
|