refactor: Rewrite all codes, adding noto mono patchs
parent
697f7ace59
commit
9816ee7f6d
File diff suppressed because it is too large
Load Diff
9
Makefile
9
Makefile
|
@ -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
|
||||
|
|
4
build.py
4
build.py
|
@ -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)
|
||||
|
|
18
config.py
18
config.py
|
@ -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 패치를 적용할지에 대한
|
||||
# 여부입니다. 폰트 용량이 매우 커집니다!!
|
||||
# 웹용 폰트의 경우 끄는것을 추천합니다
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
58
src/build.py
58
src/build.py
|
@ -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()
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
patchVersion = 2 # 업데이트 후 캐시를 무시하기 위해서 사용
|
||||
|
||||
from . import select as selectGlyphs
|
||||
from .download import download
|
||||
from .cacheBuilder import getCachedFont
|
||||
from .patcher import pasteGlyphs
|
|
@ -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
|
|
@ -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"
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
from .download import downloadPatcher
|
||||
from .download import nerdFonts_Download_Test
|
|
@ -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()
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
patchVersion = 2
|
||||
|
||||
from .download import download
|
||||
from . import select as selectGlyphs
|
||||
from .cacheBuilder import getCachedFont
|
||||
from .patcher import pasteGlyphs
|
|
@ -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
|
|
@ -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"
|
|
@ -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()
|
|
@ -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()
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
from .build import build
|
||||
|
||||
from .NerdFonts import nerdFonts_Download_Test
|
|
@ -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()
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
def pasteGlyphs(target,sourceList):
|
||||
|
||||
|
|
@ -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()
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
from .utility import *
|
|
@ -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
src/wget
1
src/wget
|
@ -1 +0,0 @@
|
|||
Subproject commit fdd3a0f8404ccab90f939f9952af139e6c55142a
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
from .wgetHandler import *
|
|
@ -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>
|
|
@ -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(),
|
||||
)
|
|
@ -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
|
||||
"""
|
|
@ -1,6 +1,7 @@
|
|||
from .wget import wget
|
||||
import math
|
||||
|
||||
from .wget import wget
|
||||
|
||||
# bar 를 커스텀.... 하는 어떤 글에서 가져온거
|
||||
def bar_custom(current, total, width=80):
|
||||
width=30
|
Loading…
Reference in New Issue