refactor: Rewrite all codes, adding noto mono patchs

master
Qwreey 2023-03-11 18:29:41 +09:00
parent 697f7ace59
commit 9816ee7f6d
33 changed files with 2688 additions and 610 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
build:
python3 build.py
py3clean .
@mkdir -p out
@python3 build.py -B 2> out/err.log
@py3clean .
install: build
cp out/KawaiiMonoRegularPatched.ttf ~/.local/share/fonts/KawaiiMonoRegular.ttf
fc-cache -f -v
@cp out/KawaiiMonoRegularPatched.ttf ~/.local/share/fonts/KawaiiMonoRegular.ttf
@fc-cache -f -v

View File

@ -1,3 +1,7 @@
import os
import sys
sys.path.append(os.path.realpath(os.path.dirname(__file__)+"/src"))
sys.path.append(os.path.realpath(os.path.dirname(__file__)+"/assets"))
from src.build import build
import config
if __name__ == "__main__": build(config.config)

View File

@ -41,25 +41,31 @@ config = {
# 폰트 용량이 매우 커집니다!!
"CopyKoreanGlyphs": True,
# TODO: 이 설정은 아직 동작하지 않습니다
# 노토 산스 모노에서 가타카나/히라가나
# 글리프를 복사할 지에 대한 여부입니다
# 한자는 포함하지 않습니다
"CopyJapaneseGlyphs": False,
"CopyJapaneseGlyphs": True,
# 노토 산스에서 단위관련 기호, 원형 기호
# 글리프를 복사할 지에 대한 여부입니다
# 폰트위 용량이 매우 커집니다!!
"CopySymbols": True,
# TODO: 이 설정은 아직 동작하지 않습니다
# 노토 산스 모노에서 CJK 공용 한자 글리프를
# 복사할 지에 대한 여부입니다.
# 폰트 용량이 매우 커집니다!!
# 웹용 폰트의 경우 끄는것을 추천합니다
"CopyCJKUnifiedIdeographs": False,
#! 최소 2분 이상 걸립니다!!
"CopyCJKUnifiedIdeographs": True, # 일반적인 CJK Unified
"CopyCJKUnifiedIdeographsExtension": False, # Extension A~F
"CopyCJKCompatibilityIdeographs": False, # Compatibility Ideograph, Supplement
#! 이 옵션들을 활성화시 ttf 포멧으로 저장에 실패할 수 있습니다
#? ttf 의 글자수 제한 때문에 그런것이므로, 다른 포멧으로 저장해야합니다.
# TODO: 이 설정은 아직 동작하지 않습니다
# 라틴 글리프를 Hack 폰트에서 더 가져옵니다
# (성조 표시된 라틴, ...)
"CopyLatinExtra": True,
# TODO: 이 설정은 아직 동작하지 않습니다
# Nerd Fonts 패치를 적용할지에 대한
# 여부입니다. 폰트 용량이 매우 커집니다!!
# 웹용 폰트의 경우 끄는것을 추천합니다

View File

@ -1,87 +0,0 @@
from . import wgetHandler
import os
import zipfile
import shutil
import math
from . import utility as Utility
import fontforge
link_NanumSquareNeo = "https://campaign.naver.com/nanumsquare_neo/download/NaverNanumSquareNeo.zip"
patchVersion = 2 # 업데이트 후 캐시를 무시하기 위해서 사용
# 폰트 다운로드와 열기
# 배포 방식이 zip 으로 배포이기 때문에 zipfile 라이브러리로
# 다운로드 후 언팩함
def getFontPath():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NanumSquareNeoKr.ttf"):
if not os.path.exists("assets/NanumSquareNeoKr.zip"):
wgetHandler.download(link_NanumSquareNeo,"assets/NanumSquareNeoKr.zip")
print("Unzipping NanumSquareNeoKr.zip",end="")
with zipfile.ZipFile("assets/NanumSquareNeoKr.zip", 'r') as zip_ref:
extractName = zip_ref.extract("NaverNanumSquareNeo/TTF/NanumSquareNeo-bRg.ttf","assets/NanumSquareNeoKr.extract")
os.rename(extractName,"assets/NanumSquareNeoKr.ttf")
shutil.rmtree('assets/NanumSquareNeoKr.extract')
print(" [OK]")
return "assets/NanumSquareNeoKr.ttf"
# 한글 범위의 글립을 선택함
def selectGlyphs(font):
font.selection.none()
font.selection.select(("more","ranges","unicode"),0x3131,0x32BF) # ㄱ ~ ㊿
font.selection.select(("more","ranges","unicode"),0xAC00,0xD7A3) # 가 ~ 힣
# 굵기/폭 설정 캐시파일 만들기
def getCache(sourcePath,baseSize=550,weight=16):
# 캐시된 파일을 확인하고 있으면 반환
filename = "assets/cache/NanumSquareNeoKr.cache_{}.base_{}.weight_{}.sfd".format(patchVersion,baseSize,weight)
if os.path.exists(filename):
return fontforge.open(filename)
# 새로운 캐시용 폰트 생성
cache=fontforge.font()
cache.encoding = 'UnicodeFull'
# 소스 폰트를 패치시킴
source=fontforge.open(sourcePath)
selectGlyphs(source)
source.changeWeight(weight) # 굵기 변경
# 너비 지정
Utility.setWidthWithSavingPosition(
font=source,targetWidth=baseSize*2
)
# 캐시에 붇여넣기
source.copy()
selectGlyphs(cache)
cache.paste()
# 캐시 폰트 저장
if not os.path.exists("assets/cache"): os.mkdir("assets/cache")
cache.save(filename)
return cache
# Regular 같은 문자열 weight 를 포인트 값으로 변경
weightStrToNum = {
"Regular": 16,
}
# 캐시를 가져와서 글리프를 타겟 폰트에 붇여넣음
def pasteGlyphs(target,sourcePath,baseSize=550,weightStr="Regular"):
# 캐시된 소스를 읽어드림
source = getCache(
sourcePath = sourcePath,
baseSize = baseSize,
weight = weightStrToNum.get(weightStr)
)
# 타겟으로 글리프 복사
selectGlyphs(source)
source.copy()
selectGlyphs(target)
target.paste()
# 캐시 닫기
source.close()

View File

@ -1,38 +0,0 @@
from . import wgetHandler
import os
github_NotoSansMonoCJKkr = "https://github.com/googlefonts/noto-cjk/raw/main/Sans/Mono/NotoSansMonoCJKkr-Regular.otf"
# 폰트 다운로드와 열기
def getFontPath():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NotoMonoCJKkr.otf"):
wgetHandler.download(github_NotoSansMonoCJKkr,"assets/NotoMonoCJKkr.otf")
return "assets/NotoMonoCJKkr.otf"
def pasteGlyphs(target,source,baseSize=550,JapaneseGlyphs=False,CJKUnifiedIdeographs=False):
source.cidFlatten()
def select(font):
font.selection.none()
font.selection.select(("more","ranges","unicode"),0x3131,0x32BF) # ㄱ ~ ㊿
font.selection.select(("more","ranges","unicode"),0xAC00,0xD7A3) # 가 ~ 힣
select(source)
# 넓은 글자 크기 (한글에 모두 적용)
wideWidth = baseSize*2
for glyph in source.selection.byGlyphs:
widthDiff = wideWidth-glyph.width # 타겟 너비와 얼마나 크기 차이가 나는지
sideAdjust = widthDiff/2 # 좌우 사이드 조정해야하는 정도
glyph.left_side_bearing = int(glyph.left_side_bearing + math.floor(sideAdjust)) # 좌우 베어링을 조정함
glyph.right_side_bearing = int(glyph.right_side_bearing + math.ceil(sideAdjust))
glyph.width = wideWidth # 타겟 너비로 정확하게 설정
# 타겟으로 글리프 복사
source.copy()
select(target)
target.paste()
if __name__ == "__main__":
getFontPath()

View File

@ -1,58 +0,0 @@
import fontforge
import os
from . import NanumSquareNeo as NanumSquareNeoLoader
from . import NotoMono as NotoMonoLoader
from . import KawaiiMono as KawaiiMonoLoader
from . import utility as Utility
def build(config=None):
# 메인 폰트 불러오기
kawaii = fontforge.open(
KawaiiMonoLoader.getFontPath())
# 모든 글리프를 붇여넣을 수 있도록 인코딩을 utf full 로 변경
kawaii.encoding = 'UnicodeFull'
# 폰트 가로폭 설정
baseSize = config.get("FontBaseWidth")
if baseSize != 550:
Utility.setWidthWithSavingPosition(
font=kawaii,targetWidth=baseSize
)
# 한글 글리프 붇여넣기
if config.get("CopyKoreanGlyphs"):
# 나눔 스퀘어 네오 다운로드/불러오기
nanumSquareNeo = NanumSquareNeoLoader.getFontPath()
# 글리프 붇여넣기
NanumSquareNeoLoader.pasteGlyphs(
target=kawaii,baseSize=baseSize,weightStr="Regular",
sourcePath=nanumSquareNeo)
if (config.get("CopyJapaneseGlyphs") or
config.get("CopyCJKUnifiedIdeographs")):
# 노토 모노 다운로드/불러오기
notoMono = fontforge.open(
NotoMonoLoader.getFontPath())
# 글리프 붇여넣기
NotoMonoLoader.pasteGlyphs(
JapaneseGlyphs=config.get("CopyJapaneseGlyphs") or False,
CJKUnifiedIdeographs=config.get("CopyCJKUnifiedIdeographs") or False,
target=kawaii,baseSize=550,
source=notoMono)
notoMono.close()
# 생성
if not os.path.exists("out"): os.mkdir("out")
kawaii.generate("out/"+"KawaiiMonoRegularPatched.ttf")
# KawaiiMonoRegularPatched.ttf
# KawaiiMonoRegularPatched.otf
# KawaiiMonoRegularPatched.woff
# KawaiiMonoRegularPatched.eot
# 파일 닫기
kawaii.close()
if __name__ == "__main__": build()

View File

@ -0,0 +1,7 @@
patchVersion = 2 # 업데이트 후 캐시를 무시하기 위해서 사용
from . import select as selectGlyphs
from .download import download
from .cacheBuilder import getCachedFont
from .patcher import pasteGlyphs

View File

@ -0,0 +1,40 @@
import utility as Utility
import fontforge
import os
from . import selectGlyphs
from . import patchVersion
# 굵기/폭 설정 캐시파일 만들기
def getCachedFont(sourcePath,baseSize=550,weight=16):
# 캐시된 파일을 확인하고 있으면 반환
filename = "assets/cache/NanumSquareNeoKr.cache_{}.base_{}.weight_{}.sfd".format(patchVersion,baseSize,weight)
if os.path.exists(filename):
print("Found build cache [OK]")
return fontforge.open(filename)
print("Creating new build cache",end="")
# 새로운 캐시용 폰트 생성
cache=fontforge.font()
cache.encoding = 'UnicodeFull'
# 소스 폰트를 패치시킴
source=fontforge.open(sourcePath)
selectGlyphs.Korean(source)
source.changeWeight(weight) # 굵기 변경
# 너비 지정
Utility.setWidthWithSavingPosition(
font=source,targetWidth=baseSize*2
)
# 캐시에 붇여넣기
source.copy()
selectGlyphs.Korean(cache)
cache.paste()
# 캐시 폰트 저장
if not os.path.exists("assets/cache"): os.mkdir("assets/cache")
cache.save(filename)
print(" [OK]")
return cache

View File

@ -0,0 +1,22 @@
import zipfile
import shutil
import wgetHandler
import os
link_NanumSquareNeo = "https://campaign.naver.com/nanumsquare_neo/download/NaverNanumSquareNeo.zip"
# 폰트 다운로드와 열기
# 배포 방식이 zip 으로 배포이기 때문에 zipfile 라이브러리로
# 다운로드 후 언팩함
def download():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NanumSquareNeoKr.ttf"):
if not os.path.exists("assets/NanumSquareNeoKr.zip"):
wgetHandler.download(link_NanumSquareNeo,"assets/NanumSquareNeoKr.zip")
print("Unzipping NanumSquareNeoKr.zip",end="")
with zipfile.ZipFile("assets/NanumSquareNeoKr.zip", 'r') as zip_ref:
extractName = zip_ref.extract("NaverNanumSquareNeo/TTF/NanumSquareNeo-bRg.ttf","assets/NanumSquareNeoKr.extract")
os.rename(extractName,"assets/NanumSquareNeoKr.ttf")
shutil.rmtree('assets/NanumSquareNeoKr.extract')
print(" [OK]")
return "assets/NanumSquareNeoKr.ttf"

View File

@ -0,0 +1,32 @@
from . import getCachedFont
from . import selectGlyphs
# Regular 같은 문자열 weight 를 포인트 값으로 변경
weightStrToNum = {
"Regular": 16,
}
# 캐시를 가져와서 글리프를 타겟 폰트에 붇여넣음
def pasteGlyphs(target,sourcePath,deselectOriginalGlyphs,baseSize=550,weightStr="Regular"):
# 캐시된 소스를 읽어드림
print("Patching: NanumSquareNeo")
source = getCachedFont(
sourcePath = sourcePath,
baseSize = baseSize,
weight = weightStrToNum.get(weightStr)
)
selectGlyphs.Clear(source)
selectGlyphs.Clear(target)
# 타겟으로 글리프 복사
selectGlyphs.Korean(source)
deselectOriginalGlyphs(source)
source.copy()
selectGlyphs.Korean(target)
deselectOriginalGlyphs(target)
target.paste()
# 캐시 닫기
source.close()

View File

@ -0,0 +1,8 @@
# 한글 범위의 글립을 선택함
def Korean(font):
font.selection.none()
font.selection.select(("more","ranges","unicode"),0x3131,0x32BF) # ㄱ ~ ㊿
font.selection.select(("more","ranges","unicode"),0xAC00,0xD7A3) # 가 ~ 힣
def Clear(font):
font.selection.none()

View File

@ -0,0 +1,3 @@
from .download import downloadPatcher
from .download import nerdFonts_Download_Test

View File

@ -0,0 +1,21 @@
import os
import wgetHandler
import zipfile
link_FontPatcher = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/FontPatcher.zip"
def downloadPatcher():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NerdFontPatcher_extract"):
if not os.path.exists("assets/NerdFontPatcher.zip"):
wgetHandler.download(link_FontPatcher,"assets/NerdFontPatcher.zip")
print("Unzipping NerdFontPatcher.zip",end="")
with zipfile.ZipFile("assets/NerdFontPatcher.zip", 'r') as zip_ref:
extractName = zip_ref.extractall("assets/NerdFontPatcher_extract")
os.rename("assets/NerdFontPatcher_extract/font-patcher","assets/NerdFontPatcher_extract/fontPatcher.py")
with open("assets/NerdFontPatcher_extract/__init__.py","w") as file:
file.write("")
print(" [OK]")
return "assets/NerdFontPatcher_extract"
def nerdFonts_Download_Test(): downloadPatcher()

View File

@ -0,0 +1,7 @@
patchVersion = 2
from .download import download
from . import select as selectGlyphs
from .cacheBuilder import getCachedFont
from .patcher import pasteGlyphs

View File

@ -0,0 +1,56 @@
import utility as Utility
import fontforge
import os
from . import selectGlyphs
from . import patchVersion
# 굵기/폭 설정 캐시파일 만들기
def getCachedFont(sourcePath,EnabledItems,baseSize=550,weight=16):
# 캐시된 파일을 확인하고 있으면 반환
filename = "assets/cache/NotoMono.cache_{}.base_{}.weight_{}{}{}{}{}{}.sfd".format(
patchVersion,baseSize,weight,
EnabledItems.get("JapaneseGlyphs") and ".jp" or "",
EnabledItems.get("CJKUnifiedIdeographs") and ".id" or "",
EnabledItems.get("CopyCJKUnifiedIdeographsExtension") and ".ide" or "",
EnabledItems.get("CopyCJKCompatibilityIdeographs") and ".cid" or "",
EnabledItems.get("Symbols") and ".sym" or "")
if os.path.exists(filename):
print("Found build cache [OK]")
return fontforge.open(filename)
print("Creating new build cache",end="")
# 새로운 캐시용 폰트 생성
cache=fontforge.font()
cache.encoding = 'UnicodeFull'
# 소스 폰트를 패치시킴
source=fontforge.open(sourcePath)
source.cidFlatten()
source.encoding = 'UnicodeFull'
if EnabledItems.get("JapaneseGlyphs"): selectGlyphs.JapaneseGlyphs(source)
if EnabledItems.get("Symbols"): selectGlyphs.Symbols(source)
source.changeWeight(weight) # 굵기 변경
selectGlyphs.SelectByEnabledList(source,EnabledItems)
# 한자 글립은 냥많아서 크기조절하면 끝이 안난다냥
# 너비 지정
Utility.setWidthWithSavingPosition(
font=source,targetWidth=baseSize*2
)
Utility.scale(font=source,targetScale=0.85)
# 캐시에 붇여넣기
source.copy()
selectGlyphs.SelectByEnabledList(cache,EnabledItems)
cache.paste()
selectGlyphs.Clear(cache)
selectGlyphs.Clear(source)
# 캐시 폰트 저장
if not os.path.exists("assets/cache"): os.mkdir("assets/cache")
cache.save(filename)
print(" [OK]")
return cache

View File

@ -0,0 +1,11 @@
import os
import wgetHandler
github_NotoSansMonoCJKkr = "https://github.com/googlefonts/noto-cjk/raw/main/Sans/Mono/NotoSansMonoCJKkr-Regular.otf"
# 폰트 다운로드와 열기
def download():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NotoMonoCJKkr.otf"):
wgetHandler.download(github_NotoSansMonoCJKkr,"assets/NotoMonoCJKkr.otf")
return "assets/NotoMonoCJKkr.otf"

View File

@ -0,0 +1,30 @@
from . import selectGlyphs
from . import getCachedFont
# Regular 같은 문자열 weight 를 포인트 값으로 변경
weightStrToNum = {
"Regular": 16,
}
def pasteGlyphs(target,sourcePath,deselectOriginalGlyphs,EnabledItems,baseSize=550,weightStr="Regular"):
# 캐시된 소스를 읽어드림
source = getCachedFont(
sourcePath = sourcePath,
baseSize = baseSize,
weight = weightStrToNum.get(weightStr),
EnabledItems = EnabledItems
)
selectGlyphs.Clear(source)
selectGlyphs.Clear(target)
# 타겟으로 글리프 복사
selectGlyphs.SelectByEnabledList(source,EnabledItems)
deselectOriginalGlyphs(source)
source.copy()
selectGlyphs.SelectByEnabledList(source,target)
deselectOriginalGlyphs(target)
target.paste()
# 캐시 닫기
source.close()

View File

@ -0,0 +1,73 @@
selFlag = ("more","ranges","unicode")
# 일본어 글립 선택
def JapaneseGlyphs(font):
# 사각문자 (Square)
font.selection.select(selFlag,0x32FF,0x2271)
font.selection.select(selFlag,0x337B,0x337F) # Missing one char
font.selection.select(selFlag,0x1F200,0x1F200)
# 히라가나/가타카나
font.selection.select(selFlag,0x3041,0x30FF)
font.selection.select(selFlag,0x31F0,0x31FF) # SMALL Katakana
font.selection.select(selFlag,0xFF66,0xFF9F) # HalfWidth Katakana
# 일어 기호
font.selection.select(selFlag,0xFF5B,0xFF65)
# CJK 한자 글립 선택
def CJKUnifiedIdeographs(font):
# CJK Stroke
font.selection.select(selFlag,0x31C0,0x31E3)
# Bopomofo
font.selection.select(selFlag,0x3105,0x312F)
font.selection.select(selFlag,0x31A0,0x31BB)
# CJK Unified Ideograph
font.selection.select(selFlag,0x4E00,0x9FFF)
def CJKUnifiedIdeographsExtension(font):
font.selection.select(selFlag,0x3400,0x4DBF) # Extension A
font.selection.select(selFlag,0x20000,0x2A6DF) # Extension B
font.selection.select(selFlag,0x2A700,0x2B73F) # Extension C
font.selection.select(selFlag,0x2B740,0x2B81F) # Extension D
font.selection.select(selFlag,0x2B820,0x2CEAF) # Extension E
font.selection.select(selFlag,0x2CEB0,0x2EBEF) # Extension F
def CJKCompatibilityIdeographs(font):
# CJK Compatibility Ideograph
font.selection.select(selFlag,0xF900,0xFAFF)
# CJK Compatibility Ideographs Supplement
font.selection.select(selFlag,0x2F800,0x2FA1F)
def Symbols(font):
# 원형, 단위기호
font.selection.select(selFlag,0x3220,0x3250)
font.selection.select(selFlag,0x3220,0x3250)
font.selection.select(selFlag,0x3280,0x32B0) # Missing ...
font.selection.select(selFlag,0x32C0,0x32FE)
# 단위 기호
font.selection.select(selFlag,0x3358,0x337A)
font.selection.select(selFlag,0x3380,0x33FF)
# Latin Ligature
font.selection.select(selFlag,0xFB00,0xFB04)
# 특수기호/FULLWIDTH Latin
font.selection.select(selFlag,0xFE10,0xFF5A)
font.selection.select(selFlag,0xFFE0,0xFFEE)
font.selection.select(selFlag,0x1F100,0x1F1AC)
font.selection.select(selFlag,0x1F201,0x1F251)
def SelectByEnabledList(target,EnabledItems):
if EnabledItems.get("JapaneseGlyphs"): JapaneseGlyphs(target)
if EnabledItems.get("CJKUnifiedIdeographs"): CJKUnifiedIdeographs(target)
if EnabledItems.get("CJKUnifiedIdeographsExtension"): CJKUnifiedIdeographsExtension(target)
if EnabledItems.get("CJKCompatibilityIdeographs"): CJKCompatibilityIdeographs(target)
if EnabledItems.get("Symbols"): Symbols(target)
def Clear(font):
font.selection.none()

4
src/build/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from .build import build
from .NerdFonts import nerdFonts_Download_Test

91
src/build/build.py Normal file
View File

@ -0,0 +1,91 @@
import os
import utility as Utility
import fontforge
from . import NerdFonts as NerdFontsLoader
from . import NanumSquareNeo as NanumSquareNeoLoader
from . import NotoMono as NotoMonoLoader
from . import KawaiiMono as KawaiiMonoLoader
deselectFlags = ("less","unicode")
def build(config=None):
# 메인 폰트 불러오기 / 에셋 다운로드
kawaii = fontforge.open(
KawaiiMonoLoader.getFontPath())
print("-------------- DOWNLOAD PATCH CONTENTS --------------")
# 나눔 스퀘어 네오 다운로드
nanumSquareNeo = None
if config.get("CopyKoreanGlyphs"):
nanumSquareNeo = NanumSquareNeoLoader.download()
# 노토 모노 다운로드/불러오기
notoMono = None
if (config.get("CopyJapaneseGlyphs") or
config.get("CopyCJKUnifiedIdeographs") or
config.get("CopyCJKUnifiedIdeographsExtension") or
config.get("CopyCJKCompatibilityIdeographs")):
notoMono = NotoMonoLoader.download()
# Nerd fonts 패치기 다운로드
nerdFontsPatcherPath = None
if config.get("NerdFonts"):
nerdFontsPatcherPath = NerdFontsLoader.downloadPatcher()
print("--------------------- Patching ---------------------")
# 유지 목록 (덮어쓰기 금지) 만들기
kawaii.selection.all()
keepList = [i.unicode for i in kawaii.selection.byGlyphs]
def deselectOriginalGlyphs(target):
for unicode in keepList:
if unicode == -1: continue
target.selection.select(deselectFlags,unicode)
# 모든 글리프를 붇여넣을 수 있도록 인코딩을 utf full 로 변경
kawaii.encoding = 'UnicodeFull'
# 폰트 가로폭 설정
baseSize = config.get("FontBaseWidth")
if baseSize != 550:
Utility.setWidthWithSavingPosition(
font=kawaii,targetWidth=baseSize
)
# 한글 글리프 붇여넣기
if nanumSquareNeo:
# 글리프 붇여넣기
NanumSquareNeoLoader.pasteGlyphs(
target=kawaii,baseSize=baseSize,weightStr="Regular",
sourcePath=nanumSquareNeo,
deselectOriginalGlyphs = deselectOriginalGlyphs)
# 일어 글리프 혹은 한자 글리프 추가
if notoMono:
# 글리프 붇여넣기
NotoMonoLoader.pasteGlyphs(
EnabledItems = {
"JapaneseGlyphs": config.get("CopyJapaneseGlyphs") or False,
"CJKUnifiedIdeographs": config.get("CopyCJKUnifiedIdeographs") or False,
"CJKUnifiedIdeographsExtension": config.get("CopyCJKUnifiedIdeographsExtension"),
"CJKCompatibilityIdeographs": config.get("CopyCJKCompatibilityIdeographs"),
},
Symbols=config.get("CopySymbols") or False,
target=kawaii,baseSize=550,
sourcePath=notoMono,
deselectOriginalGlyphs = deselectOriginalGlyphs)
# NerdFonts 패치 적용
# if config.get("NerdFonts"):
# nerdFontsPatcherPath = NerdFontsLoader.downloadPatcher()
# 생성
if not os.path.exists("out"): os.mkdir("out")
kawaii.generate("out/"+"KawaiiMonoRegularPatched.ttf")
# KawaiiMonoRegularPatched.ttf
# KawaiiMonoRegularPatched.otf
# KawaiiMonoRegularPatched.woff
# KawaiiMonoRegularPatched.eot
# 파일 닫기
kawaii.close()
if __name__ == "__main__": build()

View File

@ -1,4 +0,0 @@
def pasteGlyphs(target,sourceList):

View File

@ -1,36 +0,0 @@
import os
import zipfile
# from .. import wgetHandler
import os
import importlib
basePath = os.path.realpath(os.path.dirname(__file__)+"/../")
wgetHandlerSpec = importlib.util.spec_from_file_location("wgetHandler",basePath+"/wgetHandler.py")
wgetHandler = importlib.util.module_from_spec(wgetHandlerSpec)
wgetHandlerSpec.loader.exec_module(wgetHandler)
# import wgetHandler
print("__file__ : {__file__}".format(__file__=__file__))
print("wgetHandler (spec)")
print(importlib.util.spec_from_file_location("wgetHandler",basePath))
print("wgetHandler [object]")
print(wgetHandler)
print(help(wgetHandler))
# import wgetHandler
link_FontPatcher = "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/FontPatcher.zip"
def downloadPatcher():
if not os.path.exists("assets"): os.mkdir("assets")
if not os.path.exists("assets/NerdFontPatcher_extract"):
if not os.path.exists("assets/NerdFontPatcher.zip"):
wgetHandler.download(link_FontPatcher,"assets/NerdFontPatcher.zip")
print("Unzipping NerdFontPatcher.zip",end="")
with zipfile.ZipFile("assets/NerdFontPatcher.zip", 'r') as zip_ref:
extractName = zip_ref.extractall("assets/NerdFontPatcher_extract")
print(" [OK]")
return "assets/NanumSquareNeoKr.ttf"
if __name__ == "__main__": downloadPatcher()

2
src/utility/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .utility import *

View File

@ -1,4 +1,5 @@
import math
import psMat
def setWidthWithSavingPosition(font,targetWidth):
for glyph in font.selection.byGlyphs:
@ -7,3 +8,7 @@ def setWidthWithSavingPosition(font,targetWidth):
glyph.left_side_bearing = int(glyph.left_side_bearing + math.floor(sideAdjust)) # 좌우 베어링을 조정함
glyph.right_side_bearing = int(glyph.right_side_bearing + math.ceil(sideAdjust))
glyph.width = targetWidth # 타겟 너비로 정확하게 설정
def scale(font,targetScale):
font.transform(psMat.scale(targetScale))
font.round()

@ -1 +0,0 @@
Subproject commit fdd3a0f8404ccab90f939f9952af139e6c55142a

View File

@ -0,0 +1,2 @@
from .wgetHandler import *

View File

@ -0,0 +1,95 @@
Usage
=====
python -m wget [options] <URL>
options:
-o --output FILE|DIR output filename or directory
API Usage
=========
>>> import wget
>>> url = 'http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3'
>>> filename = wget.download(url)
100% [................................................] 3841532 / 3841532>
>> filename
'razorback.mp3'
The skew that you see above is a documented side effect.
Alternative progress bar:
>>> wget.download(url, bar=bar_thermometer)
ChangeLog
=========
2.2 (2014-07-19)
* it again can download without -o option
2.1 (2014-07-10)
* it shows command line help
* -o option allows to select output file/directory
* download(url, out, bar) contains out parameter
2.0 (2013-04-26)
* it shows percentage
* it has usage examples
* it changes if being used as a library
* download shows progress bar by default
* bar_adaptive gets improved algorithm
* download(url, bar) contains bar parameter
* bar(current, total)
* progress_callback is named callback_progress
1.0 (2012-11-13)
* it runs with Python 3
0.9 (2012-11-13)
* it renames file if it already exists
* it can be used as a library
* download(url) returns filename
* bar_adaptive() draws progress bar
* bar_thermometer() simplified bar
0.8 (2011-05-03)
* it detects filename from HTTP headers
0.7 (2011-03-01)
* compatibility fix for Python 2.5
* limit width of progress bar to 100 chars
0.6 (2010-04-24)
* it detects console width on POSIX
0.5 (2010-04-23)
* it detects console width on Windows
0.4 (2010-04-15)
* it shows cute progress bar
0.3 (2010-04-05)
* it creates temp file in current dir
0.2 (2010-02-16)
* it tries to detect filename from URL
0.1 (2010-02-04)
* it can download file
Release Checklist
=================
| [ ] update version in wget.py
| [x] update description in setup.py
| [ ] python setup.py check -mrs
| [ ] python setup.py sdist upload
| [ ] tag hg version
--
anatoly techtonik <techtonik@gmail.com>

View File

@ -0,0 +1,37 @@
from distutils.core import setup
def get_version(relpath):
"""read version info from file without importing it"""
from os.path import dirname, join
for line in open(join(dirname(__file__), relpath)):
if '__version__' in line:
if '"' in line:
# __version__ = "0.9"
return line.split('"')[1]
elif "'" in line:
return line.split("'")[1]
setup(
name='wget',
version=get_version('wget.py'),
author='anatoly techtonik <techtonik@gmail.com>',
url='http://bitbucket.org/techtonik/python-wget/',
description="pure python download utility",
license="Public Domain",
classifiers=[
'Environment :: Console',
'License :: Public Domain',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Networking',
'Topic :: Utilities',
],
py_modules=['wget'],
long_description=open('README.txt').read(),
)

402
src/wgetHandler/wget/wget.py Executable file
View File

@ -0,0 +1,402 @@
#!/usr/bin/env python
"""
Download utility as an easy way to get file from the net
python -m wget <URL>
python wget.py <URL>
Downloads: http://pypi.python.org/pypi/wget/
Development: http://bitbucket.org/techtonik/python-wget/
wget.py is not option compatible with Unix wget utility,
to make command line interface intuitive for new people.
Public domain by anatoly techtonik <techtonik@gmail.com>
Also available under the terms of MIT license
Copyright (c) 2010-2014 anatoly techtonik
"""
import sys, shutil, os
import tempfile
import math
PY3K = sys.version_info >= (3, 0)
if PY3K:
import urllib.request as urllib
import urllib.parse as urlparse
else:
import urllib
import urlparse
__version__ = "2.3-beta1"
def filename_from_url(url):
""":return: detected filename or None"""
fname = os.path.basename(urlparse.urlparse(url).path)
if len(fname.strip(" \n\t.")) == 0:
return None
return fname
def filename_from_headers(headers):
"""Detect filename from Content-Disposition headers if present.
http://greenbytes.de/tech/tc2231/
:param: headers as dict, list or string
:return: filename from content-disposition header or None
"""
if type(headers) == str:
headers = headers.splitlines()
if type(headers) == list:
headers = dict([x.split(':', 1) for x in headers])
cdisp = headers.get("Content-Disposition")
if not cdisp:
return None
cdtype = cdisp.split(';')
if len(cdtype) == 1:
return None
if cdtype[0].strip().lower() not in ('inline', 'attachment'):
return None
# several filename params is illegal, but just in case
fnames = [x for x in cdtype[1:] if x.strip().startswith('filename=')]
if len(fnames) > 1:
return None
name = fnames[0].split('=')[1].strip(' \t"')
name = os.path.basename(name)
if not name:
return None
return name
def filename_fix_existing(filename):
"""Expands name portion of filename with numeric ' (x)' suffix to
return filename that doesn't exist already.
"""
dirname = '.'
name, ext = filename.rsplit('.', 1)
names = [x for x in os.listdir(dirname) if x.startswith(name)]
names = [x.rsplit('.', 1)[0] for x in names]
suffixes = [x.replace(name, '') for x in names]
# filter suffixes that match ' (x)' pattern
suffixes = [x[2:-1] for x in suffixes
if x.startswith(' (') and x.endswith(')')]
indexes = [int(x) for x in suffixes
if set(x) <= set('0123456789')]
idx = 1
if indexes:
idx += sorted(indexes)[-1]
return '%s (%d).%s' % (name, idx, ext)
# --- terminal/console output helpers ---
def get_console_width():
"""Return width of available window area. Autodetection works for
Windows and POSIX platforms. Returns 80 for others
Code from http://bitbucket.org/techtonik/python-pager
"""
if os.name == 'nt':
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
# get console handle
from ctypes import windll, Structure, byref
try:
from ctypes.wintypes import SHORT, WORD, DWORD
except ImportError:
# workaround for missing types in Python 2.5
from ctypes import (
c_short as SHORT, c_ushort as WORD, c_ulong as DWORD)
console_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
# CONSOLE_SCREEN_BUFFER_INFO Structure
class COORD(Structure):
_fields_ = [("X", SHORT), ("Y", SHORT)]
class SMALL_RECT(Structure):
_fields_ = [("Left", SHORT), ("Top", SHORT),
("Right", SHORT), ("Bottom", SHORT)]
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
_fields_ = [("dwSize", COORD),
("dwCursorPosition", COORD),
("wAttributes", WORD),
("srWindow", SMALL_RECT),
("dwMaximumWindowSize", DWORD)]
sbi = CONSOLE_SCREEN_BUFFER_INFO()
ret = windll.kernel32.GetConsoleScreenBufferInfo(console_handle, byref(sbi))
if ret == 0:
return 0
return sbi.srWindow.Right+1
elif os.name == 'posix':
from fcntl import ioctl
from termios import TIOCGWINSZ
from array import array
winsize = array("H", [0] * 4)
try:
ioctl(sys.stdout.fileno(), TIOCGWINSZ, winsize)
except IOError:
pass
return (winsize[1], winsize[0])[0]
return 80
def bar_thermometer(current, total, width=80):
"""Return thermometer style progress bar string. `total` argument
can not be zero. The minimum size of bar returned is 3. Example:
[.......... ]
Control and trailing symbols (\r and spaces) are not included.
See `bar_adaptive` for more information.
"""
# number of dots on thermometer scale
avail_dots = width-2
shaded_dots = int(math.floor(float(current) / total * avail_dots))
return '[' + '.'*shaded_dots + ' '*(avail_dots-shaded_dots) + ']'
def bar_adaptive(current, total, width=80):
"""Return progress bar string for given values in one of three
styles depending on available width:
[.. ] downloaded / total
downloaded / total
[.. ]
if total value is unknown or <= 0, show bytes counter using two
adaptive styles:
%s / unknown
%s
if there is not enough space on the screen, do not display anything
returned string doesn't include control characters like \r used to
place cursor at the beginning of the line to erase previous content.
this function leaves one free character at the end of string to
avoid automatic linefeed on Windows.
"""
# process special case when total size is unknown and return immediately
if not total or total < 0:
msg = "%s / unknown" % current
if len(msg) < width: # leaves one character to avoid linefeed
return msg
if len("%s" % current) < width:
return "%s" % current
# --- adaptive layout algorithm ---
#
# [x] describe the format of the progress bar
# [x] describe min width for each data field
# [x] set priorities for each element
# [x] select elements to be shown
# [x] choose top priority element min_width < avail_width
# [x] lessen avail_width by value if min_width
# [x] exclude element from priority list and repeat
# 10% [.. ] 10/100
# pppp bbbbb sssssss
min_width = {
'percent': 4, # 100%
'bar': 3, # [.]
'size': len("%s" % total)*2 + 3, # 'xxxx / yyyy'
}
priority = ['percent', 'bar', 'size']
# select elements to show
selected = []
avail = width
for field in priority:
if min_width[field] < avail:
selected.append(field)
avail -= min_width[field]+1 # +1 is for separator or for reserved space at
# the end of line to avoid linefeed on Windows
# render
output = ''
for field in selected:
if field == 'percent':
# fixed size width for percentage
output += ('%s%%' % (100 * current // total)).rjust(min_width['percent'])
elif field == 'bar': # [. ]
# bar takes its min width + all available space
output += bar_thermometer(current, total, min_width['bar']+avail)
elif field == 'size':
# size field has a constant width (min == max)
output += ("%s / %s" % (current, total)).rjust(min_width['size'])
selected = selected[1:]
if selected:
output += ' ' # add field separator
return output
# --/ console helpers
__current_size = 0 # global state variable, which exists solely as a
# workaround against Python 3.3.0 regression
# http://bugs.python.org/issue16409
# fixed in Python 3.3.1
def callback_progress(blocks, block_size, total_size, bar_function):
"""callback function for urlretrieve that is called when connection is
created and when once for each block
draws adaptive progress bar in terminal/console
use sys.stdout.write() instead of "print,", because it allows one more
symbol at the line end without linefeed on Windows
:param blocks: number of blocks transferred so far
:param block_size: in bytes
:param total_size: in bytes, can be -1 if server doesn't return it
:param bar_function: another callback function to visualize progress
"""
global __current_size
width = min(100, get_console_width())
if sys.version_info[:3] == (3, 3, 0): # regression workaround
if blocks == 0: # first call
__current_size = 0
else:
__current_size += block_size
current_size = __current_size
else:
current_size = min(blocks*block_size, total_size)
progress = bar_function(current_size, total_size, width)
if progress:
sys.stdout.write("\r" + progress)
class ThrowOnErrorOpener(urllib.FancyURLopener):
def http_error_default(self, url, fp, errcode, errmsg, headers):
raise Exception("%s: %s" % (errcode, errmsg))
def download(url, out=None, bar=bar_adaptive):
"""High level function, which downloads URL into tmp file in current
directory and then renames it to filename autodetected from either URL
or HTTP headers.
:param bar: function to track download progress (visualize etc.)
:param out: output filename or directory
:return: filename where URL is downloaded to
"""
names = dict()
names["out"] = out or ''
names["url"] = filename_from_url(url)
# get filename for temp file in current directory
prefix = (names["url"] or names["out"] or ".") + "."
(fd, tmpfile) = tempfile.mkstemp(".tmp", prefix=prefix, dir=".")
os.close(fd)
os.unlink(tmpfile)
# set progress monitoring callback
def callback_charged(blocks, block_size, total_size):
# 'closure' to set bar drawing function in callback
callback_progress(blocks, block_size, total_size, bar_function=bar)
if bar:
callback = callback_charged
else:
callback = None
(tmpfile, headers) = ThrowOnErrorOpener().retrieve(url, tmpfile, callback)
names["header"] = filename_from_headers(headers)
if os.path.isdir(names["out"]):
filename = names["header"] or names["url"]
filename = names["out"] + "/" + filename
else:
filename = names["out"] or names["header"] or names["url"]
# add numeric ' (x)' suffix if filename already exists
if os.path.exists(filename):
filename = filename_fix_existing(filename)
shutil.move(tmpfile, filename)
#print headers
return filename
usage = """\
usage: wget.py [options] URL
options:
-o --output FILE|DIR output filename or directory
-h --help
--version
"""
if __name__ == "__main__":
if len(sys.argv) < 2 or "-h" in sys.argv or "--help" in sys.argv:
sys.exit(usage)
if "--version" in sys.argv:
sys.exit("wget.py " + __version__)
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-o", "--output", dest="output")
(options, args) = parser.parse_args()
url = sys.argv[1]
filename = download(args[0], out=options.output)
print("")
print("Saved under %s" % filename)
r"""
features that require more tuits for urlretrieve API
http://www.python.org/doc/2.6/library/urllib.html#urllib.urlretrieve
[x] autodetect filename from URL
[x] autodetect filename from headers - Content-Disposition
http://greenbytes.de/tech/tc2231/
[ ] make HEAD request to detect temp filename from Content-Disposition
[ ] process HTTP status codes (i.e. 404 error)
http://ftp.de.debian.org/debian/pool/iso-codes_3.24.2.orig.tar.bz2
[ ] catch KeyboardInterrupt
[ ] optionally preserve incomplete file
[x] create temp file in current directory
[ ] resume download (broken connection)
[ ] resume download (incomplete file)
[x] show progress indicator
http://mail.python.org/pipermail/tutor/2005-May/038797.html
[x] do not overwrite downloaded file
[x] rename file automatically if exists
[x] optionally specify path for downloaded file
[ ] options plan
[x] -h, --help, --version (CHAOS speccy)
[ ] clpbar progress bar style
_ 30.0Mb at 3.0 Mbps eta: 0:00:20 30% [===== ]
[ ] test "bar \r" print with \r at the end of line on Windows
[ ] process Python 2.x urllib.ContentTooShortError exception gracefully
(ideally retry and continue download)
(tmpfile, headers) = urllib.urlretrieve(url, tmpfile, callback_progress)
File "C:\Python27\lib\urllib.py", line 93, in urlretrieve
return _urlopener.retrieve(url, filename, reporthook, data)
File "C:\Python27\lib\urllib.py", line 283, in retrieve
"of %i bytes" % (read, size), result)
urllib.ContentTooShortError: retrieval incomplete: got only 15239952 out of 24807571 bytes
[ ] find out if urlretrieve may return unicode headers
[ ] test suite for unsafe filenames from url and from headers
[ ] security checks
[ ] filename_from_url
[ ] filename_from_headers
[ ] MITM redirect from https URL
[ ] https certificate check
[ ] size+hash check helpers
[ ] fail if size is known and mismatch
[ ] fail if hash mismatch
"""

View File

@ -1,6 +1,7 @@
from .wget import wget
import math
from .wget import wget
# bar 를 커스텀.... 하는 어떤 글에서 가져온거
def bar_custom(current, total, width=80):
width=30