2017년 12월 18일 월요일

나눔스퀘어 글꼴에서는 안보이는 글자들

요즘 만들고 있는 웹어플리케이션에서 나눔스퀘어를 한글의 기본 글꼴로 사용하고 있다. 그런데 최종테스트 중에 특정 글자가 보이지 않는다는 결함이 발생해서 그 원인과 해결책을 블로그로 남긴다.

1.
결함의 내용은 이러했다.
한자 입력하면 글자색이 흰색이라 보이지 않음
"넫넫"  등 잘못된 한글 입력해도 글자색이 흰색으로 보임
한자를 입력하거나 넫넫을 입력해봤더니 정말 보이지 않는 것이었다. 이게 뭔가라는 생각에 나눔스퀘어 사이트에서 입력을 해보았더니 여기서도 보이지 않게 된다!



이번에는 "덜거덕"을 입력해보았다. "덜거덕"을 입력하려다 보면, "덝"까지만 입력한 상태가 만들어지는데, 이때 "덝"글자가 보이지 않게된다.



이럴수가! 나눔스퀘어 글꼴에 뭔가 문제가 있을 거라고 생각하고 글꼴 파일을 파헤쳐보기 시작했다.


2.
나눔스퀘어 글꼴은 "꼭 필요한 2,350자만을 추렸기" 때문에 한자나 "넫"같은 글자가 제외되어 만들어진 것이다. 그런데 제외된 글자들이 글꼴 파일에서도 제외되어야 브라우저가 다른 글꼴로 표시할 텐데, 실제로 제외하지 않고 투명한 글자로 포함시켜서 만들어진 것이 문제였다.

글꼴의 상세한 내용을 볼 수 있는 FontForge로 나눔스퀘어 글꼴 파일의 내용을 열어보면 이렇다. 가운데에 파란색으로 선택된 부분이 "덝"글자가 표시되는 자리이다. 그런데 다른 여러 빈 공간처럼 "덝"부분도 비어있다. 즉, 나눔스퀘어 글꼴이 "덝"글자를 빈 공간으로 표시하는 것이다.


다른 글꼴 중에도 2350자만 추려서 "경량화"된 글꼴이 존재한다. 최근에 글꼴과 관련되어 좋은 글이 여러번 올라와 알게된 스포카한산스도 그 중 하나이다.  아래 캡쳐를 자세히 보면 "덝"에 해당하는 부분이 붉은색 X 표시되어 있어서, 글꼴 파일에 포함되지 않은 것을 알 수 있다.










참고로 경량화 되지 않은 글꼴은 아래와 같이 모든 문자가 포함되어 있다.










3.
결국 나눔스퀘어가 실제로는 포함하지 않는 2350자 이외의 문자에 대해서 실제 파일에서도 포함하지 않도록 하는 것이 해결책이라고 할 수 있겠다.

우선 나눔스퀘어에 포함된 2350자가 명확하게 어떤 글자들인지 그 목록이 명시되어 있지 않지만, 일반적으로 완성형 한글의 2350자일 것이라 추측했다. 그리고 실제 완성형 한글의 2350자의 목록은 나무위키에서 얻을 수 있었다.

2350자만 추출하여 새로운 글꼴 파일을 만들어내는 방법을 통해서 투명한 글자가 생기지 않도록 새로운 나눔스퀘어 글꼴 파일을 만들 수 있었다. 그리고 2350자가 올바르게 추출되었는지를 확인하기 위해 SIL ViewGlyph를 이용해서 글꼴에 포함된 글자의 수를 확인했다.


4.
이 문제를 나눔스퀘어 글꼴이 가지고 있다는 것을 어딘가에 알려주고 싶은데, 알려줄 방법이 없다.


2017년 12월 3일 일요일

spring boot + gradle + proguard + 실행가능한 jar

spring boot + gradle + proguard를 사용하여 웹어플리케이션의 실행가능한 jar를 만드는 방법을 소개한다.


1.
spring boot를 사용하면 보통 gradle을 빌드도구로 사용하게 되고, 웹어플리케이션을 만들 때 보통 실행가능한 jar를 만들어서 쓰곤 한다. 아래와 같이 build.gradle을 구성하고 gradle assemble 명령어로 실행가능한 jar를 만든다.

