From 50a208ae792433d07fc023499b993acc3acdb113 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Tue, 29 Sep 2026 20:10:22 +0900 Subject: [PATCH 1/6] feat(form_fit): measure KS X 1001 symbols and common blocks at each face's width FormFit took the design advance of a few dozen punctuation marks and symbols per face and class averages for everything else. It now reads the KS X 1001 symbols and full-width forms, and the Latin-1, Latin Extended-A, General Punctuation, Mathematical Operators, Geometric Shapes, Miscellaneous Symbols, Dingbats and Halfwidth and Fullwidth Forms blocks of twelve faces, from a table read from the font files (form_fit/_glyph_table.py). A glyph a face lacks takes the advance of the face Hancom lays it out from, moved into the face's own units. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 + docs/architecture/module-ownership.json | 6 + src/hwpx/form_fit/_glyph_table.py | 1216 +++++++++++++++++++++++ src/hwpx/form_fit/measure.py | 49 +- tests/test_form_fit_hancom_rules.py | 31 +- tests/test_product_boundary.py | 3 +- 6 files changed, 1297 insertions(+), 14 deletions(-) create mode 100644 src/hwpx/form_fit/_glyph_table.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b50dd71..29fa05af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,12 @@ 더한다. ### 바꿈 +- FormFit이 글꼴 12종(바탕·바탕체·돋움·돋움체·굴림·굴림체·궁서·궁서체·한컴 고딕·함초롬바탕·함초롬돋움·맑은 고딕)에서 + KS X 1001 특수 기호와 전각 문자, 라틴-1·라틴 확장-A·일반 문장 부호·수학 연산자·도형·기타 기호·딩뱃·반각/전각 + 형태 블록의 글자를 글꼴의 설계 폭으로 잰다. 전에는 흔한 문장 부호와 기호 몇십 자 말고는 글자 종류의 평균 폭을 + 썼다. 글꼴에 없는 글자는 한/글이 대신 그리는 글꼴(바탕·궁서와 -체 글꼴은 함초롬바탕, 굴림·돋움·맑은 고딕·한컴 + 고딕은 함초롬돋움, 함초롬 글꼴은 Segoe UI Symbol)의 폭을 그 글꼴 단위로 옮겨 쓴다. 이런 글자가 든 칸은 채우기 + 결과(글자 크기, 줄 수)가 달라질 수 있다. - FormFit이 칸의 높이 예산으로 저장된 안쪽 높이를 그대로 쓴다. 한/글은 그 높이까지 줄을 넣어도 행을 키우지 않고, 줄 간격은 한/글과 같게 센다. 폭의 안전 여유(0.93)는 그대로다. 전에는 높이에도 0.93을 곱해, 칸에 들어가는 값을 줄이거나 거부했다(예: 안쪽 2047에 1939를 쓰는 두 줄 값). diff --git a/docs/architecture/module-ownership.json b/docs/architecture/module-ownership.json index 55f38e5c..50b92e43 100644 --- a/docs/architecture/module-ownership.json +++ b/docs/architecture/module-ownership.json @@ -710,6 +710,12 @@ "disposition": "core", "approvedBy": "owner-page-estimate-2026-09-29", "rationale": "Experimental page estimate (re-exported from hwpx.experimental): follows each body paragraph onto its page and column from the document alone -- lines from a paragraph's valid layout cache or FormFit line breaking, line and paragraph spacing, page/column breaks and paragraph break rules, equal columns, inline and flowing tables, top-and-bottom objects and footnotes -- and reports anything else as unsupported with a reason. Reads OXML only; writes nothing." + }, + { + "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..a7e8f7b2 --- /dev/null +++ b/src/hwpx/form_fit/_glyph_table.py @@ -0,0 +1,1216 @@ +# 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, Mathematical Operators, Geometric Shapes, Miscellaneous Symbols, Dingbats and Halfwidth +and Fullwidth Forms blocks (Hangul, Hanja and kana left out). 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. +""" + +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, +} + +#: The face Hancom takes a glyph from when a face does not have it. +FALLBACK: dict[str, str] = { + "바탕": "함초롬바탕", + "궁서": "함초롬바탕", + "바탕체": "함초롬바탕", + "궁서체": "함초롬바탕", + "굴림체": "함초롬바탕", + "돋움체": "함초롬바탕", + "굴림": "함초롬돋움", + "돋움": "함초롬돋움", + "맑은 고딕": "함초롬돋움", + "한컴 고딕": "함초롬돋움", + "함초롬바탕": "Segoe UI Symbol", + "함초롬돋움": "Segoe UI Symbol", +} + +#: 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[\]^_`ab" + "cdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, + "바탕체": { + 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: "ÆЖ", + 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: "п", + 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: "【】〔〕〖〗〘〙〚〛", + 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: "‾", + 598: "к", + 599: "в", + 600: "φ", + 602: "З", + 603: "⁙", + 605: "ĹĻĽĿ", + 606: "¶Р", + 608: "þΖря‴‷", + 609: "ΕЁБЕ", + 610: "¤", + 612: "¸Γ", + 615: "ĸ", + 617: "χий", + 622: "Г", + 624: "⁈⁘", + 625: "‒", + 626: "ŹŻŽ", + 627: "ÈÉÊËĒĔĖĘĚŚŜŞŠ", + 630: "ч", + 634: "ц⁉", + 635: "ñùúûüĥńņňũūŭůűų⁄", + 638: "Ł", + 640: "đ‥", + 642: "ĶΚЯ", + 646: "ΤТ⁋⁌⁍", + 649: "Ь", + 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: "Ъ", + 795: "ю", + 799: "⁐", + 800: "©®", + 806: "ы", + 821: "ΜМ", + 825: "шщ", + 865: "ШЩ", + 874: "Ы", + 875: "―", + 877: "œ", + 878: "⁇", + 891: "æ", + 898: "ф", + 909: "⁂", + 910: "Ŵ", + 921: "Œ", + 929: "Ж", + 948: "Ю", + 956: "‱", + 959: "Æ", + 970: ( + "¼½¾\u205f℉ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡∢" + "∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞" + "≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙⊚" + "⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖" + "⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭" + "⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡" + "┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▴" + "▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◈◉◊◌◍◎◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴" + "◵◶◷◸◹◺◻◼◽◾◿☀☁☂☃☄☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☝☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴" + "☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰" + "♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮⚯" + "⚰⚱⚲✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽" + "✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵❶❷❸❹❺❻❼❽❾❿➀➁" + "➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶➷➸➹➺➻➼➽➾\u3000、。" + "〃〄々〆〇〒〓〜〝〞〟〠〡〢〣〤〥〦〧〨〩〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗" + "㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜" + "㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&" + "'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab" + "cdefghijklmnopqrstuvwxyz{|}~⦅⦆ᅠ¢£¬ ̄¦¥₩" + ), + 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: "‼", + 513: "çćĉċčśŝşš", + 514: "¢¨", + 515: "Ĵ", + 517: "⁁", + 518: "её†", + 524: "δ", + 531: "¬", + 533: "ζξ", + 534: "Þсэ", + 535: "«", + 536: "»ν", + 537: "β", + 538: "з⁖", + 540: "х", + 541: "þу", + 542: "μ", + 543: "¶⁋", + 544: "‥‷", + 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: "Р", + 608: "Ζря", + 609: "ΕЁБЕ", + 610: "⁒", + 612: "Γ", + 617: "ŚŜŞŠχий", + 622: "Г", + 624: "⁘", + 625: "ÈÉÊËĒĔĖĘĚ⁗", + 626: "€", + 630: "ч", + 633: "ÇĆĈĊČ", + 634: "ц", + 642: "ΚЯ", + 645: "ĜĞĠĢ", + 646: "ΤТ", + 649: "Ь", + 654: "ÀÁÂÃÄÅĀĂĄ", + 655: "н", + 657: "σ", + 658: "С", + 660: "α", + 661: "ΞЭ", + 662: "ď", + 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: "ΛЛ", + 744: "Д", + 746: "Ψ", + 747: "ΦФ", + 750: "У", + 755: "Σ", + 760: "ŵ", + 764: "⁂", + 769: "⁜", + 770: "⁔", + 781: "ж", + 782: "IJ", + 789: "Ъ", + 795: "ю", + 806: "ы", + 821: "ΜМ", + 825: "шщ", + 847: "æ", + 858: "©®", + 865: "ШЩ", + 874: "Ы", + 897: "Ŵ", + 898: "ф", + 929: "Ж", + 930: "⁇", + 934: "Œ", + 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: "х", + 956: "¶", + 968: "çс", + 969: "э", + 983: "ĿŁ", + 985: "ΓГ", + 999: "〔〕", + 1006: "ij", + 1009: "ýÿу", + 1016: "τ", + 1024: "ᅠᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ", + 1036: "ĸк", + 1040: "λ", + 1048: "я", + 1052: "ь", + 1053: "«»", + 1059: "ÈÉÊËΕЁЕ", + 1061: "【】", + 1065: "àáâãäåа", + 1066: "Ξ", + 1077: "Σ", + 1084: "γ", + 1093: "ŦΤТ", + 1094: "κ", + 1096: "èéêëелё", + 1101: "ν", + 1102: "♩", + 1107: "в", + 1112: "Š", + 1116: "∮", + 1119: "∂", + 1122: "∃", + 1128: "¢£¥", + 1129: "χ€∫", + 1131: "ßЗ", + 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: "θ", + 1230: "þр", + 1231: "đΧХ", + 1234: "ъ", + 1235: "Я", + 1253: "ц", + 1262: "ʼn", + 1280: "♀", + 1288: "α", + 1292: "∑", + 1294: "Э", + 1300: "ÇС", + 1309: "π", + 1313: "Λ", + 1322: "IJ", + 1347: "Δ", + 1348: "ÀÁÂÃÄÅΑА", + 1365: "√", + 1366: "∠∴∵", + 1380: "Ч", + 1401: "∥∧∨∼∽≒≡", + 1406: "Л", + 1409: "♪", + 1425: "Å∀", + 1428: "ф", + 1435: "¬", + 1436: "≠≤≥", + 1439: "ÙÚÛÜ", + 1446: "Д", + 1460: "∇", + 1463: "φ", + 1464: "♂", + 1465: "м", + 1469: "Ð", + 1476: "Ъы", + 1484: "ĦΗН", + 1491: "ΠП", + 1511: "Ф", + 1534: "⁈⁉", + 1549: "Ц", + 1553: "ж", + 1567: "ÑŊΝ", + 1568: "ИЙ", + 1570: "ψ", + 1576: "Φ", + 1580: "∏", + 1584: "ÒÓÔÕÖØΘΟО", + 1585: "ΩΩ", + 1624: "Ψ", + 1625: "™", + 1629: "Ы", + 1672: "ш", + 1695: "ω", + 1699: "ю", + 1716: "∝", + 1718: "∞", + 1721: "щ", + 1748: "æ", + 1797: "Æ", + 1804: "Ж", + 1870: "⅞", + 1872: "©®", + 1876: "¼", + 1878: "ΜМ", + 1884: "⁇", + 1909: "♬", + 1931: "⅛", + 1945: "¾", + 1946: "℉℡⅓⅔↔↕↖↗↘↙⇒⇔∈∋∩∪∬≪≫⊂⊃⊆⊇⊙⊥⌒", + 1950: "½œ", + 1954: "Œ", + 1982: "☃", + 1986: "Ш", + 2010: "⅜", + 2012: "⅝", + 2047: "Щ", + 2048: ( + "―⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎" + "┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊" + "╋▒▣▤▥▦▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♠♡♣♤♥♧♨\u3000、。〃〓㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘" + "㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝" + "㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&'" + "()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abc" + "defghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + 2134: "Ю", + 2347: "№", + }, + "Segoe UI Symbol": { + 561: "\u202f", + 621: "❟", + 1024: "❕❗", + 1067: "❠", + 1128: "⚳⚴⚵⚷⚸", + 1401: "⚹⚺⚻⚼", + 1671: "⚝", + 1764: "⚾⛳⛵⛶⛽", + 1847: "⚽", + 1987: "⚶", + 2048: ( + "⚞⚟⚿⛀⛁⛂⛃⛄⛅⛆⛇⛈⛉⛊⛋⛌⛍⛎⛏⛐⛑⛒⛓⛔⛕⛖⛗⛘⛙⛚⛛⛜⛝⛞⛟⛠⛡⛢⛣⛤⛥⛦⛧⛨⛩⛪⛫⛬⛭⛮⛯⛰⛱⛲⛴⛷⛸⛹⛺⛻" + "⛼⛾⛿✀✅✊✋✨❌❎❓❔➕➖➗➰➿" + ), + }, +} diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index ef5599fa..3957de7c 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -33,6 +33,7 @@ from typing import Any, Literal from ..oxml.table_sizes import cell_margins_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 @@ -117,8 +118,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 #: The glyphs each face's row below gives, in order: printable ASCII, then @@ -319,22 +322,44 @@ 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 + 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/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index 39f3eb99..a2c16c16 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -29,7 +29,14 @@ hancom_line_starts, measure, ) -from hwpx.form_fit.measure import classify_char, _cell_text_style, glyph_advance_em, resolve_slot_metrics, text_style_from_refs +from hwpx.form_fit.measure import ( + _cell_text_style, + char_advance, + classify_char, + glyph_advance_em, + resolve_slot_metrics, + text_style_from_refs, +) HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}" HANCOM_SAVED = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_line_rules.hwpx" @@ -64,6 +71,28 @@ def test_the_glyph_ending_a_line_takes_no_spacing() -> None: assert hancom_line_starts("가나다라마바사", [8300], 10, TextStyle(break_non_latin_word="KEEP_WORD", spacing=20)) == [0] +@pytest.mark.parametrize( + ("face", "glyph", "advance"), + [ + ("바탕", "∑", 812), # a KS X 1001 symbol at its design advance + ("맑은 고딕", "∮", 544), + ("함초롬돋움", "Ω", 720), # 함초롬돋움 lays Greek out at 함초롬바탕's advances + ("한컴 고딕", "я", 544), + ("바탕", "〄", 968), # 바탕 lacks it: 함초롬바탕's advance moved into 바탕's units + ], +) +def test_a_symbol_takes_the_advance_hancom_lays_it_out_at(face: str, glyph: str, advance: int) -> None: + assert char_advance(glyph, 10, TextStyle(glyph_face=face, hangul_face=face)) == advance + + +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_a_narrow_cell_still_gets_lines_1440_wide() -> None: doc = HwpxDocument.new() cell = doc.add_table(1, 1, width=700 + 2 * 510).cell(0, 0) diff --git a/tests/test_product_boundary.py b/tests/test_product_boundary.py index 10977402..5b2d4047 100644 --- a/tests/test_product_boundary.py +++ b/tests/test_product_boundary.py @@ -316,7 +316,8 @@ def test_real_tree_gate_runs_from_a_gitless_source_copy(tmp_path: Path) -> None: # +1: a paragraph shape's heading (oxml/paragraph_heading.py), written and read in # one place, outside header_part.py because that owner file sits at its 1600-line cap. # +1: the experimental page estimate (layout/pages.py), re-exported from hwpx.experimental. - assert report["classifiedFiles"] == 178 + # +1: FormFit's glyph width table (form_fit/_glyph_table.py), read from the font files. + assert report["classifiedFiles"] == 179 def test_gitless_cli_reproduces_literal_dynamic_import_failure_without_mutating_source( From cf0c478814de1a55ba36185022bc9501c45aa7e7 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Tue, 29 Sep 2026 21:52:51 +0900 Subject: [PATCH 2/6] test(form_fit): check the glyph table against a document Hancom laid out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The symbol widths test compared the table with values taken from the table. It is replaced by a fixture Hancom laid out and saved: each cell holds a symbol and five Hangul syllables at one of the two widths where its line breaks change, covering a face lacking the symbol (drawn from its fallback face, in faces of 1000, 1024 and 2048 units per em), the Greek of 함초롬돋움, 한컴 고딕's я and plain design advances. FormFit's line count matches Hancom's line cache in every cell; before the table 11 of the 24 cells differed. Co-Authored-By: Claude Opus 5.5 --- .../hancom_saved/formfit_glyph_cells.hwpx | Bin 0 -> 10464 bytes tests/test_form_fit_hancom_rules.py | 29 ++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 tests/fixtures/hancom_saved/formfit_glyph_cells.hwpx 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 0000000000000000000000000000000000000000..b16bfdef956fb13cda928400618f17d924e4d84e GIT binary patch literal 10464 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 CWb(%V literal 0 HcmV?d00001 diff --git a/tests/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index 5511d936..ba9bc8a7 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -31,7 +31,6 @@ ) from hwpx.form_fit.measure import ( _cell_text_style, - char_advance, classify_char, glyph_advance_em, resolve_slot_metrics, @@ -78,18 +77,22 @@ 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] -@pytest.mark.parametrize( - ("face", "glyph", "advance"), - [ - ("바탕", "∑", 812), # a KS X 1001 symbol at its design advance - ("맑은 고딕", "∮", 544), - ("함초롬돋움", "Ω", 720), # 함초롬돋움 lays Greek out at 함초롬바탕's advances - ("한컴 고딕", "я", 544), - ("바탕", "〄", 968), # 바탕 lacks it: 함초롬바탕's advance moved into 바탕's units - ], -) -def test_a_symbol_takes_the_advance_hancom_lays_it_out_at(face: str, glyph: str, advance: int) -> None: - assert char_advance(glyph, 10, TextStyle(glyph_face=face, hangul_face=face)) == advance +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_every_fallback_face_has_a_table() -> None: From 127585979fb49cd20b3a82312ca024e7888704b0 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Thu, 1 Oct 2026 09:13:52 +0900 Subject: [PATCH 3/6] feat(form_fit): lay out Wingdings and Symbol glyphs at their design advances The glyph table lists the symbol faces Wingdings and Symbol: their glyphs at the private-use code points U+F020-F0FF documents store them at, each at its design advance read from the font. A face only the glyph table lists has no Hangul or space of its own; its other characters keep the class averages. Co-Authored-By: Claude Opus 5.5 --- src/hwpx/form_fit/_glyph_table.py | 154 +++++++++++++++++++++++++++++- src/hwpx/form_fit/measure.py | 5 +- 2 files changed, 155 insertions(+), 4 deletions(-) diff --git a/src/hwpx/form_fit/_glyph_table.py b/src/hwpx/form_fit/_glyph_table.py index a7e8f7b2..09d679d3 100644 --- a/src/hwpx/form_fit/_glyph_table.py +++ b/src/hwpx/form_fit/_glyph_table.py @@ -2,8 +2,9 @@ """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, Mathematical Operators, Geometric Shapes, Miscellaneous Symbols, Dingbats and Halfwidth -and Fullwidth Forms blocks (Hangul, Hanja and kana left out). 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. +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. """ from __future__ import annotations @@ -23,6 +24,8 @@ "함초롬돋움": 1000, "맑은 고딕": 2048, "Segoe UI Symbol": 2048, + "Wingdings": 2048, + "Symbol": 2048, } #: The face Hancom takes a glyph from when a face does not have it. @@ -1213,4 +1216,151 @@ "⛼⛾⛿✀✅✊✋✨❌❎❓❔➕➖➗➰➿" ), }, + "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", + }, } diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index 270894e4..11e3dcfc 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -336,8 +336,9 @@ def _design_units(face: str, ch: str | None) -> tuple[int, int] | None: advance of its fallback face, in the face's own units.""" entry = _DESIGN.get(face) - if entry is None: - return 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 From 48b853f80351885b742fa6bc138e6fdbb8a7b5e0 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Thu, 1 Oct 2026 09:16:58 +0900 Subject: [PATCH 4/6] test(form_fit): a Wingdings bullet at its own width breaks rows like Hancom 25 rows under the bullet U+F09F in its own Wingdings shape with its own width on, laid out and saved by Hancom: the label takes the glyph's design advance (456 at 10 pt) and the half-em gap, and every row breaks where Hancom broke it. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ .../formfit_wingdings_label_rows.hwpx | Bin 0 -> 9443 bytes tests/test_form_fit_hancom_rules.py | 11 +++++++++++ 3 files changed, 13 insertions(+) create mode 100644 tests/fixtures/hancom_saved/formfit_wingdings_label_rows.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 988e4e2b..1167bb60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,8 @@ 썼다. 글꼴에 없는 글자는 한/글이 대신 그리는 글꼴(바탕·궁서와 -체 글꼴은 함초롬바탕, 굴림·돋움·맑은 고딕·한컴 고딕은 함초롬돋움, 함초롬 글꼴은 Segoe UI Symbol)의 폭을 그 글꼴 단위로 옮겨 쓴다. 이런 글자가 든 칸은 채우기 결과(글자 크기, 줄 수)가 달라질 수 있다. + - 기호 글꼴 Wingdings·Symbol도 문서가 글자를 두는 사용자 영역(U+F020–F0FF)의 설계 폭으로 잰다. 실제 너비를 쓰는 + Wingdings 글머리표(U+F09F)는 10 pt에서 456을 차지한다. - FormFit이 칸의 높이 예산으로 저장된 안쪽 높이를 그대로 쓴다. 한/글은 그 높이까지 줄을 넣어도 행을 키우지 않고, 줄 간격은 한/글과 같게 센다. 폭의 안전 여유(0.93)는 그대로다. 전에는 높이에도 0.93을 곱해, 칸에 들어가는 값을 줄이거나 거부했다(예: 안쪽 2047에 1939를 쓰는 두 줄 값). 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 0000000000000000000000000000000000000000..57f3b73b7854a83700531c28914f0fe7de463a86 GIT binary patch literal 9443 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 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. From cc9ab4c84e2ad78ca4f1396a1451aa563014bb5d Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:00:32 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat(form=5Ffit):=20lay=20out=20HY=ED=97=A4?= =?UTF-8?q?=EB=93=9C=EB=9D=BC=EC=9D=B8M=20and=20currency=20symbols=20at=20?= =?UTF-8?q?their=20design=20advances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HY헤드라인M joins the glyph table at its design advances. The glyphs it lacks take 함초롬바탕's advance moved into its units, and so does the grave accent; я and ✀ carry the advance Hancom lays them out at. Every text face also gains the Currency Symbols block (₩ and the rest), which the table left to the class averages. The new test reads 22 cells Hancom laid out and saved: a glyph and five Hangul syllables in HY헤드라인M at the two widths where the line count changes, at 30 pt and 10 pt. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 +- src/hwpx/form_fit/_glyph_table.py | 187 ++++++++++++------ src/hwpx/form_fit/measure.py | 18 +- .../formfit_hy_headline_cells.hwpx | Bin 0 -> 9867 bytes tests/test_form_fit_hancom_rules.py | 24 +++ 5 files changed, 168 insertions(+), 66 deletions(-) create mode 100644 tests/fixtures/hancom_saved/formfit_hy_headline_cells.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 1167bb60..44273757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -155,13 +155,14 @@ ### 바꿈 - FormFit이 글꼴 12종(바탕·바탕체·돋움·돋움체·굴림·굴림체·궁서·궁서체·한컴 고딕·함초롬바탕·함초롬돋움·맑은 고딕)에서 - KS X 1001 특수 기호와 전각 문자, 라틴-1·라틴 확장-A·일반 문장 부호·수학 연산자·도형·기타 기호·딩뱃·반각/전각 - 형태 블록의 글자를 글꼴의 설계 폭으로 잰다. 전에는 흔한 문장 부호와 기호 몇십 자 말고는 글자 종류의 평균 폭을 + KS X 1001 특수 기호와 전각 문자, 라틴-1·라틴 확장-A·일반 문장 부호·통화 기호·수학 연산자·도형·기타 기호·딩뱃· + 반각/전각 형태 블록의 글자를 글꼴의 설계 폭으로 잰다. 전에는 흔한 문장 부호와 기호 몇십 자 말고는 글자 종류의 평균 폭을 썼다. 글꼴에 없는 글자는 한/글이 대신 그리는 글꼴(바탕·궁서와 -체 글꼴은 함초롬바탕, 굴림·돋움·맑은 고딕·한컴 고딕은 함초롬돋움, 함초롬 글꼴은 Segoe UI Symbol)의 폭을 그 글꼴 단위로 옮겨 쓴다. 이런 글자가 든 칸은 채우기 결과(글자 크기, 줄 수)가 달라질 수 있다. - 기호 글꼴 Wingdings·Symbol도 문서가 글자를 두는 사용자 영역(U+F020–F0FF)의 설계 폭으로 잰다. 실제 너비를 쓰는 Wingdings 글머리표(U+F09F)는 10 pt에서 456을 차지한다. + - HY헤드라인M도 글꼴의 설계 폭으로 잰다. 글꼴에 없는 글자와 `` ` ``는 함초롬바탕의 폭을 쓴다. - FormFit이 칸의 높이 예산으로 저장된 안쪽 높이를 그대로 쓴다. 한/글은 그 높이까지 줄을 넣어도 행을 키우지 않고, 줄 간격은 한/글과 같게 센다. 폭의 안전 여유(0.93)는 그대로다. 전에는 높이에도 0.93을 곱해, 칸에 들어가는 값을 줄이거나 거부했다(예: 안쪽 2047에 1939를 쓰는 두 줄 값). diff --git a/src/hwpx/form_fit/_glyph_table.py b/src/hwpx/form_fit/_glyph_table.py index 09d679d3..dd239c8f 100644 --- a/src/hwpx/form_fit/_glyph_table.py +++ b/src/hwpx/form_fit/_glyph_table.py @@ -1,10 +1,11 @@ # 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, 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 +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. +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 @@ -24,6 +25,7 @@ "함초롬돋움": 1000, "맑은 고딕": 2048, "Segoe UI Symbol": 2048, + "HY헤드라인M": 1024, "Wingdings": 2048, "Symbol": 2048, } @@ -42,6 +44,7 @@ "한컴 고딕": "함초롬돋움", "함초롬바탕": "Segoe UI Symbol", "함초롬돋움": "Segoe UI Symbol", + "HY헤드라인M": "함초롬바탕", } #: Per face, design advance -> the glyphs the face lays out at that advance. @@ -81,7 +84,7 @@ 557: "α", 560: "ν", 561: "я", - 562: "√", + 562: "₤√", 569: "г", 575: "γ", 576: "¼½¾ijκ⅛⅜⅞", @@ -104,13 +107,13 @@ 632: "т", 638: "η", 639: "♂", - 640: "¶ßĹĻĽĿŁŚŜŞŠŹŻŽΖλωГ‼", + 640: "¶ßĹĻĽĿŁŚŜŞŠŹŻŽΖλωГ‼₣", 641: "χ", 655: "и", 660: "н", 666: "Ч", 670: "Ββ", - 674: "Δ", + 674: "Δ₧", 677: "φ", 678: "п", 683: "¥ÈÉÊËÝÞĆĈĊČĒĔĖĘĚŔŖŘŶŸΕΥЁБВЕСЭЯ", @@ -164,14 +167,14 @@ 974: "∟", 981: "Ŵ", 1024: ( - "ÆːЖШЩЮй‥⁴₁₂₃₄℉№℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠⒡" - "⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕" - "┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦▧" - "▨▩▬▶▷▽◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗" - "㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜" - "㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&" - "'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab" - "cdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + "ÆːЖШЩЮй‥⁴₁₂₃₄₩℉№℡™Å⅓⅔↔↖↗↘↙⇒⇔∀∃∇∈∋∝∠∥∧∨∪∬∮∴∵∼∽≒≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠" + "⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└" + "┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦" + "▧▨▩▬▶▷▽◀◁◈◎◐◑◘◙☎☏☜☞☺☻♡♤♧♨♩♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖" + "㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛" + "㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%" + "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a" + "bcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" ), }, "바탕체": { @@ -179,7 +182,7 @@ "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" - "›‼‾⁄∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" ), 993: "✀", 1024: ( @@ -210,7 +213,7 @@ 427: "¡", 448: "ˇ˝", 469: "„", - 512: "\u00a0¨«\u00ad¯»¼½¾ýÿijŷźżžζνξУух‗‾⁄ⁿℓ∕", + 512: "\u00a0¨«\u00ad¯»¼½¾ýÿijŷźżžζνξУух‗‾⁄ⁿ₧ℓ∕", 513: "√", 525: "ь", 555: "Ĵķśŝşš", @@ -223,7 +226,7 @@ 595: "я", 597: "¢µ¿àáâãäåçèéêëùúûüāăąćĉċčēĕėęěĥħĹĻĽĿŁŢŤŦũūŭůűųΤτυχЗТЧаесчё", 598: "бг", - 609: "ΓГ", + 609: "ΓГ₣₤", 610: "т", 624: "ъ", 625: "в", @@ -276,6 +279,7 @@ 939: "Ŵ", 957: "Œ", 960: "ÆЖ", + 981: "₩", 990: "ШЩ", 995: "☃", 1006: "∟", @@ -296,7 +300,7 @@ "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" - "›‼‾⁄∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" ), 993: "✀", 1024: ( @@ -331,12 +335,12 @@ 585: "δд", 596: "Þ", 597: "¢µ¿àáâãäåçèéêëùúûüāăąćĉċčēĕėęěĥħijĹĻĽĿŁŢŤŦũūŭůűųΤμτυχТЧаестё", - 609: "£ΓГ", + 609: "£ΓГ₣₤", 623: "ц", 628: "∆", 640: "¥ÝßðñòóôõöøþďđĝğġģńņňŋōŏőŶŸŹŻŽΖΥΧαηορσφЗХбвийнопръэ‼♀♂", 673: "Ь", - 683: "ÀÁÂÃÄÅÈÉÊËĀĂĄĒĔĖĘĚĶŚŜŞŠΕΚΛΡЁАБЕКР", + 683: "ÀÁÂÃÄÅÈÉÊËĀĂĄĒĔĖĘĚĶŚŜŞŠΕΚΛΡЁАБЕКР₧", 704: "ΑΣΨθλπ―∏♪", 719: "Π", 720: "¬", @@ -365,7 +369,7 @@ 935: "∞", 936: "▼", 938: "☼", - 939: "Ŵ", + 939: "Ŵ₩", 960: "ÆШЩ", 989: "Ж", 995: "☃", @@ -386,7 +390,7 @@ "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" - "›‼‾⁄∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" ), 993: "✀", 1024: ( @@ -433,10 +437,11 @@ 675: "я", 683: ( "ÞàáâãäåñòóôõöøùúûüýþÿāăąďđĝğġģĥħķĸĹĻĽĿŁńņňʼnŋōŏőũūŭůűųŷΓΡαγηκ" - "μνρφГРУЧавдкруч" + "μνρφГРУЧавдкруч₧" ), 684: "п", - 704: "£¬µ¶ΔΖΦσЗФЭ―∆♪", + 694: "₣", + 704: "£¬µ¶ΔΖΦσЗФЭ―₤∆♪", 725: ( "ÀÁÂÃÄÅÇÈÉÊËÐÒÓÔÕÖØÝĀĂĄĆĈĊČĎĐĒĔĖĘĚŌŎŐŚŜŞŠŢŤŦŶŸŹŻŽΑΒΕΛΟΣΤΥΧЁАБ" "ВЕОСТХ€" @@ -450,7 +455,7 @@ 747: "м", 748: "χ", 751: "ъ", - 768: "ÑßĜĞĠĢĤĦĶŃŅŇŊŔŖŘΗΚΝλИЙКНПЦЯ↕≈►◄♠", + 768: "ÑßĜĞĠĢĤĦĶŃŅŇŊŔŖŘΗΚΝλИЙКНПЦЯ₩↕≈►◄♠", 779: "л", 781: "♫", 782: "Π", @@ -494,7 +499,7 @@ "\u00a0¢£¥¦©«¬¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöùú" "ûüýÿĀāĂ㥹ĆćĈĉĊċČčĎďĐĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİĴĵĶķĹĺĻļĽľ" "ŃńŅņŇňŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ‗‚‛„•‹" - "›‼‾⁄∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" + "›‼‾⁄₣₤₧₩∆−∕∙∟≈▪▫▬►◄◊◘◙◦☺☻☼♦♫" ), 993: "✀", 1024: ( @@ -631,6 +636,8 @@ 496: "śŝşš", 498: "§", 500: "【】〔〕〖〗〘〙〚〛", + 511: "₫", + 517: "₰", 518: "её", 524: "δ", 525: "‸", @@ -658,7 +665,7 @@ 566: "κ", 568: "£", 569: "àáâãäåāăą", - 570: "ðοо", + 570: "ðοо₤", 571: "б", 572: "гт", 573: "γ⁗", @@ -675,17 +682,20 @@ 592: "В", 593: "θ", 594: "‾", + 595: "₦", 598: "к", 599: "в", 600: "φ", + 601: "₠₪", 602: "З", 603: "⁙", 605: "ĹĻĽĿ", - 606: "¶Р", - 608: "þΖря‴‷", + 606: "¶Р₲", + 608: "þΖря‴‷₵", 609: "ΕЁБЕ", 610: "¤", 612: "¸Γ", + 613: "₴", 615: "ĸ", 617: "χий", 622: "Г", @@ -694,13 +704,17 @@ 626: "ŹŻŽ", 627: "ÈÉÊËĒĔĖĘĚŚŜŞŠ", 630: "ч", + 632: "₣", 634: "ц⁉", 635: "ñùúûüĥńņňũūŭůűų⁄", + 637: "₱", 638: "Ł", 640: "đ‥", 642: "ĶΚЯ", 646: "ΤТ⁋⁌⁍", 649: "Ь", + 650: "₭", + 654: "₮", 655: "н", 656: "€", 657: "σ", @@ -708,8 +722,8 @@ 660: "ŔŖŘα", 661: "ΞЭ", 664: "ŢŤŦ", - 665: "ħлп", - 666: "ψ⁁", + 665: "ħлп₥", + 666: "ψ⁁₡₢", 672: "К", 683: "ĜĞĠĢ", 684: "ď⁛", @@ -742,14 +756,16 @@ 755: "Σ", 762: "Ħ", 768: "Ð", - 769: "Đ⁜", + 769: "Đ⁜₳", 770: "‿⁀⁔", 781: "ж", 789: "Ъ", + 794: "₩", 795: "ю", 799: "⁐", 800: "©®", 806: "ы", + 810: "₨", 821: "ΜМ", 825: "шщ", 865: "ШЩ", @@ -765,27 +781,28 @@ 929: "Ж", 948: "Ю", 956: "‱", + 957: "₧", 959: "Æ", 970: ( - "¼½¾\u205f℉ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡∢" - "∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝≞" - "≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙⊚" - "⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖" - "⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭" - "⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠┡" - "┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▴" - "▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◈◉◊◌◍◎◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴" - "◵◶◷◸◹◺◻◼◽◾◿☀☁☂☃☄☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☝☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴" - "☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰" - "♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮⚯" - "⚰⚱⚲✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽" - "✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵❶❷❸❹❺❻❼❽❾❿➀➁" - "➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶➷➸➹➺➻➼➽➾\u3000、。" - "〃〄々〆〇〒〓〜〝〞〟〠〡〢〣〤〥〦〧〨〩〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗" - "㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜" - "㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%&" - "'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ab" - "cdefghijklmnopqrstuvwxyz{|}~⦅⦆ᅠ¢£¬ ̄¦¥₩" + "¼½¾\u205f₯℉ℓ№℡™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∁∂∃∄∅∆∇∈∉∊∋∌∍∎∏∐∑−∓∔∕∖∗∘∙√∛∜∝∞∟∠∡" + "∢∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀≁≂≃≄≅≆≇≈≉≊≋≌≍≎≏≐≑≒≓≔≕≖≗≘≙≚≛≜≝" + "≞≟≠≡≢≣≤≥≦≧≨≩≪≫≬≭≮≯≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊙" + "⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊥⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊰⊱⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⊿⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕" + "⋖⋗⋘⋙⋚⋛⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿⌒⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬" + "⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟┠" + "┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱" + "▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◈◉◊◌◍◎◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳" + "◴◵◶◷◸◹◺◻◼◽◾◿☀☁☂☃☄☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕☖☗☘☙☚☛☝☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳" + "☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯" + "♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌⚍⚎⚏⚐⚑⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜⚠⚡⚢⚣⚤⚥⚦⚧⚨⚩⚪⚫⚬⚭⚮" + "⚯⚰⚱⚲✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼" + "✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❨❩❪❫❬❭❮❯❰❱❲❳❴❵❶❷❸❹❺❻❼❽❾❿➀" + "➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨➩➪➫➬➭➮➯➱➲➳➴➵➶➷➸➹➺➻➼➽➾\u3000、" + "。〃〄々〆〇〒〓〜〝〞〟〠〡〢〣〤〥〦〧〨〩〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖" + "㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛" + "㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#$%" + "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a" + "bcdefghijklmnopqrstuvwxyz{|}~⦅⦆ᅠ¢£¬ ̄¦¥₩" ), 1012: "☜☞", }, @@ -847,6 +864,7 @@ 494: "‸", 500: "ð【】〔〕〖〗〘〙〚〛", 502: "‼", + 507: "₰", 513: "çćĉċčśŝşš", 514: "¢¨", 515: "Ĵ", @@ -865,6 +883,7 @@ 542: "μ", 543: "¶⁋", 544: "‥‷", + 546: "₮", 549: "ь⁊", 550: "‴", 551: "⁎", @@ -881,7 +900,7 @@ 571: "б", 572: "Ŧгт", 573: "ŢŤγ⁌⁍", - 576: "Ρ", + 576: "Ρ₤", 581: "ÝŶŸ", 582: "òóôõöøōŏő", 583: "ß", @@ -894,13 +913,14 @@ 593: "θ", 594: "ĥħ", 595: "ñùúûüńņňŋũūŭůűų", - 598: "к", + 598: "к₫", 599: "в", 600: "φ", 602: "З", - 603: "⁙", + 603: "⁙₡₢₵", 604: "đ", 606: "Р", + 607: "₴", 608: "Ζря", 609: "ΕЁБЕ", 610: "⁒", @@ -910,13 +930,16 @@ 624: "⁘", 625: "ÈÉÊËĒĔĖĘĚ⁗", 626: "€", - 630: "ч", + 630: "ч₣", + 631: "₳", 633: "ÇĆĈĊČ", 634: "ц", 642: "ΚЯ", 645: "ĜĞĠĢ", 646: "ΤТ", + 647: "₪", 649: "Ь", + 650: "₲", 654: "ÀÁÂÃÄÅĀĂĄ", 655: "н", 657: "σ", @@ -924,8 +947,9 @@ 660: "α", 661: "ΞЭ", 662: "ď", + 663: "₭", 664: "Ķ", - 665: "лп", + 665: "лп₠", 666: "ψ", 672: "К―", 676: "ÒÓÔÕÖØŌŎŐ", @@ -945,15 +969,16 @@ 704: "ΑА", 707: "λ", 709: "ъ", - 710: "д", + 710: "д₱", 712: "⁉", 714: "πω", 716: "ΗΝН", 719: "ИЙ", 721: "Ω", 728: "ÑŃŅŇŊ", - 734: "ПЦ", + 734: "ПЦ₥", 735: "ΛЛ", + 740: "₩", 744: "Д", 746: "Ψ", 747: "ΦФ", @@ -963,6 +988,7 @@ 764: "⁂", 769: "⁜", 770: "⁔", + 780: "₦", 781: "ж", 782: "IJ", 789: "Ъ", @@ -977,8 +1003,10 @@ 897: "Ŵ", 898: "ф", 929: "Ж", - 930: "⁇", + 930: "⁇₯", + 931: "₨", 934: "Œ", + 937: "₧", 941: "‿⁀⁐", 948: "Ю", 950: "œ", @@ -1048,12 +1076,13 @@ 902: "º", 917: "ε", 925: "ζ", - 930: "ξ", + 930: "ξ₮", 932: "з", 937: "§", 942: "¿", 946: "ž", 952: "х", + 954: "₰", 956: "¶", 968: "çс", 969: "э", @@ -1063,7 +1092,9 @@ 1006: "ij", 1009: "ýÿу", 1016: "τ", + 1021: "₣", 1024: "ᅠᄀᄁᆪᄂᆬᆭᄃᄄᄅᆰᆱᆲᆳᆴᆵᄚᄆᄇᄈᄡᄉᄊᄋᄌᄍᄎᄏᄐᄑ하ᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ", + 1031: "₭", 1036: "ĸк", 1040: "λ", 1048: "я", @@ -1080,14 +1111,15 @@ 1096: "èéêëелё", 1101: "ν", 1102: "♩", - 1107: "в", + 1107: "в₪", 1112: "Š", 1116: "∮", 1119: "∂", 1122: "∃", - 1128: "¢£¥", + 1128: "¢£¥₤", 1129: "χ€∫", 1131: "ßЗ", + 1132: "₡₢", 1140: "д", 1141: "β", 1152: "υ", @@ -1112,7 +1144,8 @@ 1224: "δ", 1226: "ρ", 1227: "òóôõöøοо", - 1228: "θ", + 1228: "θ₫", + 1229: "₱", 1230: "þр", 1231: "đΧХ", 1234: "ъ", @@ -1124,11 +1157,13 @@ 1292: "∑", 1294: "Э", 1300: "ÇС", + 1304: "₠", 1309: "π", 1313: "Λ", 1322: "IJ", 1347: "Δ", 1348: "ÀÁÂÃÄÅΑА", + 1350: "₦", 1365: "√", 1366: "∠∴∵", 1380: "Ч", @@ -1153,6 +1188,7 @@ 1534: "⁈⁉", 1549: "Ц", 1553: "ж", + 1564: "₩", 1567: "ÑŊΝ", 1568: "ИЙ", 1570: "ψ", @@ -1170,7 +1206,9 @@ 1718: "∞", 1721: "щ", 1748: "æ", + 1781: "₨", 1797: "Æ", + 1802: "₥", 1804: "Ж", 1870: "⅞", 1872: "©®", @@ -1198,7 +1236,9 @@ "defghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" ), 2134: "Ю", + 2306: "₧", 2347: "№", + 2354: "₯", }, "Segoe UI Symbol": { 561: "\u202f", @@ -1363,4 +1403,25 @@ 2048: "\uf0bc\uf0be", 2134: "\uf0ab\uf0db", }, + "HY헤드라인M": { + 512: ( + "\u00a0¢£¥¦©«¬®¯µ»ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöù" + "úûüýÿ" + ), + 553: "я", + 993: "✀", + 1024: ( + "¡¤§¨ª\u00ad²³´¶¸¹º¼½¾¿ÆÐØÞßæðøþđĦħıIJijĸĿŀŁłʼnŊŋŒœŦŧˇː˘˙˚˛˝ΑΒΓΔΕΖΗΘΙ" + "ΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψωЁАБВГДЕЖЗИЙКЛМНОПРСТУ" + "ФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюё―†‡‥′″⁴ⁿ₁₂₃₄℉ℓ№℡" + "™ΩÅ⅓⅔⅛⅜⅝⅞↔↕↖↗↘↙⇒⇔∀∂∃∇∈∋∏∑√∝∞∠∥∧∨∩∪∫∬∮∴∵∼∽≒≠≡≤≥≪≫⊂⊃⊆⊇⊙⊥⌒⒜⒝⒞⒟⒠" + "⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ─━│┃┌┍┎┏┐┑┒┓└" + "┕┖┗┘┙┚┛├┝┞┟┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋▒▣▤▥▦" + "▧▨▩▶▷▼▽◀◁◈◎◐◑☎☏☜☞♀♂♠♡♣♤♥♧♨♩♪♬♭\u3000、。〃【】〓〔〕㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔" + "㈕㈖㈗㈘㈙㈚㈛㈜㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻㉿㎀㎁㎂㎃㎄㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙" + "㎚㎛㎜㎝㎞㎟㎠㎡㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏏㏐㏓㏖㏘㏛㏜㏝!"#" + "$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~¢£¬ ̄¥₩" + ), + }, } diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index b407724b..dc3be43c 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -141,7 +141,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, @@ -323,6 +324,21 @@ 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, + )), } 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 0000000000000000000000000000000000000000..cafe1805192de8dc7f02c3810866030797253b13 GIT binary patch literal 9867 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 a&%UmzfCQU`0Dz~kuQzN?x`8zW0R9gQrjKs` literal 0 HcmV?d00001 diff --git a/tests/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index 6d4aa93d..d4d051e6 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -63,6 +63,7 @@ ] 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 = [ @@ -112,6 +113,27 @@ def test_cells_of_symbols_break_where_hancom_breaks_them() -> None: 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_every_fallback_face_has_a_table() -> None: from hwpx.form_fit import _glyph_table as table @@ -727,6 +749,8 @@ def test_a_wingdings_bullet_at_its_own_width_takes_the_glyph_design_advance() -> 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. From 11f7fd170704555768902deb63b6bca36b40c1d5 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Thu, 1 Oct 2026 10:32:02 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat(form=5Ffit):=20lay=20out=20=ED=9C=B4?= =?UTF-8?q?=EB=A8=BC=EB=AA=85=EC=A1=B0=20at=20its=20design=20advances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 휴먼명조 joins the glyph table at its design advances, read from the face's font file; the glyphs it lacks take 함초롬바탕's advance moved into its units. A new test pins a few of them. The test that measured ○ in a face the table does not list used 휴먼명조 cells; it now measures them with 휴먼명조's advances and with the face left unnamed, and Hancom's line counts hold for both. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/hwpx/form_fit/_glyph_table.py | 17 +++++++++++++++++ src/hwpx/form_fit/measure.py | 15 +++++++++++++++ tests/test_form_fit_hancom_rules.py | 16 ++++++++++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44273757..49ebe88b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ - 기호 글꼴 Wingdings·Symbol도 문서가 글자를 두는 사용자 영역(U+F020–F0FF)의 설계 폭으로 잰다. 실제 너비를 쓰는 Wingdings 글머리표(U+F09F)는 10 pt에서 456을 차지한다. - HY헤드라인M도 글꼴의 설계 폭으로 잰다. 글꼴에 없는 글자와 `` ` ``는 함초롬바탕의 폭을 쓴다. + - 휴먼명조도 글꼴의 설계 폭으로 잰다. 글꼴에 없는 글자는 함초롬바탕의 폭을 쓴다. - FormFit이 칸의 높이 예산으로 저장된 안쪽 높이를 그대로 쓴다. 한/글은 그 높이까지 줄을 넣어도 행을 키우지 않고, 줄 간격은 한/글과 같게 센다. 폭의 안전 여유(0.93)는 그대로다. 전에는 높이에도 0.93을 곱해, 칸에 들어가는 값을 줄이거나 거부했다(예: 안쪽 2047에 1939를 쓰는 두 줄 값). diff --git a/src/hwpx/form_fit/_glyph_table.py b/src/hwpx/form_fit/_glyph_table.py index dd239c8f..da246701 100644 --- a/src/hwpx/form_fit/_glyph_table.py +++ b/src/hwpx/form_fit/_glyph_table.py @@ -25,6 +25,7 @@ "함초롬돋움": 1000, "맑은 고딕": 2048, "Segoe UI Symbol": 2048, + "휴먼명조": 512, "HY헤드라인M": 1024, "Wingdings": 2048, "Symbol": 2048, @@ -44,6 +45,7 @@ "한컴 고딕": "함초롬돋움", "함초롬바탕": "Segoe UI Symbol", "함초롬돋움": "Segoe UI Symbol", + "휴먼명조": "함초롬바탕", "HY헤드라인M": "함초롬바탕", } @@ -1424,4 +1426,19 @@ "`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 dc3be43c..6409ba14 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -339,6 +339,21 @@ 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, + )), } diff --git a/tests/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index d4d051e6..5a13551f 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -134,6 +134,14 @@ def test_cells_in_hy_headline_break_where_hancom_breaks_them() -> None: 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 @@ -494,8 +502,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] @@ -504,6 +513,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))