Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
필드와 메모 필드는 한/글이 배치 정보 없이 저장해 건너뛴다). 필드의 시작·끝은
줄에 자리를 차지하지 않고 줄 위치(textpos)에서 하나에 8글자씩이며, 줄이 나뉘는 자리에 있으면 다음 줄이 그것으로
시작한다. 형광펜 표시는 자리도 줄 위치도 차지하지 않는다. 한/글이 그렇게 놓는다. 전에는 그런 문단을 건너뛰었다.
- FormFit과 쪽 수 추정이 문단 모양의 한글·영문/숫자 자동 간격(`hh:autoSpacing`의 `eAsianEng`·`eAsianNum`)을 따른다.
한/글은 한글 음절 바로 옆의 영문 글자(또는 숫자) 사이에 em의 1/4(자간 적용, 장평은 그대로)을 넣고, 그 간격은 앞 글자에
붙어 줄이 그 자리에서 나뉠 때도 앞 줄 폭에 든다. 빈칸·문장 부호를 사이에 두거나 영문과 숫자 사이는 넣지 않는다. 전에는
간격 없이 세어 그런 문단의 줄이 한/글보다 늦게 나뉘었다. `TextStyle.auto_space_latin`·`auto_space_digit`, `auto_space_gap()`.
- FormFit과 쪽 수 추정이 글꼴 폭 표에 없는 가나(히라가나·가타카나)를 그 글꼴의 한글 음절 폭으로 잡는다(함초롬바탕
972, 한컴 고딕 932). 한/글이 그렇게 놓는다. 전에는 전각(1 em)으로 셌다.
- 쪽 수 추정(실험, `estimate_pages`)이 탭 정의를 한 번만 읽는다. 전에는 글자 모양을 볼 때마다 머리 정보의 탭 정의 전체를
Expand Down
32 changes: 29 additions & 3 deletions src/hwpx/form_fit/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,7 +1301,10 @@ class TextStyle:
out when a character shape gives its scripts different faces (see
:func:`glyph_face_of`). ``bold`` glyphs in a face of ``_BOLD_WIDER_FACES``
(the 한양 faces) are a twentieth of the em wider; in the others bold
keeps the regular advance.
keeps the regular advance. ``auto_space_latin`` and ``auto_space_digit``
are the paragraph shape's automatic spacing (``hh:autoSpacing``): a
quarter of the em, 자간 applied, between a Hangul syllable and a Latin
letter or a digit right next to it (see :func:`auto_space_gap`).
"""

ratio: float = 100.0
Expand All @@ -1327,6 +1330,8 @@ class TextStyle:
auto_tab_left: bool = False
auto_tab_right: bool = False
bold: bool = False
auto_space_latin: bool = False
auto_space_digit: bool = False


def _line_origin(style: TextStyle, first: bool) -> float:
Expand Down Expand Up @@ -1703,9 +1708,10 @@ def hancom_line_starts(
break
shrink = (inner + pending) * space * style.condense / 100.0
last = fixed if fixed is not None else char_advance(ch, size, unspaced if styles is None else _without_spacing(look))
if (used + last - shrink > width or ch == "\t" and used + last >= width) and end > start:
gap = auto_space_gap(ch, text[end + 1], size, style, look) if end + 1 < length else 0.0
if (used + last + gap - shrink > width or ch == "\t" and used + last >= width) and end > start:
break # a tab reaching the margin itself no longer fits
used += advance
used += advance + gap

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Include the same automatic gaps in width and confidence measurement. hancom_line_starts now adds these gaps, but estimate_text_width still sums only char_advance, and measure uses that old width for ratio and its error-band confidence. Reproduced with text='가A'10, font_pt=10 and TextStyle(auto_space_latin=True): reported width remains 17000 (identical to spacing off), while the new rule adds 19250=4750, giving 21750. A one-line SlotMetrics(available_width=23000,...) consequently returns confidence='high' and ratio=0.739, even though the modeled width is within the 11.18% uncertainty band (21750/23000=0.946). Update the width/confidence path to include applicable gaps and add a public measure/estimate_text_width regression; line-start-only tests miss this inconsistency.

inner += pending
pending = 0
seen = True
Expand Down Expand Up @@ -1756,6 +1762,24 @@ def _width_after_tab(
return width


def auto_space_gap(previous: str, ch: str, font_pt: float, style: TextStyle, look: TextStyle | None = None) -> float:
"""The room Hancom's automatic spacing (``style.auto_space_latin``/``auto_space_digit``) puts between
*previous* and *ch*: a quarter of the em at *font_pt*, its 자간 (*look*'s, else *style*'s) applied, when one is
a Hangul syllable and the other a Latin letter (or a digit) right next to it; else none. 장평 leaves it as it
is; a space or a mark between them, or a Latin letter beside a digit, takes none. The gap goes with the
character before it: that character fits on its line only with the gap after it, even where the line breaks
at the gap."""

if not (style.auto_space_latin or style.auto_space_digit):
return 0.0
for hangul, other in ((previous, ch), (ch, previous)):
if "\uac00" <= hangul <= "\ud7a3" and other.isascii() and (
style.auto_space_latin and other.isalpha() or style.auto_space_digit and other.isdigit()):
spacing = (look or style).spacing
return font_pt * 100.0 / 4 * (1 + spacing / 100.0)
return 0.0


def _spills(ch: str, previous: str) -> bool:
"""Whether the space *ch*, starting at or past the margin after *previous*, begins the next line: a
fixed-width space does even right after a word, a space only after another space (the first one after a
Expand Down Expand Up @@ -2434,6 +2458,8 @@ def text_style_from_refs(
tab_stops=tab_stops,
auto_tab_left=bool(getattr(tabs, "auto_tab_left", False)),
auto_tab_right=bool(getattr(tabs, "auto_tab_right", False)),
auto_space_latin=bool(getattr(getattr(prop, "auto_spacing", None), "e_asian_eng", False)),
auto_space_digit=bool(getattr(getattr(prop, "auto_spacing", None), "e_asian_num", False)),
bold=bold,
)
heading = paragraph_label(root, para_pr_id_ref, label)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_form_fit_hancom_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
)
from hwpx.form_fit.measure import (
_cell_text_style,
auto_space_gap,
char_advance,
classify_char,
font_line_height,
Expand Down Expand Up @@ -539,6 +540,26 @@ def test_the_two_spaces_right_after_a_word_hang_past_the_margin() -> None:
assert hancom_line_starts("가나다라마 바", [5200], 10, TextStyle()) == [0, 6]


def test_automatic_spacing_puts_a_quarter_em_between_hangul_and_latin_or_digits() -> None:
# hh:autoSpacing: a quarter of the em, its 자간 applied (장평 leaves it), between a Hangul syllable and a Latin
# letter (eAsianEng) or a digit (eAsianNum) right next to it; none across a space or a mark, or Latin to digit.
both = replace(TextStyle(), auto_space_latin=True, auto_space_digit=True)
assert auto_space_gap("가", "A", 10, both) == auto_space_gap("1", "가", 10, both) == 250
assert auto_space_gap("가", "A", 10, replace(both, spacing=-10.0)) == 225
assert auto_space_gap("가", "A", 10, replace(both, ratio=80.0)) == 250
assert auto_space_gap("A", "1", 10, both) == auto_space_gap("가", " ", 10, both) == 0
assert auto_space_gap("가", "(", 10, both) == auto_space_gap("가", "A", 10, TextStyle()) == 0
assert auto_space_gap("가", "1", 10, replace(TextStyle(), auto_space_latin=True)) == 0


def test_an_automatic_space_goes_with_the_character_before_it() -> None:
# "1" fits only with the gap after it, even where the line breaks at that gap.
digits = replace(TextStyle(), auto_space_digit=True)
assert hancom_line_starts("가가가1가가", [3900], 10, digits) == [0, 3]
assert hancom_line_starts("가가가1가가", [4100], 10, digits) == [0, 4]
assert hancom_line_starts("가가가1가가", [3900], 10, TextStyle()) == [0, 4]


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)
Expand Down
Loading