1*d57664e9SAndroid Build Coastguard Worker/* 2*d57664e9SAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project 3*d57664e9SAndroid Build Coastguard Worker * 4*d57664e9SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*d57664e9SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*d57664e9SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*d57664e9SAndroid Build Coastguard Worker * 8*d57664e9SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*d57664e9SAndroid Build Coastguard Worker * 10*d57664e9SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*d57664e9SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*d57664e9SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*d57664e9SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*d57664e9SAndroid Build Coastguard Worker * limitations under the License. 15*d57664e9SAndroid Build Coastguard Worker */ 16*d57664e9SAndroid Build Coastguard Worker 17*d57664e9SAndroid Build Coastguard Worker/** 18*d57664e9SAndroid Build Coastguard Worker Generates arrays for non-linear font scaling, to be pasted into 19*d57664e9SAndroid Build Coastguard Worker frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java 20*d57664e9SAndroid Build Coastguard Worker 21*d57664e9SAndroid Build Coastguard Worker To use: 22*d57664e9SAndroid Build Coastguard Worker `node font-scaling-array-generator.js` 23*d57664e9SAndroid Build Coastguard Worker or just open a browser, open DevTools, and paste into the Console. 24*d57664e9SAndroid Build Coastguard Worker*/ 25*d57664e9SAndroid Build Coastguard Worker 26*d57664e9SAndroid Build Coastguard Worker/** 27*d57664e9SAndroid Build Coastguard Worker * Modify this to match your 28*d57664e9SAndroid Build Coastguard Worker * frameworks/base/packages/SettingsLib/res/values/arrays.xml#entryvalues_font_size 29*d57664e9SAndroid Build Coastguard Worker * array so that all possible scales are generated. 30*d57664e9SAndroid Build Coastguard Worker */ 31*d57664e9SAndroid Build Coastguard Workerconst scales = [1.15, 1.30, 1.5, 1.8, 2]; 32*d57664e9SAndroid Build Coastguard Worker 33*d57664e9SAndroid Build Coastguard Workerconst commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100]; 34*d57664e9SAndroid Build Coastguard Worker 35*d57664e9SAndroid Build Coastguard Worker/** 36*d57664e9SAndroid Build Coastguard Worker * Enum for GENERATION_STYLE which determines how to generate the arrays. 37*d57664e9SAndroid Build Coastguard Worker */ 38*d57664e9SAndroid Build Coastguard Workerconst GenerationStyle = { 39*d57664e9SAndroid Build Coastguard Worker /** 40*d57664e9SAndroid Build Coastguard Worker * Interpolates between hand-tweaked curves. This is the best option and 41*d57664e9SAndroid Build Coastguard Worker * shouldn't require any additional tweaking. 42*d57664e9SAndroid Build Coastguard Worker */ 43*d57664e9SAndroid Build Coastguard Worker CUSTOM_TWEAKED: 'CUSTOM_TWEAKED', 44*d57664e9SAndroid Build Coastguard Worker 45*d57664e9SAndroid Build Coastguard Worker /** 46*d57664e9SAndroid Build Coastguard Worker * Uses a curve equation that is mostly correct, but will need manual tweaking 47*d57664e9SAndroid Build Coastguard Worker * at some scales. 48*d57664e9SAndroid Build Coastguard Worker */ 49*d57664e9SAndroid Build Coastguard Worker CURVE: 'CURVE', 50*d57664e9SAndroid Build Coastguard Worker 51*d57664e9SAndroid Build Coastguard Worker /** 52*d57664e9SAndroid Build Coastguard Worker * Uses straight linear multiplication. Good starting point for manual 53*d57664e9SAndroid Build Coastguard Worker * tweaking. 54*d57664e9SAndroid Build Coastguard Worker */ 55*d57664e9SAndroid Build Coastguard Worker LINEAR: 'LINEAR' 56*d57664e9SAndroid Build Coastguard Worker} 57*d57664e9SAndroid Build Coastguard Worker 58*d57664e9SAndroid Build Coastguard Worker/** 59*d57664e9SAndroid Build Coastguard Worker * Determines how arrays are generated. Must be one of the GenerationStyle 60*d57664e9SAndroid Build Coastguard Worker * values. 61*d57664e9SAndroid Build Coastguard Worker */ 62*d57664e9SAndroid Build Coastguard Workerconst GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED; 63*d57664e9SAndroid Build Coastguard Worker 64*d57664e9SAndroid Build Coastguard Worker// These are hand-tweaked curves from which we will derive the other 65*d57664e9SAndroid Build Coastguard Worker// interstitial curves using linear interpolation, in the case of using 66*d57664e9SAndroid Build Coastguard Worker// GenerationStyle.CUSTOM_TWEAKED. 67*d57664e9SAndroid Build Coastguard Workerconst interpolationTargets = { 68*d57664e9SAndroid Build Coastguard Worker 1.0: commonSpSizes, 69*d57664e9SAndroid Build Coastguard Worker 1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100], 70*d57664e9SAndroid Build Coastguard Worker 2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100] 71*d57664e9SAndroid Build Coastguard Worker}; 72*d57664e9SAndroid Build Coastguard Worker 73*d57664e9SAndroid Build Coastguard Worker/** 74*d57664e9SAndroid Build Coastguard Worker * Interpolate a value with specified extrema, to a new value between new 75*d57664e9SAndroid Build Coastguard Worker * extrema. 76*d57664e9SAndroid Build Coastguard Worker * 77*d57664e9SAndroid Build Coastguard Worker * @param value the current value 78*d57664e9SAndroid Build Coastguard Worker * @param inputMin minimum the input value can reach 79*d57664e9SAndroid Build Coastguard Worker * @param inputMax maximum the input value can reach 80*d57664e9SAndroid Build Coastguard Worker * @param outputMin minimum the output value can reach 81*d57664e9SAndroid Build Coastguard Worker * @param outputMax maximum the output value can reach 82*d57664e9SAndroid Build Coastguard Worker */ 83*d57664e9SAndroid Build Coastguard Workerfunction map(value, inputMin, inputMax, outputMin, outputMax) { 84*d57664e9SAndroid Build Coastguard Worker return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin)); 85*d57664e9SAndroid Build Coastguard Worker} 86*d57664e9SAndroid Build Coastguard Worker 87*d57664e9SAndroid Build Coastguard Worker/*** 88*d57664e9SAndroid Build Coastguard Worker * Interpolate between values a and b. 89*d57664e9SAndroid Build Coastguard Worker */ 90*d57664e9SAndroid Build Coastguard Workerfunction lerp(a, b, fraction) { 91*d57664e9SAndroid Build Coastguard Worker return (a * (1.0 - fraction)) + (b * fraction); 92*d57664e9SAndroid Build Coastguard Worker} 93*d57664e9SAndroid Build Coastguard Worker 94*d57664e9SAndroid Build Coastguard Workerfunction generateRatios(scale) { 95*d57664e9SAndroid Build Coastguard Worker // Find the best two arrays to interpolate between. 96*d57664e9SAndroid Build Coastguard Worker let startTarget, endTarget; 97*d57664e9SAndroid Build Coastguard Worker let startTargetScale, endTargetScale; 98*d57664e9SAndroid Build Coastguard Worker const targetScales = Object.keys(interpolationTargets).sort(); 99*d57664e9SAndroid Build Coastguard Worker for (let i = 0; i < targetScales.length - 1; i++) { 100*d57664e9SAndroid Build Coastguard Worker const targetScaleKey = targetScales[i]; 101*d57664e9SAndroid Build Coastguard Worker const targetScale = parseFloat(targetScaleKey, 10); 102*d57664e9SAndroid Build Coastguard Worker const startTargetScaleKey = targetScaleKey; 103*d57664e9SAndroid Build Coastguard Worker const endTargetScaleKey = targetScales[i + 1]; 104*d57664e9SAndroid Build Coastguard Worker 105*d57664e9SAndroid Build Coastguard Worker if (scale < parseFloat(startTargetScaleKey, 10)) { 106*d57664e9SAndroid Build Coastguard Worker break; 107*d57664e9SAndroid Build Coastguard Worker } 108*d57664e9SAndroid Build Coastguard Worker 109*d57664e9SAndroid Build Coastguard Worker startTargetScale = parseFloat(startTargetScaleKey, 10); 110*d57664e9SAndroid Build Coastguard Worker endTargetScale = parseFloat(endTargetScaleKey, 10); 111*d57664e9SAndroid Build Coastguard Worker startTarget = interpolationTargets[startTargetScaleKey]; 112*d57664e9SAndroid Build Coastguard Worker endTarget = interpolationTargets[endTargetScaleKey]; 113*d57664e9SAndroid Build Coastguard Worker } 114*d57664e9SAndroid Build Coastguard Worker const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1); 115*d57664e9SAndroid Build Coastguard Worker 116*d57664e9SAndroid Build Coastguard Worker return commonSpSizes.map((sp, i) => { 117*d57664e9SAndroid Build Coastguard Worker const originalSizeDp = sp; 118*d57664e9SAndroid Build Coastguard Worker let newSizeDp; 119*d57664e9SAndroid Build Coastguard Worker switch (GENERATION_STYLE) { 120*d57664e9SAndroid Build Coastguard Worker case GenerationStyle.CUSTOM_TWEAKED: 121*d57664e9SAndroid Build Coastguard Worker newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress); 122*d57664e9SAndroid Build Coastguard Worker break; 123*d57664e9SAndroid Build Coastguard Worker case GenerationStyle.CURVE: { 124*d57664e9SAndroid Build Coastguard Worker let coeff1; 125*d57664e9SAndroid Build Coastguard Worker let coeff2; 126*d57664e9SAndroid Build Coastguard Worker if (scale < 1) { 127*d57664e9SAndroid Build Coastguard Worker // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x 128*d57664e9SAndroid Build Coastguard Worker coeff1 = -5; 129*d57664e9SAndroid Build Coastguard Worker coeff2 = scale; 130*d57664e9SAndroid Build Coastguard Worker } else { 131*d57664e9SAndroid Build Coastguard Worker // (1.22^{-\left(x-10\right)}+1\right)\cdot x 132*d57664e9SAndroid Build Coastguard Worker coeff1 = map(scale, 1, 2, 2, 8); 133*d57664e9SAndroid Build Coastguard Worker coeff2 = 1; 134*d57664e9SAndroid Build Coastguard Worker } 135*d57664e9SAndroid Build Coastguard Worker newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp); 136*d57664e9SAndroid Build Coastguard Worker break; 137*d57664e9SAndroid Build Coastguard Worker } 138*d57664e9SAndroid Build Coastguard Worker case GenerationStyle.LINEAR: 139*d57664e9SAndroid Build Coastguard Worker newSizeDp = originalSizeDp * scale; 140*d57664e9SAndroid Build Coastguard Worker break; 141*d57664e9SAndroid Build Coastguard Worker default: 142*d57664e9SAndroid Build Coastguard Worker throw new Error('Invalid GENERATION_STYLE'); 143*d57664e9SAndroid Build Coastguard Worker } 144*d57664e9SAndroid Build Coastguard Worker return { 145*d57664e9SAndroid Build Coastguard Worker fromSp: sp, 146*d57664e9SAndroid Build Coastguard Worker toDp: newSizeDp 147*d57664e9SAndroid Build Coastguard Worker } 148*d57664e9SAndroid Build Coastguard Worker }); 149*d57664e9SAndroid Build Coastguard Worker} 150*d57664e9SAndroid Build Coastguard Worker 151*d57664e9SAndroid Build Coastguard Workerconst scaleArrays = 152*d57664e9SAndroid Build Coastguard Worker scales 153*d57664e9SAndroid Build Coastguard Worker .map(scale => { 154*d57664e9SAndroid Build Coastguard Worker const scaleString = (scale * 100).toFixed(0); 155*d57664e9SAndroid Build Coastguard Worker return { 156*d57664e9SAndroid Build Coastguard Worker scale, 157*d57664e9SAndroid Build Coastguard Worker name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent` 158*d57664e9SAndroid Build Coastguard Worker } 159*d57664e9SAndroid Build Coastguard Worker }) 160*d57664e9SAndroid Build Coastguard Worker .map(scaleArray => { 161*d57664e9SAndroid Build Coastguard Worker const items = generateRatios(scaleArray.scale); 162*d57664e9SAndroid Build Coastguard Worker 163*d57664e9SAndroid Build Coastguard Worker return { 164*d57664e9SAndroid Build Coastguard Worker ...scaleArray, 165*d57664e9SAndroid Build Coastguard Worker items 166*d57664e9SAndroid Build Coastguard Worker } 167*d57664e9SAndroid Build Coastguard Worker }); 168*d57664e9SAndroid Build Coastguard Worker 169*d57664e9SAndroid Build Coastguard Workerfunction formatDigit(d) { 170*d57664e9SAndroid Build Coastguard Worker const twoSignificantDigits = Math.round(d * 100) / 100; 171*d57664e9SAndroid Build Coastguard Worker return String(twoSignificantDigits).padStart(4, ' '); 172*d57664e9SAndroid Build Coastguard Worker} 173*d57664e9SAndroid Build Coastguard Worker 174*d57664e9SAndroid Build Coastguard Workerconsole.log( 175*d57664e9SAndroid Build Coastguard Worker '' + 176*d57664e9SAndroid Build Coastguard Worker scaleArrays.reduce( 177*d57664e9SAndroid Build Coastguard Worker (previousScaleArray, currentScaleArray) => { 178*d57664e9SAndroid Build Coastguard Worker const itemsFromSp = currentScaleArray.items.map(d => d.fromSp) 179*d57664e9SAndroid Build Coastguard Worker .map(formatDigit) 180*d57664e9SAndroid Build Coastguard Worker .join('f, '); 181*d57664e9SAndroid Build Coastguard Worker const itemsToDp = currentScaleArray.items.map(d => d.toDp) 182*d57664e9SAndroid Build Coastguard Worker .map(formatDigit) 183*d57664e9SAndroid Build Coastguard Worker .join('f, '); 184*d57664e9SAndroid Build Coastguard Worker 185*d57664e9SAndroid Build Coastguard Worker return previousScaleArray + ` 186*d57664e9SAndroid Build Coastguard Worker put( 187*d57664e9SAndroid Build Coastguard Worker /* scaleKey= */ ${currentScaleArray.scale}f, 188*d57664e9SAndroid Build Coastguard Worker new FontScaleConverter( 189*d57664e9SAndroid Build Coastguard Worker /* fromSp= */ 190*d57664e9SAndroid Build Coastguard Worker new float[] {${itemsFromSp}}, 191*d57664e9SAndroid Build Coastguard Worker /* toDp= */ 192*d57664e9SAndroid Build Coastguard Worker new float[] {${itemsToDp}}) 193*d57664e9SAndroid Build Coastguard Worker ); 194*d57664e9SAndroid Build Coastguard Worker `; 195*d57664e9SAndroid Build Coastguard Worker }, 196*d57664e9SAndroid Build Coastguard Worker '')); 197