생각보다 수학으로 할 수 있는 많은것들 (Non-linear font scaling 구현하기)

신원규·2024년 3월 3일
9
post-thumbnail

CS 나, 수학적 사고력 등은 이직을 위한 공부라고 생각하거나
ML, CG, CV 등 연구직에서만 중요도가 높고 개발직군에서는 큰 연관도가 낮다는 주장을 주변에서 많이 접할 수 있었어요.

특히 FE는 대규모 시스템을 다룰 일이 적기 때문에 그 중요도가 더욱 낮다는 주장을 하시는 분들도 종종 볼 수 있었답니다.

저는 아직 그리 길지 않은 경력을 가지고 있지만, 크로스 플랫폼 앱을 개발하며 CS 지식은 물론이고, 수학적 사고력이 실무의 문제를 간단하게 풀 수 있는 해결책이 되는 경우를 많이 겪었어요.

그 중 하나의 예시를 보여드리고자 합니다.

NonLinear Font scaling 이라는 용어가 있답니다 혹시 들어보셨나요?

자세한 코드레벨의 구현을 보고싶으시다면, 이 링크를 참조하셔도 좋습니다.

Android 14 버전에서 본격적으로 적용된 개념인데요, 모바일 화면을 개발하다보면 다음과 같은 상황을 겪으신 적이 종종 있으실 겁니다.

데스크탑에 비해 작은 화면 안에, 복잡한 UI 레이아웃을 가지고 있는 화면에서 UI 에 표시되는 폰트의 크기가 일정 사이즈를 넘어갈 경우,
개행(line break)으로 인해 전체 레이아웃이 무너지거나, 위와 같은 경우처럼 레이아웃을 강제하다 보니, 텍스트의 겹침, 생략, 표기 오류등이 일어나게 되죠.

이때 새로운 아이디어가 도입되게 되요.
노인, 저시력자등을 위한 폰트 사이즈 설정의 핵심은 작은 텍스트를 크게 키우는것이지, 이미 큰 폰트를 더욱 크게 바꾸는건 시인성 증진에 큰 도움이 되지 않는다는 것이죠.

그래서 기존의 모든 폰트의 크기를 n 배 확대하는 선형적인 모델에서,
큰 폰트는 조금 확대하고 작은 폰트는 유저가 의도한 만큼 확대하는 비선형적 크기 조정 곡선을 사용하는 모델로 변경해,

다양한 크기의 텍스트 간의 계층구조를 유지함과 동시에
(H1 이 H3 과 크기가 동일해 진다면, 유저는 디자인에서 의도한 UI 간 계층 구도를 이해하기 힘들겠죠?)
텍스트가 잘리거나 표시되는 크기가 너무 커져 읽기 어려워지는 경우를 완하하는데 도움을 줄 수 있어요.

이제 저의 실무 이야기를 드려볼께요.

저는 Flutter를 사용해 Cross platform 앱을 개발하고 있답니다.

대부분의 코드를 작성할때, kotlin, swift 를 떠나 dart 라는 언어로 Flutter 라는 프레임 워크에서 작업을 하지만, 위에 설명한 non linear font scale 처럼 빌드 대상이 되는 OS 의 breaking change 가 있을 경우,

제가 사용하는 Flutter 에도 그 여파가 미치는 경우가 종종 발생해요.

이런식으로 업데이트가 일어나기 전에는 Os에서 제공하는 textScaleFactor 라는 변수를 받아와 font size 에 곱하는 단순한 선형적 모델을 사용했지만, Flutter 의 버전 업데이트 이후로 해당 필드가 dprecated 되어 새로운 방식을 적용해야 했어요.

그래서 저는 이전에 설명한 non linear font scale 에 대해서 조사한 이후 이를 적용해보려 했답니다.

하지만, Flutter 프레임워크에서 제공하는 TextScaler 의 실 구현체가 _ClampedTextScaler 와 LinearTextScaler 두개 뿐인것을 확인했어요. (_UnspecifiedTextScaler 는 그 이름에서 알 수 있듯이 일반적으로 사용할 수 있는 클래스가 아니랍니다.)


위의 목적을 달성하기 위해선 LinerTextScaler 는 선택할 수 없고,
clampedTextScaler 는 fontSize 를 조정할때 clampDouble 이라는 함수를 사용하는데, 이는 단순히 설정한 최소-최대값을 벗어나는 입력을 보정해주는 함수라는걸 알 수 있었어요.

하지만, 저는 단순한 범위 지정 clamping 보다는 좀더 부드러운 보정을 UI 시스템에 주고 싶었어요.
급격하게 cut-off 되는 변화보단, UI의 부드러운 변화를 주고 싶었어요.