buildscript {
    repositories {
     mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

springBoot {
    executable = true
} 

2.
만들어진 jar를 보면 BOOT-INF/clesses에 내가 만든 클래스들이, BOOT-INF/lib에 내가 사용하는 라이브러리들이 포함된다.

여기서 내가 만든 클래스들을 proguard로 난독화를 적용하고 싶으면 proguard가 제공하는 gradle 플러그인을 이용하면 손쉽게 만들수 있다.

buildscript {
    repositories {
     mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.8.RELEASE")
        classpath("net.sf.proguard:proguard-gradle:5.3.3")
    }
}

def proguardJarPath = jar.archivePath.absolutePath.replace(".jar", "-proguard.jar")

task proguard(type: proguard.gradle.ProGuardTask, dependsOn: jar) {
    configuration "proguard.conf"

    injars jar.archivePath.absolutePath
    outjars proguardJarPath

    libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
    libraryjars project.configurations.compile
}


3.
그런데 난독화된 jar를 실행가능한 jar에 포함시키려면 gradle 설정이 조금 더 필요하다. 실행가능한 jar를 만드는 gradle의 spring boot 플러그인의 bootRepackage 태스크가 사용하게 되는 jar를 난독화된 jar로 바꿔줘야 한다.

bootRepackage {
 withJarTask = proguardJar
}

이때 withJarTask에 대입이 가능한 값은 Jar 타입의 gradle 태스크여야 한다. ProGuardTask는 Jar 타입이 아니다. 그래서 proguardJar라고 내가 만든 Jar 타입의 태스크가 필요하다. 이미 난독화된 jar 파일이 있으므로 zipTree로 파일 구조를 유지하면서 새로운 jar 파일을 만들어야 한다.

task proguardJar(type: Jar, overwrite: true, dependsOn: proguard) {
 from zipTree(proguardJarPath)
}

4.
다 합치면 아래와 같은 gradle 설정이 된다.




2017년 11월 27일 월요일

모달 팝업뜰때 바닥 스크롤 막기

모달 팝업이 표시될 때 브라우저 스크롤을 막는 방법

브라우저 스크롤이 있는데 모달팝업의 내부 스크롤도 있는 경우, 모달팝업의 내부 스크롤을 마우스로 내리다가 끝에 다다르면, 브라우저 스크롤이 내려간다. (어둡게 처리된 내용이 스크롤되는 현상)

이런 현상을 막기위해 모달팝업이 표시될 때는 body의 overflow를 hidden으로 변경해서 브라우저 스크롤을 없에는 방법을 사용했더니, 브라우저 스크롤바가 사라지면서 그만큼 브라우저의 가로 길이가 늘어나면서 body의 컨텐츠가 움찔하면서 새로 확보된 공간만큼 재배치가 일어난다. 모달팝업이 닫히고 다시 브라우저 스크롤바가 생기면 반대방향으로 움찔하는 현상이 일어난다.


이런 현상을 막기위해 body의 position을 fixed로 만들고, overflow를 scroll로 변경해서 스크롤바 영역은 남기지만 실제로 스크롤은 되지 않도록 하는 방법도 있다. 하지만 바닥의 스크롤이 맨위로 올라가버리는 부작용이 있고, 바닥의 스크롤바가 없는 경우에는 오히려 불필요하게 스크롤바가 표시되는 문제가 있다.

body의 스크롤을 일으키는 이벤트를 모두 막아버리는 방법도 있다. 하지만 컨텐츠가 스크롤되는 경우는 마우스 휠뿐만이 아니라 키보드의 화살표입력, 터치 이벤트, 마우스의 가운데 버튼을 클릭하고 움직이는 경우 등 모든 것을 제어하기가 쉽지 않다.

가장 코드가 깔끔하고도 동작이 정확한 방법은 body의 스크롤바를 없에고 자식들에게만 스크롤바를 만들어주는 것이다. javscript 없이도 css만으로 원하는 상태를 만들 수 있고, 바닥의 스크롤바의 표시여부와 스크롤 위치도 보존되는 방법이다.

body를 브라우저 꽉차게 만들어서 스크롤바가 생기지 않게 만들고
body 하위에 바닥용 div와 모달팝업용 div를 자식으로 만들고
바닥용 div (아래 예시의 .content)는 스크롤바를 생기게 만들고
모달팝업용 div (아래 예시의 .modal-background)는 스크롤바를 생기지 않게 만든다.

모달팝업이 화면을 꽉 채우기 떄문에 스크롤이 위나 아래에 닿아도 부모 body로만 스크롤 이벤트가 올라가기 때문에 body의 자식인 바닥용 div는 스크롤되지 않는다.




2017년 4월 1일 토요일

[번역] 객체로서의 함수 [FunctionAsObject]

이 포스트는 FunctionAsObject를 2017년 3월 30일 새벽에 번역한 글입니다. 원작자인 Martin Fowler의 허락을 받아 블로그에 게시합니다. 원문의 영어 단어나 표현이 필요하다고 생각한 경우에 괄호안에 원문을 같이 표기했습니다.

객체로서의 함수

프로그래밍에 있어서, 객체(object)의 가장 기본 개념은 데이터와 행동방식(behavior)을 함께 묶는 것입니다. 이렇게 하면 서로 관련있는 여러 함수를 만들 때, 공통의 데이터 컨텍스트(common data context)를 만들 수 있습니다. 그리고 그 데이터를 조작하는 인터페이스를 만들어 내서, 객체 내의 데이터에 대한 접근을 해당 객체가 제어할 수 있도록 할 수 있습니다. 그러면 관련있는 다른 데이터(derived data)를 만들어내기도 쉬워지고, 데이터를 잘못 수정하는 일도 미리 막을 수 있게 됩니다. 많은 프로그래밍 언어가 객체 정의를 위한 클래스를 선언하는 명시적인 방법을 가지고 있습니다. 하지만 1급함수(first-class function)와 클로저(closure)를 사용할 수 있는 프로그래밍 언어에서는, Function As Object 패턴을 이용해서 객체를 만들어 낼 수 있습니다. (Eugene Wallingford에 의해 처음 제안되었습니다.)

간단하게 사람을 객체로 만든 예제를 들어보겠습니다. 객체로서의 함수(function-as-object) 스타일을 JavaScript에서 사용했습니다. [1]

function createPerson(name) {
  let birthday;
  return {
    name: () => name,
    setName: (aString) => name = aString,
    birthday: () => birthday,
    setBirthday: (aLocalDate) => birthday = aLocalDate,
    age: age,
    canTrust: canTrust,
  };
  function age() {
    return birthday.until(clock.today(), ChronoUnit.YEARS);
  }
  function canTrust() {
    return age() <= 30;
  }
}

가장 바깥쪽의 형태가 객체로서의 함수인 함수입니다. 이는 생성자 함수(constructor function)라고도 불립니다. 이 함수의 호출 결과는 (엄밀히 따지면) 함수들의 해쉬맵(hashmap)입니다. [2] 이 해쉬맵은 메서드 셀렉터(method selector)의 역할을 합니다. 이 해쉬맵은 변수들의 상태를 클로저안에 있는 함수에 보관합니다. 이로써 각각의 함수 호출과는 상관없이 데이터를 유지할 수 있게 합니다. 그러면 이 해쉬맵은 전통적인 객체처럼 취급될 수 있습니다.

const kent = createPerson("kent");
kent.setBirthday(LocalDate.parse("1961-03-31"));
const youngEnoughToTrust = kent.canTrust();

객체로서의 함수를 전통적인 객체지향관점에서 분석해보겠습니다.
  • 객체의 필드(field)는 위 예제의 name처럼 생성자 함수의 인자의 형식, 또는 birthday처럼 지역 변수의 형식으로 표현됩니다.
  • 객체의 메서드는 생성자 함수 내부의 함수입니다. 보통의 객체의 메서드처럼, 내부의 함수들은 서로 자유롭게 호출이 가능하며 지역변수로 선언된 데이터를 조작할 수 있습니다.
  • 생성자 함수의 바깥에서는 내부의 변수에 접근할 수 없습니다. 이로 인해 데이터의 캡슐화(encapsulation)가 지켜집니다.
  • 객체의 퍼블릭(public) 메서드는 생성자 함수가 리턴하는 해쉬맵에 포함된 함수들입니다.
  • 생성자 함수 내부에서 선언된 함수들 중에서, 리턴 해쉬맵에 포함되지 않은 함수들이 바로 객체의 프라이빗(private) 메서드입니다.
  • 퍼블릭 메서드의 함수 이름은 리턴하는 해쉬맵의 키와 같습니다. 생성자 함수 내부에서 선언한 함수 이름이 아닙니다. 저는 불필요한 혼란을 피하기 위해서 내부의 이름과 외부로 노출되는 함수 이름을 같게 유지하는 것을 더 좋아합니다. (필요하면 간단한 방법으로 별도의 함수 별명을 지정할 수 있습니다.) [3]

이 패턴의 또 다른 일반적인 구현방법은 바로 메서드 셀렉터를 가진 함수를 리턴하는 것입니다. 해쉬맵처럼 JavaScript의 기본적인 메서드 셀렉터가 아니어도 됩니다. 메서드 셀렉터로 함수를 사용하기 위해서는, 첫번째 인자로 사용하려는 메서드의 이름을 받아들이는 함수를 리턴하면 됩니다. 함수의 내부 구현에서 인자로 들어온 메서드의 이름을 기준으로 조건을 주어 구별하면 됩니다. (Wallingford가 더 자세하게 작성한 문서가 있습니다.)

위와 같은 객체로서의 함수를 사용하는 방법은 예전 부터 있었습니다. lisp에서도 많이 볼 수 있었고, JavaScript에서도 폭넓게 사용되어 왔습니다. (ES6 이전에는 클래스에 대한 아주 제한적인 표현법만 있긴 했습니다.) 클래스를 위한 특별한 문법이 불필요하다는 논쟁이 종종 있었습니다. 이건 마치 객체애호가들이 메서드 1개짜리 클래스를 사용하면 되기 때문에 1급 함수가 필요없다고 했던 논쟁과 같은 것입니다. 그 결과로 JavaScript 진영의 많은 사람들이 ES6의 클래스 문법을 사용하는 것에 대해 아직 논쟁을 벌이고 있습니다. 1급 함수와 1급 클래스가 모두 있어서 좋습니다만, 개인적으로는 ES6의 클래스 문법을 더 좋아합니다.

더 읽어보기

Eugene Wallingford가 "객체로서의 함수"라는 이름을 만든 것은 1999년의 패턴 언어 "Envoy"에서 였습니다. 이 글을 보면 더 상세한 내용을 알수 있습니다. 함수를 메서드 셀렉터로 사용하는 것과 상속의 일부 개념을 지원하는 위임 형태로 사용하는 것에 대한 내용이 있습니다. Scheme을 예제로 사용하고 있습니다.

감사의 글

이 글의 초안에 대해서 의견을 보내준 Chris Ford, Fred George, James Shore, Kevin Yeung, Lucas Lego, Matteo Vaccari, Rob Miles, 그리고 Eugene Wallingford에게 감사드립니다.

주석

1. 날짜를 다루기 위해 js-joda를 사용했습니다. js-joda는 Java가 날짜와 시간에 대해서 엉망이었던 것을 깔끔하게 정리한 Joda-Time 라이브러리의 JavaScript 버전입니다. js-joda로 인해 JavaScript의 날짜와 시간에 대한 문제를 제대로 사용할 수 있게 되어 다행이라 생각합니다.

2. JavaScript 용어로 그걸 객체라고 부릅니다. 비록 우리가 만들려고 했던 전통적인 객체는 아니지만, 그래도 JavaScript의 객체입니다. 그래서 혹시 모를 혼란을 줄이기 위해 해쉬맵이라고 지칭했습니다.

3. ES6에서는 "age : age," 처럼 중복되는 내용은 그냥 "age,"로 대체 할 수 있습니다.


2017년 1월 15일 일요일

underscore 스터디 중 Collection 함수 유형별 분류 결과

개발팀내에서 underscore.js 스터디를 하고 있는데, Collection 함수들에 대해서 유형별로 정리하면서 진행을 했다. 아래는 그 결과이다.

filter ( [a,b,c] -> [b,c] )
_.filter
_.where
_.reject
_.sample
_.first
_.last
_.initial
_.rest
_.compact
_.without
_.union
_.intersection
_.difference
_.uniq


map ( [a,b,c] -> [A,B,C] )
_.each
_.map
_.invoke
_.pluck
_.sortBy
_.groupBy
_.indexBy
_.shuffle
_.toArray
_.partition
_.flatten
_.zip
_.unzip


reduce ( [a,b,c] -> abc )
_.reduce
_.reduceRight
_.find
_.findWhere
_.every
_.some
_.contains
_.max
_.min
_.countBy
_.sample
_.size
_.first
_.last
_.indexOf
_.lastIndexOf
_.sortedIndex
_.findIndex
_.findLastIndex


object ( [] -> {} )
_.object


range ( 3 -> [1,2,3] )
_.range