diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e02a972..8ea7b04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,16 @@ 않는다. `hwpx.TextExtractor`·`hwpx.doc_diff`·`hwpx.validate_package`처럼 `hwpx.tools`에 사는 이름은 처음 쓸 때 경고 없이 읽고, 같은 객체다. 위 `hwpx.hwp5`와 합쳐 `import hwpx`가 읽는 hwpx 모듈은 117개에서 97개로 준다. 저장은 여전히 처음 저장할 때 패키지 검증 도구를 읽는다. +- FormFit이 글꼴 12종(바탕·바탕체·돋움·돋움체·굴림·굴림체·궁서·궁서체·한컴 고딕·함초롬바탕·함초롬돋움·맑은 고딕)에서 + KS X 1001 특수 기호와 전각 문자, 라틴-1·라틴 확장-A·일반 문장 부호·통화 기호·수학 연산자·도형·기타 기호·딩뱃· + 반각/전각 형태 블록의 글자를 글꼴의 설계 폭으로 잰다. 전에는 흔한 문장 부호와 기호 몇십 자 말고는 글자 종류의 평균 폭을 + 썼다. 글꼴에 없는 글자는 한/글이 대신 그리는 글꼴(바탕·궁서와 -체 글꼴은 함초롬바탕, 굴림·돋움·맑은 고딕·한컴 + 고딕은 함초롬돋움, 함초롬 글꼴은 Segoe UI Symbol)의 폭을 그 글꼴 단위로 옮겨 쓴다. 이런 글자가 든 칸은 채우기 + 결과(글자 크기, 줄 수)가 달라질 수 있다. + - 기호 글꼴 Wingdings·Symbol도 문서가 글자를 두는 사용자 영역(U+F020–F0FF)의 설계 폭으로 잰다. 실제 너비를 쓰는 + Wingdings 글머리표(U+F09F)는 10 pt에서 456을 차지한다. + - HY헤드라인M도 글꼴의 설계 폭으로 잰다. 글꼴에 없는 글자와 `` ` ``는 함초롬바탕의 폭을 쓴다. + - 휴먼명조도 글꼴의 설계 폭으로 잰다. 글꼴에 없는 글자는 함초롬바탕의 폭을 쓴다. ### 고침 diff --git a/docs/architecture/module-ownership.json b/docs/architecture/module-ownership.json index 266e3e9d..190750a6 100644 --- a/docs/architecture/module-ownership.json +++ b/docs/architecture/module-ownership.json @@ -716,6 +716,12 @@ "disposition": "core", "approvedBy": "shape-curves-and-connectors", "rationale": "HwpxOxmlParagraph.add_curve and add_connector: builds hp:curve (CURVE segments through the given points, the box of the Catmull-Rom curve through them, the points relative to that box) and hp:connectLine (STRAIGHT or STROKE, NOARROW) whose ends sit at the middle of a side of two floating objects placed in the same frame, with the connector's start/end object references. A new module rather than more objects.py lines (the 1,600-line owner-file cap); the two builders are attached to HwpxOxmlParagraph as plain class attributes, the same escape valve as shape_position.py and drop_cap.py. Format-level only: no renderer, application workflow or agent policy." + }, + { + "path": "src/hwpx/form_fit/_glyph_table.py", + "disposition": "core", + "approvedBy": "formfit-glyph-widths", + "rationale": "FormFit glyph widths beyond measure._GLYPHS: per face, the design advance of each glyph of the KS X 1001 symbols, the full-width forms and eight common Unicode blocks, read from the font files, and the face Hancom lays a glyph out from when a face lacks it. Data only; imports nothing." } ], "removed": [ diff --git a/src/hwpx/form_fit/_glyph_table.py b/src/hwpx/form_fit/_glyph_table.py new file mode 100644 index 00000000..da246701 --- /dev/null +++ b/src/hwpx/form_fit/_glyph_table.py @@ -0,0 +1,1444 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Design advances of glyphs beyond ``measure._GLYPHS``, per face, read from the fonts' ``hmtx`` and +``cmap`` tables: the KS X 1001 symbols and full-width forms, and the Latin-1, Latin Extended-A, General +Punctuation, Currency Symbols, Mathematical Operators, Geometric Shapes, Miscellaneous Symbols, Dingbats and +Halfwidth and Fullwidth Forms blocks (Hangul, Hanja and kana left out); for the symbol faces Wingdings and Symbol, the +private-use code points U+F020-F0FF their glyphs are stored at. A face lacking a glyph lays it out at its fallback +face's advance. The table is read from the font files, not written by hand, but for a few glyphs Hancom lays out +at an advance of its own (я in 한컴 고딕 and HY헤드라인M, for one), which they carry instead. +""" + +from __future__ import annotations + +#: Units per em of each face below. +UNITS_PER_EM: dict[str, int] = { + "바탕": 1024, + "바탕체": 1024, + "돋움": 1024, + "돋움체": 1024, + "굴림": 1024, + "굴림체": 1024, + "궁서": 1024, + "궁서체": 1024, + "한컴 고딕": 1000, + "함초롬바탕": 1000, + "함초롬돋움": 1000, + "맑은 고딕": 2048, + "Segoe UI Symbol": 2048, + "휴먼명조": 512, + "HY헤드라인M": 1024, + "Wingdings": 2048, + "Symbol": 2048, +} + +#: The face Hancom takes a glyph from when a face does not have it. +FALLBACK: dict[str, str] = { + "바탕": "함초롬바탕", + "궁서": "함초롬바탕", + "바탕체": "함초롬바탕", + "궁서체": "함초롬바탕", + "굴림체": "함초롬바탕", + "돋움체": "함초롬바탕", + "굴림": "함초롬돋움", + "돋움": "함초롬돋움", + "맑은 고딕": "함초롬돋움", + "한컴 고딕": "함초롬돋움", + "함초롬바탕": "Segoe UI Symbol", + "함초롬돋움": "Segoe UI Symbol", + "휴먼명조": "함초롬바탕", + "HY헤드라인M": "함초롬바탕", +} + +#: Per face, design advance -> the glyphs the face lays out at that advance. +ADVANCES: dict[str, dict[int, str]] = { + "바탕": { + 256: "¦", + 271: "′∙", + 299: "ìíîïĩīĭįıĺļľł‚‛", + 319: "ĵ˙", + 320: "˚˛", + 339: "‹›", + 341: "´¸∕", + 352: "ŀ", + 368: "²³", + 384: "\u00ad¯¹ÌÍÎÏĨĪĬĮİţťŧſˇ˘Ι•▪▫◦", + 401: "″", + 408: "ι", + 427: "¡ŕŗř", + 429: "ℓ", + 443: "ⁿ", + 448: "˝ζξ", + 468: "с", + 469: "Ĵźżž", + 475: "з", + 480: "ªº", + 497: "ε∫", + 498: "υ", + 512: "\u00a0¢£¤§¨«»¿àáâãäåāăąćĉċčśŝşšао‗„‾⁄∂", + 518: "э", + 532: "ç", + 533: "р", + 535: "ρ", + 539: "τ", + 543: "⅝", + 550: "З", + 555: "µèéêëðñòóôõöøùúûüþďđēĕėęěĝğġģĥħķĸńņňŋōŏőũūŭůűųδμοσеё", + 557: "α", + 560: "ν", + 561: "я", + 562: "₤√", + 569: "г", + 575: "γ", + 576: "¼½¾ijκ⅛⅜⅞", + 577: "х", + 579: "π", + 588: "б", + 591: "у", + 594: "ь", + 597: "ýÿŷ", + 601: "‡", + 607: "ч", + 608: "в†", + 617: "Ξ", + 619: "к", + 624: "Ρ", + 625: "Γ", + 627: "Р", + 628: "∆", + 631: "Ь", + 632: "т", + 638: "η", + 639: "♂", + 640: "¶ßĹĻĽĿŁŚŜŞŠŹŻŽΖλωГ‼₣", + 641: "χ", + 655: "и", + 660: "н", + 666: "Ч", + 670: "Ββ", + 674: "Δ₧", + 677: "φ", + 678: "п", + 683: "¥ÈÉÊËÝÞĆĈĊČĒĔĖĘĚŔŖŘŶŸΕΥЁБВЕСЭЯ", + 684: "∩", + 686: "д", + 690: "Ç€", + 700: "ΧХ", + 702: "ц", + 704: "ʼn―♪", + 705: "ъ", + 707: "ψф", + 709: "У", + 717: "Πл∏", + 725: "ÐÒÓÔÕÖØĎĐĜĞĠĢŌŎŐΘΟθО", + 728: "Т", + 733: "Τ", + 734: "Ω", + 736: "Ω", + 747: "♀", + 754: "ΦЛФ", + 759: "Ψ", + 768: "ÀÁÂÃÄÅÑÙÚÛÜĀĂĤĦIJĶŃŅŇŊŢŤŦŨŪŬŮŰŲΑΗΚΛΝАИЙН↕►◄♠", + 769: "Ą", + 780: "ж", + 781: "♫", + 786: "≈", + 788: "м", + 793: "◊♦", + 799: "П", + 800: "Σ", + 802: "©®", + 804: "¬", + 811: "ŵ", + 812: "♥", + 813: "Д", + 814: "Ъ", + 817: "ю", + 820: "Ц", + 832: "К∑−≠≡≤≥", + 864: "♣", + 865: "æ", + 870: "œ", + 896: "шы", + 911: "щ", + 922: "Ы", + 933: "∞", + 934: "ΜМ", + 936: "▼", + 938: "☼", + 960: "Œ", + 974: "∟", + 981: "Ŵ", + 1024: ( + "ÆːЖШЩЮй‥⁴₁₂₃₄₩℉№℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠" + "⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└" + "┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦" + "▧▨▩▬▶▷▽◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖" + "㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛" + "㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%" + "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a" + "bcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "바탕체": { + 512: ( + "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" + "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" + "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + ), + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad®²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘ" + "ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТ" + "УФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё―†‡‥′″⁴ⁿ₁₂₃₄€℉" + "ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝" + "⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑" + "┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣" + "▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑" + "㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖" + "㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝" + "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\" + "]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "돋움": { + 256: "¦ìíîïīĭĮįıĵĺļľł", + 299: "ÌÍÎĨĩĪĬİΙ‚‛∙", + 302: "Ï", + 320: "ŀ˚˛", + 325: "˙", + 330: "²³", + 341: "´¸ŕŗřţťŧ", + 344: "′", + 384: "¹ºſ˘ι•‹›∫▪▫◦", + 413: "ª", + 427: "¡", + 448: "ˇ˝", + 469: "„", + 512: "\u00a0¨«\u00ad¯»¼½¾ýÿijŷźżžζνξУух‗‾⁄ⁿ₧ℓ∕", + 513: "√", + 525: "ь", + 555: "Ĵķśŝşš", + 575: "⅜", + 576: "£¤§¶ĸγεκꆇ⅛⅝⅞∂", + 577: "βз″", + 581: "μ", + 585: "δ", + 588: "э", + 595: "я", + 597: "¢µ¿àáâãäåçèéêëùúûüāăąćĉċčēĕėęěĥħĹĻĽĿŁŢŤŦũūŭůűųΤτυχЗТЧаесчё", + 598: "бг", + 609: "ΓГ₣₤", + 610: "т", + 624: "ъ", + 625: "в", + 628: "∆", + 634: "ц", + 640: "¥ÝßðñòóôõöøþďđĝğġģńņňŋōŏőŶŸŹŻŽΖΥΧαηορσφХор‼♀♂", + 656: "н", + 657: "л", + 661: "п", + 669: "д", + 674: "и", + 678: "й", + 683: "ÀÁÂÃÄÅÈÉÊËÞĀĂĄĒĔĖĘĚŚŜŞŠΑΕΛΡЁАЕРЬ", + 691: "Σ", + 704: "ʼnθλπ―∏♪", + 719: "Π", + 720: "¬", + 722: "Δ", + 725: "ĶŔŖŘΒΚБВКЯ", + 732: "Л", + 736: "∩", + 749: "ψ", + 756: "Э", + 758: "м", + 760: "Ц", + 768: "ÇÑÙÚÛÜĆĈĊČĎĤĦIJŃŅŇŨŪŬŮŰŲŵΗΝΞΩωИЙНПС€Ω↕►◄♠", + 769: "Ŋ", + 770: "ÐĐ", + 781: "♫", + 782: "ф", + 787: "ж", + 789: "ΦФю", + 793: "◊♦", + 798: "ы", + 802: "©®", + 806: "Ъ", + 811: "ÒÓÔÕÖØŌŎŐΘΟО", + 812: "♥", + 814: "Ψ", + 832: "æД∑", + 845: "Ы", + 853: "ĜĞĠĢ−≈≠≡≤≥", + 859: "щ", + 861: "ш", + 864: "♣", + 896: "œΜМ", + 933: "∞", + 936: "▼", + 938: "☼", + 939: "Ŵ", + 957: "Œ", + 960: "ÆЖ", + 981: "₩", + 990: "ШЩ", + 995: "☃", + 1006: "∟", + 1015: "Ю", + 1024: ( + "ː‥⁴₁₂₃₄℉№℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧" + "⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛" + "├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦▧▨▩▬▶▷▽" + "◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠" + "㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢" + "㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&'()*+," + "-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh" + "ijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "돋움체": { + 512: ( + "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" + "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" + "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + ), + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad®²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘ" + "ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТ" + "УФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё―†‡‥′″⁴ⁿ₁₂₃₄€℉" + "ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝" + "⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑" + "┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣" + "▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑" + "㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖" + "㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝" + "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\" + "]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "굴림": { + 256: "¦ìíîïīĭįıĵĺļľł∙", + 293: "ĩ", + 299: "ÌÍÎÏĨĪĬĮİΙ", + 320: "ŀ˙˚˛", + 341: "¸ŕŗřţťŧ‚‛", + 384: "²³´¹ſˇ˘ι•′‹›∫▪▫◦", + 427: "¡⁄∕", + 448: "ªº˝ⁿ", + 512: "\u00a0¨«\u00ad¯»ýÿŷźżžζνξгух‗„″‾ℓ", + 555: "ĴķĸśŝşšκУк", + 561: "ь", + 562: "√", + 576: "¤§¶¼½¾γεзлчя†‡⅛⅜⅝⅞∂", + 577: "β", + 585: "δд", + 596: "Þ", + 597: "¢µ¿àáâãäåçèéêëùúûüāăąćĉċčēĕėęěĥħijĹĻĽĿŁŢŤŦũūŭůűųΤμτυχТЧаестё", + 609: "£ΓГ₣₤", + 623: "ц", + 628: "∆", + 640: "¥ÝßðñòóôõöøþďđĝğġģńņňŋōŏőŶŸŹŻŽΖΥΧαηορσφЗХбвийнопръэ‼♀♂", + 673: "Ь", + 683: "ÀÁÂÃÄÅÈÉÊËĀĂĄĒĔĖĘĚĶŚŜŞŠΕΚΛΡЁАБЕКР₧", + 704: "ΑΣΨθλπ―∏♪", + 719: "Π", + 720: "¬", + 725: "ŔŖŘΒВЯ", + 729: "Л", + 736: "ы∩", + 738: "ʼn", + 749: "ψ", + 756: "Ц", + 768: "ÇÐÑÙÚÛÜĆĈĊČĎĐĤĦIJŃŅŇŊŨŪŬŮŰŲŵΗΝΩωИЙНСЪЭжмф€Ω↕►◄♠", + 769: "Δ", + 781: "♫", + 790: "П", + 793: "◊♦", + 802: "©®", + 806: "æ", + 811: "ÒÓÔÕÖØŌŎŐΘΟΦО", + 812: "♥", + 832: "ΞДФшщ∑", + 853: "ĜĞĠĢ−≈≠≡≤≥", + 856: "М", + 864: "♣", + 865: "Ы", + 896: "œΜ", + 911: "ю", + 935: "∞", + 936: "▼", + 938: "☼", + 939: "Ŵ₩", + 960: "ÆШЩ", + 989: "Ж", + 995: "☃", + 1006: "∟", + 1024: ( + "ŒːЮ‥⁴₁₂₃₄℉№℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥" + "⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙" + "┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦▧▨▩▬▶" + "▷▽◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛" + "㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠" + "㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&'()*" + "+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdef" + "ghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "굴림체": { + 512: ( + "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" + "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" + "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + ), + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad®²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘ" + "ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТ" + "УФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё―†‡‥′″⁴ⁿ₁₂₃₄€℉" + "ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝" + "⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑" + "┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣" + "▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑" + "㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖" + "㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝" + "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\" + "]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "궁서": { + 256: "¦∙", + 320: "˙˚", + 341: "¸‚‛′", + 384: "´ˇ˘˛•‹›∕▪▫◦", + 427: "¡⁄", + 448: "\u00ad¯", + 512: "\u00a0¤¨ª²³¹º¼½¾ĵſ˝ζξ‗„‾ⁿ⅛⅜⅝⅞", + 538: "″ℓ", + 550: "«»", + 555: "ìíîïĩīĭįıĺļľŀł", + 557: "ι", + 563: "√", + 576: "冇", + 583: "∫", + 597: "¿ÌÍÎÏĨĪĬĮİŕŗřśŝşšţťŧźżž", + 602: "Ι", + 608: "∂", + 611: "‼", + 612: "δ", + 614: "τз", + 620: "г", + 631: "οо", + 640: "¢§çèéêëðćĉċčēĕėęěĴβθυбестхэё♀♂", + 663: "Ь", + 665: "ь", + 666: "н", + 675: "я", + 683: ( + "ÞàáâãäåñòóôõöøùúûüýþÿāăąďđĝğġģĥħķĸĹĻĽĿŁńņňʼnŋōŏőũūŭůűųŷΓΡαγηκ" + "μνρφГРУЧавдкруч₧" + ), + 684: "п", + 694: "₣", + 704: "£¬µ¶ΔΖΦσЗФЭ―₤∆♪", + 725: ( + "ÀÁÂÃÄÅÇÈÉÊËÐÒÓÔÕÖØÝĀĂĄĆĈĊČĎĐĒĔĖĘĚŌŎŐŚŜŞŠŢŤŦŶŸŹŻŽΑΒΕΛΟΣΤΥΧЁАБ" + "ВЕОСТХ€" + ), + 726: "Θ", + 733: "ий", + 736: "∩", + 738: "ц", + 742: "¥", + 746: "π", + 747: "м", + 748: "χ", + 751: "ъ", + 768: "ÑßĜĞĠĢĤĦĶŃŅŇŊŔŖŘΗΚΝλИЙКНПЦЯ₩↕≈►◄♠", + 779: "л", + 781: "♫", + 782: "Π", + 786: "ф", + 792: "Д", + 793: "◊♦", + 796: "ω", + 802: "©®", + 810: "Ξ", + 811: "ÙÚÛÜŨŪŬŮŰŲŵ", + 812: "♥", + 817: "Лш", + 818: "∏", + 832: "æΩψжΩ", + 853: "ŴΜМ−≠≡≤≥", + 862: "щ", + 864: "Ъ♣", + 896: "ы", + 905: "Ψ", + 909: "ю", + 919: "∑", + 923: "ij", + 935: "∞", + 936: "▼", + 938: "☼", + 971: "œ", + 983: "№", + 1024: ( + "ÆIJŒːЖШЩЫЮ‥⁴₁₂₃₄℉℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∟∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟" + "⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓" + "└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥" + "▦▧▨▩▬▶▷▽◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕" + "㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚" + "㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$" + "%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "궁서체": { + 512: ( + "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" + "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" + "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + ), + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad®²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘ" + "ΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТ" + "УФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё―†‡‥′″⁴ⁿ₁₂₃₄€℉" + "ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝" + "⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑" + "┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣" + "▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑" + "㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖" + "㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝" + "!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\" + "]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "한컴 고딕": { + 254: "ι", + 260: "²³¹", + 300: "´", + 317: "Ι", + 378: "ª", + 385: "º", + 436: "⁑", + 446: "¡§¸", + 448: "υ", + 473: "ν", + 477: "ψ", + 478: "\u00ad", + 486: "ː˛′″⁴ⁿ₁₂₃₄∂、。【】〔〕", + 501: "σ", + 525: "κ", + 529: "ε", + 544: "я", + 547: "γ", + 549: "β", + 552: "ρ", + 556: "θ", + 560: "δ", + 572: "Τ", + 575: "μ", + 581: "Υ", + 582: "ο", + 584: "Ζ", + 586: "•", + 588: "ζ", + 593: "λτ", + 595: "αη", + 600: "Λ", + 607: "Χ", + 608: "ξ", + 609: "Γ", + 620: "Σ", + 624: "ΞΦ", + 625: "Ε", + 637: "ω", + 650: "Ωπ", + 653: "ΑΔ", + 656: "φ", + 657: "Ψχ", + 660: "Ρ", + 664: "ΒΚ", + 666: "Π", + 669: "¿", + 672: "―", + 676: "ΘΟ", + 680: "€", + 694: "Η", + 728: "Ν", + 800: "¨¶øþ", + 826: "Μ", + 892: "¤®¼½¾ÆÐØÞßæðŒœ†‡™", + 932: ( + "đĦħıIJijĸĿŀŁłʼnŊŋŦŧˇ˘˙˚˝ЁАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгде" + "жзийклмнопрстуфхцчшщъыьэюё‥℉ℓ№℡ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∃∇∈∋∏∑√∝∞∠∥∧" + "∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙ" + "ⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳" + "┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000" + "〃〓㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿" + "㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾" + "㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGH" + "IJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + 970: "◉◯☐☑❍", + }, + "함초롬바탕": { + 0: "\u302a\u302b", + 157: "˙", + 168: "ː", + 257: "˛", + 264: "˚", + 286: "¦", + 287: "ìíîïĩīĭįıĺļΙ", + 288: "ĵ", + 289: "․‧", + 290: "⁚", + 291: "‚‛⁏⁝⁞", + 296: "´", + 300: "\u00a0", + 305: "ÌÍÎÏĨĪĬĮİ", + 315: "Ĵ", + 320: "¡²³¹⁴ⁿ₁₂₃₄", + 330: "ι", + 335: "‖′‵", + 336: "ſ", + 356: "ţŧ", + 364: "ł", + 369: "‣", + 374: "¨", + 377: "˘", + 386: "ˇ", + 396: "º‹›", + 399: "•", + 411: "„‟", + 422: "ľ", + 426: "ŀ", + 433: "ª", + 434: "ť", + 435: "˝", + 438: "‡", + 440: "«»", + 447: "τ‼", + 456: "\u00ad⁃", + 472: "⁅⁆", + 474: "″‶", + 478: "ŕŗř", + 480: "‽", + 485: ( + "〿。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロ" + "ワン゙゚ᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ│←↑→↓" + "■○" + ), + 486: "źżž⁎⁑", + 496: "śŝşš", + 498: "§", + 500: "【】〔〕〖〗〘〙〚〛", + 511: "₫", + 517: "₰", + 518: "её", + 524: "δ", + 525: "‸", + 528: "‐‑", + 531: "⁊", + 533: "ζξ", + 534: "сэ", + 536: "èéêëēĕėęěν", + 537: "β", + 538: "з⁖", + 540: "х", + 541: "у", + 542: "μ", + 543: "ýÿŷ", + 544: "ß", + 549: "ь", + 550: "µ¿", + 552: "çćĉċčρ", + 555: "ε⁒", + 558: "†⁕", + 560: "ŋа", + 561: "¢", + 562: "ĝğġģ", + 564: "¬", + 566: "κ", + 568: "£", + 569: "àáâãäåāăą", + 570: "ðοо₤", + 571: "б", + 572: "гт", + 573: "γ⁗", + 574: "ij", + 575: "¯Þ", + 576: "Ρ", + 579: "‗", + 580: "ķ", + 584: "IJ", + 587: "Β", + 588: "òóôõöōŏő", + 590: "η", + 591: "υ", + 592: "В", + 593: "θ", + 594: "‾", + 595: "₦", + 598: "к", + 599: "в", + 600: "φ", + 601: "₠₪", + 602: "З", + 603: "⁙", + 605: "ĹĻĽĿ", + 606: "¶Р₲", + 608: "þΖря‴‷₵", + 609: "ΕЁБЕ", + 610: "¤", + 612: "¸Γ", + 613: "₴", + 615: "ĸ", + 617: "χий", + 622: "Г", + 624: "⁈⁘", + 625: "‒", + 626: "ŹŻŽ", + 627: "ÈÉÊËĒĔĖĘĚŚŜŞŠ", + 630: "ч", + 632: "₣", + 634: "ц⁉", + 635: "ñùúûüĥńņňũūŭůűų⁄", + 637: "₱", + 638: "Ł", + 640: "đ‥", + 642: "ĶΚЯ", + 646: "ΤТ⁋⁌⁍", + 649: "Ь", + 650: "₭", + 654: "₮", + 655: "н", + 656: "€", + 657: "σ", + 658: "С", + 660: "ŔŖŘα", + 661: "ΞЭ", + 664: "ŢŤŦ", + 665: "ħлп₥", + 666: "ψ⁁₡₢", + 672: "К", + 683: "ĜĞĠĢ", + 684: "ď⁛", + 685: "ÇøĆĈĊČ", + 688: "⁓", + 695: "Δм", + 696: "ØΘΟО", + 697: "Π", + 700: "Ч", + 703: "ΥΧХ", + 704: "ΑА", + 705: "ÝʼnŶŸ", + 706: "ÀÁÂÃÄÅĀĂĄ", + 707: "¥λ", + 709: "ъ", + 710: "д", + 714: "πω", + 716: "ŊΗΝН", + 719: "ĎИЙ", + 720: "ŵ", + 721: "Ω", + 731: "ÙÚÛÜŨŪŬŮŰŲ", + 732: "ÒÓÔÕÖŌŎŐ", + 734: "ÑĤŃŅŇПЦ", + 735: "ΛЛ", + 744: "Д", + 746: "Ψ", + 747: "ΦФ", + 750: "У", + 755: "Σ", + 762: "Ħ", + 768: "Ð", + 769: "Đ⁜₳", + 770: "‿⁀⁔", + 781: "ж", + 789: "Ъ", + 794: "₩", + 795: "ю", + 799: "⁐", + 800: "©®", + 806: "ы", + 810: "₨", + 821: "ΜМ", + 825: "шщ", + 865: "ШЩ", + 874: "Ы", + 875: "―", + 877: "œ", + 878: "⁇", + 891: "æ", + 898: "ф", + 909: "⁂", + 910: "Ŵ", + 921: "Œ", + 929: "Ж", + 948: "Ю", + 956: "‱", + 957: "₧", + 959: "Æ", + 970: ( + "¼½¾\u205f₯℉ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡" + "∢∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝" + "≞≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙" + "⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕" + "⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬" + "⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠" + "┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱" + "▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◈◉◊◌◍◎◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳" + "◴◵◶◷◸◹◺◻◼◽◾◿☀☁☂☃☄☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☝☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳" + "☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯" + "♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮" + "⚯⚰⚱⚲✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼" + "✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵❶❷❸❹❺❻❼❽❾❿➀" + "➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶➷➸➹➺➻➼➽➾\u3000、" + "。〃〄々〆〇〒〓〜〝〞〟〠〡〢〣〤〥〦〧〨〩〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖" + "㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛" + "㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%" + "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a" + "bcdefghijklmnopqrstuvwxyz{|}~⦅⦆ᅠ¢£¬ ̄¦¥₩" + ), + 1012: "☜☞", + }, + "함초롬돋움": { + 0: "\u302a\u302b", + 134: "˙", + 203: "˛", + 207: "ː", + 226: "˚", + 237: "‐", + 240: "´", + 258: "ĺļ", + 268: "ĵ", + 271: "ìíîïĩīĭįı", + 287: "Ι", + 288: "‚‛․‧", + 290: "⁚", + 291: "⁝⁞", + 297: "⁏", + 300: "\u00a0‑", + 304: "′‵", + 309: "ſ", + 317: "ÌÍÎÏĨĪĬĮİ", + 320: "²³¹⁴ⁿ₁₂₃₄", + 330: "ι", + 333: "¡", + 334: "ł", + 338: "˘", + 343: "⁅⁆", + 348: "‾", + 349: "ţŧ", + 359: "⁄", + 361: "ľ", + 368: "‖", + 373: "¸ˇ", + 378: "ªť", + 382: "ŀ", + 385: "º", + 398: "‒", + 408: "˝", + 419: "‗", + 427: "ŕŗř", + 436: "⁑", + 447: "τ", + 448: "‡‹›", + 451: "„", + 464: "¦", + 466: "ýÿŷ", + 478: "\u00ad¯", + 479: "źżž", + 485: ( + "〿。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロ" + "ワン゙゚ᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ│←↑→↓" + "■○" + ), + 486: "§", + 488: "‟″‶", + 489: "ij", + 494: "‸", + 500: "ð【】〔〕〖〗〘〙〚〛", + 502: "‼", + 507: "₰", + 513: "çćĉċčśŝşš", + 514: "¢¨", + 515: "Ĵ", + 517: "⁁", + 518: "её†", + 524: "δ", + 531: "¬", + 533: "ζξ", + 534: "Þсэ", + 535: "«", + 536: "»ν", + 537: "β", + 538: "з⁖", + 540: "х", + 541: "þу", + 542: "μ", + 543: "¶⁋", + 544: "‥‷", + 546: "₮", + 549: "ь⁊", + 550: "‴", + 551: "⁎", + 552: "ρ", + 555: "¥µε", + 556: "£‽", + 558: "⁕", + 559: "ķĸ", + 560: "а", + 563: "àáâãäåāăąĝğġģ", + 566: "κ", + 567: "èéêëēĕėęě", + 570: "¤¿οо", + 571: "б", + 572: "Ŧгт", + 573: "ŢŤγ⁌⁍", + 576: "Ρ₤", + 581: "ÝŶŸ", + 582: "òóôõöøōŏő", + 583: "ß", + 584: "ĹĻĽĿŁŹŻŽ", + 586: "•", + 587: "Β", + 590: "η‣", + 591: "υ", + 592: "В", + 593: "θ", + 594: "ĥħ", + 595: "ñùúûüńņňŋũūŭůűų", + 598: "к₫", + 599: "в", + 600: "φ", + 602: "З", + 603: "⁙₡₢₵", + 604: "đ", + 606: "Р", + 607: "₴", + 608: "Ζря", + 609: "ΕЁБЕ", + 610: "⁒", + 612: "Γ", + 617: "ŚŜŞŠχий", + 622: "Г", + 624: "⁘", + 625: "ÈÉÊËĒĔĖĘĚ⁗", + 626: "€", + 630: "ч₣", + 631: "₳", + 633: "ÇĆĈĊČ", + 634: "ц", + 642: "ΚЯ", + 645: "ĜĞĠĢ", + 646: "ΤТ", + 647: "₪", + 649: "Ь", + 650: "₲", + 654: "ÀÁÂÃÄÅĀĂĄ", + 655: "н", + 657: "σ", + 658: "С", + 660: "α", + 661: "ΞЭ", + 662: "ď", + 663: "₭", + 664: "Ķ", + 665: "лп₠", + 666: "ψ", + 672: "К―", + 676: "ÒÓÔÕÖØŌŎŐ", + 681: "⁃", + 683: "ŔŖŘ", + 684: "⁛", + 688: "⁈⁓", + 693: "ʼn", + 694: "ĤĦ", + 695: "Δм", + 696: "ΘΟО", + 697: "Π", + 700: "Ч", + 701: "ÐĎĐ", + 702: "ÙÚÛÜŨŪŬŮŰŲ", + 703: "ΥΧХ", + 704: "ΑА", + 707: "λ", + 709: "ъ", + 710: "д₱", + 712: "⁉", + 714: "πω", + 716: "ΗΝН", + 719: "ИЙ", + 721: "Ω", + 728: "ÑŃŅŇŊ", + 734: "ПЦ₥", + 735: "ΛЛ", + 740: "₩", + 744: "Д", + 746: "Ψ", + 747: "ΦФ", + 750: "У", + 755: "Σ", + 760: "ŵ", + 764: "⁂", + 769: "⁜", + 770: "⁔", + 780: "₦", + 781: "ж", + 782: "IJ", + 789: "Ъ", + 795: "ю", + 806: "ы", + 821: "ΜМ", + 825: "шщ", + 847: "æ", + 858: "©®", + 865: "ШЩ", + 874: "Ы", + 897: "Ŵ", + 898: "ф", + 929: "Ж", + 930: "⁇₯", + 931: "₨", + 934: "Œ", + 937: "₧", + 941: "‿⁀⁐", + 948: "Ю", + 950: "œ", + 956: "‱", + 967: "Æ", + 970: ( + "¼½¾\u205f℉ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡∢" + "∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞" + "≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙⊚" + "⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖" + "⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭" + "⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡" + "┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▴" + "▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◈◉◊◌◍◎◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴" + "◵◶◷◸◹◺◻◼◽◾◿☀☁☂☃☄☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☝☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴" + "☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰" + "♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮⚯" + "⚰⚱⚲✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽" + "✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵❶❷❸❹❺❻❼❽❾❿➀➁" + "➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶➷➸➹➺➻➼➽➾\u3000、。" + "〃〄々〆〇〒〓〜〝〞〟〠〡〢〣〤〥〦〧〨〩〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗" + "㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜" + "㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&" + "'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab" + "cdefghijklmnopqrstuvwxyz{|}~⦅⦆ᅠ¢£¬ ̄¦¥₩" + ), + 1012: "☜☞", + }, + "맑은 고딕": { + 377: "˛", + 416: "˙", + 430: "¸", + 467: "ː", + 473: "‚′", + 490: "¦", + 499: "ł", + 504: "ìíîïı", + 552: "ι", + 553: "ÌÍÎÏΙ", + 590: "´˚", + 592: "¡", + 633: "˘", + 645: "∙", + 660: "‹›", + 670: "˝", + 679: "ℓ", + 695: "ŀ", + 706: "ŧ", + 720: "\u00a0", + 729: "⁴", + 732: "¹", + 764: "²³", + 776: "„″", + 778: "ˇ", + 783: "†‡", + 788: "₁₂₃₄", + 801: "г", + 816: "ⁿ", + 821: "ª", + 840: "\u00ad", + 855: "•", + 856: "т", + 864: "¨", + 887: "š", + 895: "‥", + 901: "¯", + 902: "º", + 917: "ε", + 925: "ζ", + 930: "ξ₮", + 932: "з", + 937: "§", + 942: "¿", + 946: "ž", + 952: "х", + 954: "₰", + 956: "¶", + 968: "çс", + 969: "э", + 983: "ĿŁ", + 985: "ΓГ", + 999: "〔〕", + 1006: "ij", + 1009: "ýÿу", + 1016: "τ", + 1021: "₣", + 1024: "ᅠᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ", + 1031: "₭", + 1036: "ĸк", + 1040: "λ", + 1048: "я", + 1052: "ь", + 1053: "«»", + 1059: "ÈÉÊËΕЁЕ", + 1061: "【】", + 1065: "àáâãäåа", + 1066: "Ξ", + 1077: "Σ", + 1084: "γ", + 1093: "ŦΤТ", + 1094: "κ", + 1096: "èéêëелё", + 1101: "ν", + 1102: "♩", + 1107: "в₪", + 1112: "Š", + 1116: "∮", + 1119: "∂", + 1122: "∃", + 1128: "¢£¥₤", + 1129: "χ€∫", + 1131: "ßЗ", + 1132: "₡₢", + 1140: "д", + 1141: "β", + 1152: "υ", + 1154: "ÝŸΥ", + 1168: "¤", + 1169: "ÞΡР", + 1170: "ð", + 1174: "♭", + 1183: "ħч", + 1184: "ñùúûüŋ‼", + 1186: "У", + 1192: "Б", + 1193: "ŽΖ", + 1195: "ΒВ", + 1202: "σ", + 1204: "ηЬ", + 1208: "µμнп", + 1209: "ΚК", + 1213: "б", + 1214: "ий", + 1217: "◌", + 1224: "δ", + 1226: "ρ", + 1227: "òóôõöøοо", + 1228: "θ₫", + 1229: "₱", + 1230: "þр", + 1231: "đΧХ", + 1234: "ъ", + 1235: "Я", + 1253: "ц", + 1262: "ʼn", + 1280: "♀", + 1288: "α", + 1292: "∑", + 1294: "Э", + 1300: "ÇС", + 1304: "₠", + 1309: "π", + 1313: "Λ", + 1322: "IJ", + 1347: "Δ", + 1348: "ÀÁÂÃÄÅΑА", + 1350: "₦", + 1365: "√", + 1366: "∠∴∵", + 1380: "Ч", + 1401: "∥∧∨∼∽≒≡", + 1406: "Л", + 1409: "♪", + 1425: "Å∀", + 1428: "ф", + 1435: "¬", + 1436: "≠≤≥", + 1439: "ÙÚÛÜ", + 1446: "Д", + 1460: "∇", + 1463: "φ", + 1464: "♂", + 1465: "м", + 1469: "Ð", + 1476: "Ъы", + 1484: "ĦΗН", + 1491: "ΠП", + 1511: "Ф", + 1534: "⁈⁉", + 1549: "Ц", + 1553: "ж", + 1564: "₩", + 1567: "ÑŊΝ", + 1568: "ИЙ", + 1570: "ψ", + 1576: "Φ", + 1580: "∏", + 1584: "ÒÓÔÕÖØΘΟО", + 1585: "ΩΩ", + 1624: "Ψ", + 1625: "™", + 1629: "Ы", + 1672: "ш", + 1695: "ω", + 1699: "ю", + 1716: "∝", + 1718: "∞", + 1721: "щ", + 1748: "æ", + 1781: "₨", + 1797: "Æ", + 1802: "₥", + 1804: "Ж", + 1870: "⅞", + 1872: "©®", + 1876: "¼", + 1878: "ΜМ", + 1884: "⁇", + 1909: "♬", + 1931: "⅛", + 1945: "¾", + 1946: "℉℡⅓⅔↔↕↖↗↘↙⇒⇔∈∋∩∪∬≪≫⊂⊃⊆⊇⊙⊥⌒", + 1950: "½œ", + 1954: "Œ", + 1982: "☃", + 1986: "Ш", + 2010: "⅜", + 2012: "⅝", + 2047: "Щ", + 2048: ( + "―⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎" + "┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊" + "╋▒▣▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♠♡♣♤♥♧♨\u3000、。〃〓㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘" + "㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝" + "㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&'" + "()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc" + "defghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + 2134: "Ю", + 2306: "₧", + 2347: "№", + 2354: "₯", + }, + "Segoe UI Symbol": { + 561: "\u202f", + 621: "❟", + 1024: "❕❗", + 1067: "❠", + 1128: "⚳⚴⚵⚷⚸", + 1401: "⚹⚺⚻⚼", + 1671: "⚝", + 1764: "⚾⛳⛵⛶⛽", + 1847: "⚽", + 1987: "⚶", + 2048: ( + "⚞⚟⚿⛀⛁⛂⛃⛄⛅⛆⛇⛈⛉⛊⛋⛌⛍⛎⛏⛐⛑⛒⛓⛔⛕⛖⛗⛘⛙⛚⛛⛜⛝⛞⛟⛠⛡⛢⛣⛤⛥⛦⛧⛨⛩⛪⛫⛬⛭⛮⛯⛰⛱⛲⛴⛷⛸⛹⛺⛻" + "⛼⛾⛿✀✅✊✋✨❌❎❓❔➕➖➗➰➿" + ), + }, + "Wingdings": { + 641: "\uf09e\uf0a0", + 789: "\uf0fa", + 937: "\uf073\uf09f\uf0a7", + 938: "\uf027", + 987: "\uf0f9", + 1086: "\uf07d\uf07e", + 1124: "\uf047\uf048", + 1135: "\uf035", + 1183: "\uf077", + 1203: "\uf041", + 1233: "\uf036", + 1301: "\uf0fb", + 1332: "\uf053", + 1352: "\uf04e", + 1381: "\uf043\uf044", + 1420: "\uf058", + 1431: "\uf032\uf033", + 1480: "\uf057", + 1529: "\uf055\uf056\uf06c\uf06e\uf074", + 1589: "\uf0e3\uf0e4\uf0e5\uf0e6", + 1597: "\uf069", + 1601: "\uf0f5\uf0f6\uf0f7\uf0f8", + 1609: "\uf0fc", + 1624: "\uf042", + 1627: "\uf059\uf0d7\uf0d8", + 1660: "\uf0f1\uf0f2\uf0f4", + 1665: "\uf054", + 1727: "\uf04a\uf04b\uf04c", + 1739: "\uf04f", + 1787: "\uf0eb\uf0ec\uf0ed\uf0ee", + 1803: "\uf052", + 1813: "\uf05a", + 1819: "\uf051", + 1825: ( + "\uf03b\uf03e\uf05d\uf06f\uf070\uf071\uf072\uf076\uf07a\uf0a1\uf0a2\uf0a3\uf0a4\uf0a5\uf0a8\uf0a9" + "\uf0aa\uf0ab\uf0ac\uf0ad\uf0ae\uf0af\uf0b0\uf0b1\uf0b2\uf0b3\uf0b4\uf0b5\uf0b6\uf0b7\uf0b8\uf0b9" + "\uf0ba\uf0bb\uf0bc\uf0bd\uf0be\uf0bf\uf0c0\uf0c1\uf0c2\uf0c3\uf0c4\uf0c5\uf0c6\uf0d9\uf0da\uf0db" + "\uf0dc\uf0dd\uf0de\uf0e1\uf0e2\uf0e9\uf0ea\uf0ef\uf0f0" + ), + 1826: ( + "\uf029\uf034\uf03c\uf03d\uf049\uf05b\uf07b\uf07c\uf080\uf081\uf082\uf083\uf084\uf085\uf086\uf087" + "\uf088\uf089\uf08a\uf08b\uf08c\uf08d\uf08e\uf08f\uf090\uf091\uf092\uf093\uf094\uf095\uf0fd\uf0fe" + ), + 1830: "\uf025", + 1834: "\uf05c", + 1863: "\uf03f\uf040", + 1902: "\uf066", + 1911: "\uf063", + 1924: "\uf03a", + 1927: "\uf062", + 1929: "\uf045\uf046", + 1937: "\uf064", + 1941: "\uf038", + 1951: "\uf06d\uf0a6", + 1974: "\uf060", + 2006: "\uf0df\uf0e0", + 2020: "\uf075", + 2048: ( + "\uf020\uf096\uf097\uf098\uf099\uf09a\uf09b\uf09c\uf09d\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d0\uf0d1" + "\uf0d2\uf0d3\uf0d4" + ), + 2099: "\uf065", + 2110: "\uf021", + 2119: "\uf0ff", + 2146: "\uf0c7\uf0c8\uf0c9\uf0ca", + 2150: "\uf06a", + 2159: "\uf05f", + 2171: "\uf078\uf079\uf0f3", + 2181: "\uf068", + 2186: "\uf0e7\uf0e8", + 2196: "\uf037", + 2208: "\uf039", + 2220: "\uf028", + 2230: "\uf050", + 2233: "\uf061", + 2245: "\uf030\uf067", + 2275: "\uf04d", + 2319: "\uf02a\uf02b", + 2344: "\uf022", + 2368: "\uf05e", + 2399: "\uf02c\uf02d", + 2492: "\uf026", + 2566: "\uf0d5\uf0d6", + 2603: "\uf06b", + 2665: "\uf023", + 2751: "\uf031", + 2752: "\uf024", + 2951: "\uf02e", + 2956: "\uf02f", + }, + "Symbol": { + 342: "\uf0a4", + 410: "\uf07c", + 506: "\uf0a2", + 512: "\uf020\uf02c\uf02e\uf0d7", + 561: "\uf0f2", + 569: "\uf02f\uf03a\uf03b", + 674: "\uf069\uf0e1\uf0f1", + 682: "\uf021\uf028\uf029\uf049\uf05b\uf05d", + 786: "\uf0e6\uf0e7\uf0e8\uf0e9\uf0ea\uf0eb\uf0f6\uf0f7\uf0f8\uf0f9\uf0fa\uf0fb", + 819: "\uf0b0", + 842: "\uf067\uf0b2", + 899: "\uf027\uf056\uf065\uf074", + 909: "\uf03f", + 942: "\uf0b7", + 983: "\uf07b\uf07d", + 1010: "\uf078", + 1012: "\uf064\uf07a\uf0b6\uf0e0\uf0ec\uf0ed\uf0ee\uf0ef\uf0fc\uf0fd\uf0fe", + 1024: "\uf023\uf02a\uf030\uf031\uf032\uf033\uf034\uf035\uf036\uf037\uf038\uf039\uf05f\uf060\uf0a6", + 1067: "\uf066\uf06e\uf071", + 1124: ( + "\uf024\uf02b\uf02d\uf03c\uf03d\uf03e\uf040\uf062\uf063\uf06b\uf06c\uf06f\uf070\uf072\uf07e\uf0a3" + "\uf0b1\uf0b3\uf0b4\uf0b8\uf0b9\uf0ba\uf0bb\uf0d6" + ), + 1139: "\uf052", + 1180: "\uf06d\uf075", + 1212: "\uf053", + 1235: "\uf047\uf068\uf06a\uf073\uf0ad\uf0af\uf0bd\uf0d9\uf0da\uf0dd\uf0df", + 1251: "\uf045\uf054\uf05a", + 1253: "\uf044", + 1270: "\uf0a1", + 1292: "\uf04a\uf061", + 1321: "\uf058", + 1348: "\uf05e\uf0bf", + 1366: "\uf042", + 1405: "\uf04c\uf077\uf079\uf0c1\uf0f3\uf0f4\uf0f5", + 1413: "\uf055", + 1460: "\uf022\uf076\uf0a5\uf0b5\uf0c9\uf0ca\uf0cb\uf0cc\uf0cd\uf0ce\uf0cf\uf0d1\uf0d8\uf0e5", + 1479: "\uf041\uf043\uf048\uf04b\uf04e\uf04f", + 1518: "\uf051", + 1542: "\uf0a7\uf0a8\uf0a9\uf0aa", + 1563: "\uf046", + 1573: "\uf050\uf057\uf0c4\uf0c5\uf0c7\uf0c8\uf0d0", + 1593: "\uf026", + 1610: "\uf0e4", + 1618: "\uf0d2\uf0d3\uf0e2\uf0e3", + 1628: "\uf059\uf0c2", + 1686: "\uf0c0\uf0c6\uf0d5", + 1706: "\uf025", + 1767: "\uf05c", + 1821: "\uf04d", + 1823: "\uf0d4", + 2021: "\uf0ac\uf0ae\uf0c3\uf0dc\uf0de", + 2048: "\uf0bc\uf0be", + 2134: "\uf0ab\uf0db", + }, + "HY헤드라인M": { + 512: ( + "\u00a0¢£¥¦©«¬®¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöù" + "úûüýÿ" + ), + 553: "я", + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘΙ" + "ΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТУ" + "ФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюё―†‡‥′″⁴ⁿ₁₂₃₄℉ℓ№℡" + "™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠" + "⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└" + "┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦" + "▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔" + "㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙" + "㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#" + "$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "휴먼명조": { + 512: ( + "¡¤§¨ª\u00ad²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘΙ" + "ΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТУ" + "ФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяё―†‡‥′″⁴ⁿ₁₂₃₄₩℉ℓ" + "№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞" + "⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒" + "┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤" + "▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒" + "㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗" + "㎘㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!" + ""#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]" + "^_`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, +} diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index 73183e24..7d4fcafd 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -35,6 +35,7 @@ from typing import Any, Literal from ..oxml.table_sizes import cell_margins_of, grid_widths_of +from ._glyph_table import ADVANCES, FALLBACK, UNITS_PER_EM # Advance width as a fraction of the em (font height in HWPUNIT). Hangul/wide are # exact (full-width cells); the Latin/digit/punct values are conservative class @@ -132,8 +133,10 @@ # at that em, rounded half up at 100% 장평 and down at any other 장평, and 자간 # adds its share of that advance, rounded half away from zero. The half-em space # is half the em, rounded down, at the 장평, rounded half up. Bold text keeps the -# regular advances. A face or glyph not listed below falls back to the class -# averages, unrounded. +# regular advances. A glyph a face lacks takes the advance of the face Hancom +# lays it out from (``_glyph_table.FALLBACK``), moved into the face's own units +# and rounded. A face or glyph listed nowhere falls back to the class averages, +# unrounded. _LAYOUT_UNIT = 4 _HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}" @@ -145,7 +148,8 @@ _GLYPHS = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~·…“”‘’「」『』〈〉《》※○●□■△▲◇◆☆★→←↑↓ㆍ×÷±°℃‰—–ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇' #: Design advances per face in font units: units per em, a Hangul syllable, the -#: space, and each glyph of ``_GLYPHS`` (0: not in the face). +#: space, and each glyph of ``_GLYPHS`` (0: not in the face, or one Hancom lays +#: out from the fallback face all the same). _DESIGN: dict[str, tuple[int, int, int, tuple[int, ...]]] = { "함초롬바탕": (1000, 970, 300, ( 320, 320, 610, 610, 830, 724, 320, 320, 320, 550, 550, 320, 550, 320, 550, 550, @@ -327,6 +331,36 @@ 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 0, 0, 0, 0, 0, )), + "HY헤드라인M": (1024, 1024, 341, ( + 256, 384, 853, 597, 810, 640, 213, 298, 298, 341, 597, 213, 597, 213, 384, 597, + 597, 597, 597, 597, 597, 597, 597, 597, 597, 256, 256, 469, 597, 469, 597, 853, + 597, 640, 640, 640, 469, 469, 640, 640, 256, 341, 597, 426, 810, 597, 597, 554, + 597, 597, 597, 512, 640, 640, 938, 597, 554, 469, 341, 384, 341, 554, 512, 0, + 597, 597, 597, 597, 640, 384, 597, 597, 256, 298, 554, 256, 853, 597, 640, 597, + 597, 426, 554, 384, 597, 554, 810, 512, 512, 426, 426, 298, 426, 725, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 1024, 1024, 0, 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 0, 0, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 0, 0, 1024, 1024, 1024, 1024, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 0, 0, 0, 0, 0, + 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 0, + 0, 0, 0, 0, + )), + "휴먼명조": (512, 512, 256, ( + 104, 256, 336, 258, 384, 394, 148, 160, 160, 256, 256, 138, 256, 138, 160, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 138, 138, 256, 256, 256, 220, 398, + 338, 308, 328, 348, 320, 298, 360, 348, 132, 202, 330, 296, 400, 354, 366, 294, + 352, 334, 254, 320, 346, 340, 478, 364, 344, 306, 164, 160, 164, 186, 256, 166, + 260, 278, 248, 260, 268, 200, 264, 284, 128, 136, 268, 128, 408, 282, 262, 276, + 276, 206, 210, 174, 276, 276, 370, 250, 262, 232, 150, 104, 150, 256, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, + 512, 512, 0, 0, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 0, 0, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 0, 0, 512, 512, 512, 512, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 0, 0, 0, 0, 0, + 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 512, 0, + 0, 0, 0, 0, + )), } @@ -337,22 +371,45 @@ def glyph_advance_em(face: str, ch: str) -> float | None: return design[0] / design[1] if design is not None else None +@lru_cache(maxsize=8192) def _design_units(face: str, ch: str | None) -> tuple[int, int] | None: """``(advance, units per em)`` of *ch* in *face*, a Hangul syllable when *ch* - is ``None``, or ``None`` when not listed.""" + is ``None``, or ``None`` when not listed. A glyph the face lacks takes the + advance of its fallback face, in the face's own units.""" entry = _DESIGN.get(face) - if entry is None: - return None - upem, hangul, space, glyphs = entry - if ch is None: - units = hangul - elif ch == " ": - units = space - else: - index = _GLYPHS.find(ch) if len(ch) == 1 else -1 - units = glyphs[index] if index >= 0 else 0 - return (units, upem) if units else None + if entry is None: # a symbol face only the glyph table lists (no Hangul, no space of its own) + units = _face_glyphs(face).get(ch, 0) if ch is not None and ch != " " else 0 + return (units, UNITS_PER_EM[face]) if units else None + upem, hangul, space, _ = entry + if ch is None or ch == " ": + return (hangul if ch is None else space), upem + source = face + while source: + units = _own_units(source, ch) + if units: + if source != face: + units = math.floor(Fraction(units * upem, UNITS_PER_EM[source]) + Fraction(1, 2)) + return units, upem + source = FALLBACK.get(source, "") + return None + + +def _own_units(face: str, ch: str) -> int: + """Design advance of *ch* in *face* itself; 0 when the face does not have it.""" + + entry = _DESIGN.get(face) + index = _GLYPHS.find(ch) if len(ch) == 1 else -1 + if entry is not None and index >= 0: + return entry[3][index] + return _face_glyphs(face).get(ch, 0) + + +@lru_cache(maxsize=None) +def _face_glyphs(face: str) -> dict[str, int]: + """Design advance of every glyph ``_glyph_table`` lists for *face*.""" + + return {ch: units for units, chars in ADVANCES.get(face, {}).items() for ch in chars} @lru_cache(maxsize=4096) diff --git a/tests/fixtures/hancom_saved/formfit_glyph_cells.hwpx b/tests/fixtures/hancom_saved/formfit_glyph_cells.hwpx new file mode 100644 index 00000000..b16bfdef Binary files /dev/null and b/tests/fixtures/hancom_saved/formfit_glyph_cells.hwpx differ diff --git a/tests/fixtures/hancom_saved/formfit_hy_headline_cells.hwpx b/tests/fixtures/hancom_saved/formfit_hy_headline_cells.hwpx new file mode 100644 index 00000000..cafe1805 Binary files /dev/null and b/tests/fixtures/hancom_saved/formfit_hy_headline_cells.hwpx differ diff --git a/tests/fixtures/hancom_saved/formfit_wingdings_label_rows.hwpx b/tests/fixtures/hancom_saved/formfit_wingdings_label_rows.hwpx new file mode 100644 index 00000000..57f3b73b Binary files /dev/null and b/tests/fixtures/hancom_saved/formfit_wingdings_label_rows.hwpx differ diff --git a/tests/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index 68d41025..21ef27e5 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -62,6 +62,8 @@ for name in ("margin_word", "space_without_spacing", "text_reaching_margin") ] LABELS = Path(__file__).parent / "fixtures" / "hancom_saved" / "pages_bullet_and_number_labels.hwpx" +WINGDINGS_LABEL = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_wingdings_label_rows.hwpx" +HY_HEADLINE_CELLS = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_hy_headline_cells.hwpx" NO_BREAK_SPACES = Path(__file__).parent / "fixtures" / "hancom_saved" / "pages_no_break_spaces.hwpx" FIXED_WIDTH_SPACES = Path(__file__).parent / "fixtures" / "hancom_saved" / "pages_fixed_width_spaces.hwpx" SCRIPT_ROWS = [ @@ -93,6 +95,61 @@ def test_each_character_can_take_its_own_size() -> None: assert hancom_line_starts("가나다라마", [5000], 10, style, sizes=[10, 10, 20, 10, 10]) == [0, 4] +def test_cells_of_symbols_break_where_hancom_breaks_them() -> None: + # Hancom laid this document out. Each cell holds a symbol and five Hangul syllables at one of the two + # widths where its line breaks change: symbols a face lacks (drawn from its fallback face, in faces of + # 1000, 1024 and 2048 units per em), the Greek of 함초롬돋움, 한컴 고딕's я, and design advances. + doc = HwpxDocument.open(Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_glyph_cells.hwpx") + cells = list(doc.oxml.sections[0].element.iter(f"{HP}tc")) + for cell in cells: + paragraph = cell.find(f"{HP}subList/{HP}p") + text = "".join(t.text or "" for t in paragraph.iter(f"{HP}t")) + ref = paragraph.find(f"{HP}run").get("charPrIDRef") + style = text_style_from_refs(doc.oxml, paragraph.get("paraPrIDRef"), [ref]) + margins = cell.find(f"{HP}cellMargin") + width = int(cell.find(f"{HP}cellSz").get("width")) - int(margins.get("left")) - int(margins.get("right")) + hancom = len(paragraph.findall(f"{HP}linesegarray/{HP}lineseg")) + assert estimate_lines(text, width, 10, style) == hancom, (style.glyph_face, f"U+{ord(text[0]):04X}", width) + assert len(cells) == 24 + + +def test_cells_in_hy_headline_break_where_hancom_breaks_them() -> None: + # Hancom laid this document out: each cell holds a glyph and five Hangul syllables in HY헤드라인M at one of the + # two widths where its line count changes, at 30 pt and at 10 pt. The glyphs take the face's design advance; + # those it lacks (₩ €) and the grave accent take 함초롬바탕's, moved into the face's units; я and ✀ take the + # advance Hancom gives them. + doc = HwpxDocument.open(HY_HEADLINE_CELLS) + cells = list(doc.oxml.sections[0].element.iter(f"{HP}tc")) + for cell in cells: + paragraph = cell.find(f"{HP}subList/{HP}p") + text = "".join(t.text or "" for t in paragraph.iter(f"{HP}t")) + ref = paragraph.find(f"{HP}run").get("charPrIDRef") + style = text_style_from_refs(doc.oxml, paragraph.get("paraPrIDRef"), [ref]) + points = int(doc.oxml.char_property(ref).attributes["height"]) / 100 + margins = cell.find(f"{HP}cellMargin") + width = int(cell.find(f"{HP}cellSz").get("width")) - int(margins.get("left")) - int(margins.get("right")) + hancom = len(paragraph.findall(f"{HP}linesegarray/{HP}lineseg")) + assert style.glyph_face == "HY헤드라인M" + assert estimate_lines(text, width, points, style) == hancom, (f"U+{ord(text[0]):04X}", points, width) + assert len(cells) == 22 + + +def test_human_myeongjo_takes_the_design_advances_of_its_font() -> None: + # 휴먼명조's font has 512 units per em: a Hangul syllable takes the full em, a parenthesis 160 units, the comma + # 138 and A 338. + style = TextStyle(hangul_face="휴먼명조", glyph_face="휴먼명조") + assert glyph_advance_em("휴먼명조", "(") == 160 / 512 + assert [char_advance(ch, 10, style) for ch in "가(,A"] == [1000, 312, 268, 660] + + +def test_every_fallback_face_has_a_table() -> None: + from hwpx.form_fit import _glyph_table as table + + for face, fallback in table.FALLBACK.items(): + assert face in table.ADVANCES and fallback in table.ADVANCES + assert face in table.UNITS_PER_EM and fallback in table.UNITS_PER_EM + + def test_each_character_can_take_its_own_style() -> None: wide = TextStyle(break_non_latin_word="KEEP_WORD") narrow = TextStyle(break_non_latin_word="KEEP_WORD", ratio=50) @@ -456,8 +513,9 @@ def test_a_numeral_a_face_does_not_list_is_full_width() -> None: def test_a_symbol_in_a_face_the_table_does_not_list_breaks_where_hancom_breaks_it() -> None: - # Five ○ in 휴먼명조 10 pt, which the glyph table does not list, in cells 4400 and 5400 wide inside: - # Hancom laid them out in two lines and in one. + # Five ○ at 10 pt in cells 4400 and 5400 wide inside: Hancom laid them out in two lines and in one. The + # cells are in 휴먼명조, which the glyph table lists, so they are measured with the face left unnamed as well: + # a face the table does not list draws ○ full width. doc = HwpxDocument.open(UNLISTED_SYMBOLS.read_bytes()) tables = [table for paragraph in doc.paragraphs for table in paragraph.tables] @@ -466,6 +524,9 @@ def test_a_symbol_in_a_face_the_table_does_not_list_breaks_where_hancom_breaks_i cell = table.cell(0, 0) segs = cell.paragraphs[0].element.findall(f"{HP}linesegarray/{HP}lineseg") slot = resolve_slot_metrics(cell, doc, max_lines=10, safety=1.0) + assert slot.text_style.hangul_face == "휴먼명조" + assert measure(cell.text, slot).lines == len(segs), (cell.width, len(segs)) + slot.text_style = replace(slot.text_style, hangul_face="", glyph_face="") assert glyph_advance_em(slot.text_style.hangul_face, "○") is None assert measure(cell.text, slot).lines == len(segs), (cell.width, len(segs)) lines.append(len(segs)) @@ -725,6 +786,18 @@ def test_the_rows_under_bullets_and_numbers_break_where_hancom_breaks() -> None: assert len(rows) == 168 assert [starts for starts, _ in rows[:144]] == [hancom for _, hancom in rows[:144]] + + +def test_a_wingdings_bullet_at_its_own_width_takes_the_glyph_design_advance() -> None: + # 25 rows of 120 syllables, 0.15 mm narrower one after another, under the bullet U+F09F in its own Wingdings + # shape with its own width on (useInstWidth): Hancom takes the glyph's design advance, 456 at 10 pt, and the + # half-em gap off every line, laid out and saved by Hancom. + rows = _row_line_starts(WINGDINGS_LABEL, labelled=True) + + assert len(rows) == 25 + assert [starts for starts, _ in rows] == [hancom for _, hancom in rows] + + def test_a_no_break_space_is_half_an_em_and_keeps_the_words_together() -> None: assert char_advance("\u00a0", 10, TextStyle()) == char_advance(" ", 10, TextStyle()) == 500 # "다라" and "마바" stay together: the line breaks at the space before them, not after the no-break space. diff --git a/tests/test_product_boundary.py b/tests/test_product_boundary.py index ed49badc..0eba3a82 100644 --- a/tests/test_product_boundary.py +++ b/tests/test_product_boundary.py @@ -318,7 +318,8 @@ def test_real_tree_gate_runs_from_a_gitless_source_copy(tmp_path: Path) -> None: # +1: the experimental page estimate (layout/pages.py), re-exported from hwpx.experimental. # +1: curves and connectors (oxml/curves.py) -- objects.py's overflow module under the # 1600-line cap. - assert report["classifiedFiles"] == 179 + # +1: FormFit's glyph width table (form_fit/_glyph_table.py), read from the font files. + assert report["classifiedFiles"] == 180 def test_gitless_cli_reproduces_literal_dynamic_import_failure_without_mutating_source(