여기서부터 아이디어를 떠올리기 시작합니다.
저는 이때 생각한 함수는 tanh 하이퍼볼릭 탄젠트 함수를 생각했습니다.
(이 게시글에선 함수의 정의는 별로 신경쓰지 않을꺼에요. 우리에게 중요한건, 이 함수가 어떻게 정의되었는지가 아니라, 어떤식으로 응용할 수 있는지를 알아보는거니까요.)

tanh(x) 함수의 개형은, 최소-최댓값을 부드럽게 이어주는 모양을 가지고 있고, -1, +1 에 수렴한다는 점이 매우 사용하기 좋은 모양이라 생각했어요.

또한 삼각함수와 비슷한 속성을 가지고 있기 때문에, x 를 적절히 나누면 함수의 위상을 조정할 수 있다는 장점도 가지고 있구요.

이제 저는 이 함수를 가지고 적절히 조절해가며
보정함수{(OS에서 주는 폰트 스케일 펙터) * (우리 디자인 시스템에서 정의한 폰트 사이즈)} 가 우리의 의도에 맞게 동작하도록 설계할꺼에요.

일단, 이 함수의 출력은 글자의 변화되는 배율을 의미할꺼에요.
배율이 -1 배라는건 있을수 없는 상황이니, 함수 전체를 y축으로 1 평행이동 시켜보겠습니다.

아주 마음에 드는 모양이 나왔어요.

W3C 에서 정의한 텍스트 크기 조정또한, 200% 가 넘어가는 폰트 크기 조정에 대해서는 말하고 있지 않고 있기 때문이에요.

여기서 추가로 함수의 스케일을 조정할 필요 없이 적절히 몇개의 인자만 더해주면 될 것 같네요.

두번째로는 함수를 x 축으로 오른쪽으로 1 만큼 평행이동을 시켜주어야 할것 같아요.
아무런 폰트 스케일 조정이 없는 상황에서는 함수의 입력값 == 출력값이 되어야 하기 때문이에요.

마지막으로, 하나의 독립변수를 추가해야해요.
저희는 글자 크기가 클 수록 변화에 대해 둔감하고, 작은 크기의 폰트일수록 스케일 증감에 대해 민감하게 반응하길 원하기 때문이에요.

tanh(폰트 크기 변수 * (시스템 폰트 펙트))

위와같은 모양의 식을 구성한뒤, 적절한 모양을 갖도록 수정하면, 다음과 같은 그래프를 얻을 수 있어요.

a(폰트 크기) 가 10과 같이 작은 사이즈일 경우에는 y = x 에 가까운 크기 조정결과를 얻을 수 있고,

폰트 크기가 50 과 같이 큰 경우에는 매우 둔감하게 움직이는 조정결과를 얻을 수 있어요.

이제 이를 코드로 옮긴다면,

이런식으로 구현 할 수 있을꺼 같습니다.
(Dart 의 math 패키지에서 tanh 함수를 기본적으로 제공하지 않기 때문에 정의를 기반으로 직접 구현했어요.)

마지막으로, 구현한 솔루션을 검증 할 수 있는 테스트 코드를 작성해야합니다.

  1. 코드는 의도한 스펙을 달성했는지를 검사해야하고
  2. 제한된 시간복잡도 하에서 동작하는지를 검사해야합니다.

이 코드는 화면에 표시된 모든 텍스트에서 한번씩 호출될 거라 생각 할 수 있어요.
따라서 그 시간복잡도가 매우 중요한데요, 적어도 1/60s ~= 0.016 s 안에 실행되려면,
로직의 시간복잡도가 O(1) 과 O(N) 의 사이에 위치하고 있어야해요.

이를 테스트 코드로 검증하자면,


아주 정밀하지는 않지만, 이러한 테스트케이스를 통과하면 실제로 활용할 수 있는 스펙을 충족했다 말할 수 있어요.

물론 두가지의 테스트케이스를 모두 통과했답니다.

저는 이 글을 쓰며 프로그래밍은 응용수학이라는 것을 알리고 싶었어요.
단순히 면접이나 기초 지식을 위해서가 아닌, 수학적 사고력은 실무레벨에서도 적극적으로 쓰일 여지가 많다는 사실을 여기까지 읽으신 분들이 알아가시면 좋겠다는 생각을 하며 글을 마무리해 봅니다.

profile
생존형 개발자. 어디에 던져져도 살아 남는것이 목표입니다.

0개의 댓글