diff --git a/CHANGELOG.md b/CHANGELOG.md index 45347ef9..e9d443f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,9 @@ 한/글은 그 도형을 문단 위가 있었을 자리에서 그만큼 위에 놓고, 그 띠에 걸린 앞 줄(아래끝이 띠 위끝보다 아래인 줄)을 띠 아래끝으로 내린 뒤 그다음 줄을 이어서 놓는다. 도형의 문단 줄은 그 뒤에 온다. 추정도 이제 그렇게 놓는다(옮긴 줄이 쪽 끝을 넘거나 그 사이에 표·개체·각주가 있으면 지원 밖). +- FormFit과 쪽 수 추정이 표에 없는 글꼴 이름(설치되지 않은 글꼴 등)을 한컴바탕의 폭으로 잰다. 한/글은 찾지 못한 글꼴을 + 한컴바탕으로 배치한다. 전에는 글자 종류의 평균 폭을 썼다. 글꼴 이름이 없으면 전처럼 평균 폭이다. + - 한컴바탕의 `※`는 788, `…`는 983(1024 단위)으로 한/글이 배치하는 폭이다. - 쪽 수 추정이 빼기 부호로 쓴 개체 오프셋(`vertOffset="-1500"`처럼, 한/글이 쓰는 부호 없는 32비트 꼴이 아닌 것)을 한/글처럼 0으로 읽는다. 한/글은 그런 값을 0으로 읽어 개체를 문단(또는 종이) 기준 자리에 그대로 그리고, 저장할 때 0으로 고쳐 쓴다. 전에는 그만큼 위(왼쪽)로 놓인 것으로 읽어 뒤 줄 자리가 한/글과 달랐다. diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index 4319fa2c..49a6f562 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -138,12 +138,15 @@ # is half the em, rounded down, at the 장평, rounded half up. Bold text keeps the # 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. +# and rounded. A face no table lists takes 한컴바탕's advances, as Hancom lays out a +# face it does not find; a glyph listed nowhere falls back to the class averages, +# unrounded, and so does every glyph when no face is named. _LAYOUT_UNIT = 4 #: Hancom's default tab stops, this far apart (HWPUNIT) from the column's left edge (the section's #: ``tabStopVal``). _DEFAULT_TAB_STOP = 4000 +#: The face Hancom lays out a face it does not find with. +_UNLISTED_FACE = "한컴바탕" _HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}" _HH = "{http://www.hancom.co.kr/hwpml/2011/head}" @@ -526,8 +529,8 @@ 768, 682, 682, 725, 682, 640, 725, 768, 384, 469, 768, 640, 938, 768, 725, 640, 725, 682, 640, 768, 768, 725, 981, 682, 682, 640, 512, 341, 512, 1024, 512, 597, 512, 554, 512, 554, 554, 384, 554, 554, 298, 298, 554, 298, 853, 554, 554, 554, - 554, 426, 512, 384, 554, 554, 810, 597, 597, 469, 597, 597, 597, 810, 341, 850, - 379, 379, 276, 276, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 780, 1024, 1024, 1024, + 554, 426, 512, 384, 554, 554, 810, 597, 597, 469, 597, 597, 597, 810, 341, 983, + 379, 379, 276, 276, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 788, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 831, 831, 831, 447, 1024, 1024, 881, 441, 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, @@ -1094,6 +1097,13 @@ def _design_units(face: str, ch: str | None) -> tuple[int, int] | None: return None +def _laid_out_face(face: str) -> str: + """The face whose advances *face* takes: itself when a table lists it (or none is named), else + ``_UNLISTED_FACE``.""" + + return face if not face or face in _DESIGN or face in UNITS_PER_EM else _UNLISTED_FACE + + @lru_cache(maxsize=8192) def _hangul_units(face: str, ch: str) -> tuple[int, int] | None: """``(advance, units per em)`` of the Hangul syllable *ch* in *face*: its own design advance where the @@ -1415,11 +1425,11 @@ def char_advance(ch: str, font_pt: float, style: TextStyle | None = None) -> flo if ch in _HALF_EM_SPACES and not style.use_font_space: return _laid_out_space(height, ratio, spacing) if cls == "hangul": - design, base = _hangul_units(style.hangul_face, ch), style.hangul_advance + design, base = _hangul_units(_laid_out_face(style.hangul_face), ch), style.hangul_advance elif ch in _HALF_EM_GLYPHS: design, base = (1, 2), _ADVANCE_EM[cls] else: - design, base = _design_units(style.glyph_face, ch), _ADVANCE_EM[cls] + design, base = _design_units(_laid_out_face(style.glyph_face), ch), _ADVANCE_EM[cls] if design is not None: return _laid_out(design[0], design[1], height, ratio, spacing) return base * font_pt * 100.0 * ratio / 100.0 * (1 + spacing / 100.0) diff --git a/tests/fixtures/hancom_saved/formfit_unlisted_faces_cells.hwpx b/tests/fixtures/hancom_saved/formfit_unlisted_faces_cells.hwpx new file mode 100644 index 00000000..38343b61 Binary files /dev/null and b/tests/fixtures/hancom_saved/formfit_unlisted_faces_cells.hwpx differ diff --git a/tests/test_form_fit_hancom_rules.py b/tests/test_form_fit_hancom_rules.py index ef436488..6c8fc847 100644 --- a/tests/test_form_fit_hancom_rules.py +++ b/tests/test_form_fit_hancom_rules.py @@ -73,6 +73,9 @@ KOPUB_FACES_CELLS = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_kopub_faces_cells.hwpx" HFT_FACES_CELLS = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_hft_faces_cells.hwpx" DODUM_PUNCTUATION = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_dodum_punctuation_cells.hwpx" +UNLISTED_FACES_CELLS = Path(__file__).parent / "fixtures" / "hancom_saved" / "formfit_unlisted_faces_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 = [ @@ -288,6 +291,26 @@ def test_kopub_cells_break_where_hancom_breaks_them() -> None: for weight in ("Light", "Medium", "Bold")} +def test_cells_in_faces_no_table_lists_break_as_hancom_batang() -> None: + # The same in faces Hancom does not find (없는글꼴이름, Cre고딕, 가짜명조체), for A, a, 0, (, %, 가, ※ and W at + # 10 and 30 pt: Hancom lays them out as 한컴바탕, ※ at 788 of 1024 units. + doc = HwpxDocument.open(UNLISTED_FACES_CELLS) + cells = list(doc.oxml.sections[0].element.iter(f"{HP}tc")) + faces = set() + 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")) + faces.add(style.glyph_face) + assert estimate_lines(text, width, points, style) == hancom, (style.glyph_face, f"U+{ord(text[0]):04X}", points) + assert len(cells) == 96 and faces == {"없는글꼴이름", "Cre고딕", "가짜명조체"} + + def test_faces_in_hancoms_own_folder_take_the_design_advances_of_their_fonts() -> None: # The faces 한/글 keeps in its own folder are measured from their fonts too: a Hangul syllable takes 900 of # 1000 units in 한컴 윤고딕 230, 850 in 한컴 백제 M, the full em in 휴먼고딕 (512 units) and HY울릉도M (1024)