xref: /aosp_15_r20/frameworks/base/tools/fonts/font-scaling-array-generator.js (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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