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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@
않으면 탭은 폭이 없다. 내어쓰기 자동 탭은 둘째 줄이 시작하는 곳에 왼쪽 위치를 둔다.
- 쪽 수 추정에서 강제 줄 바꿈 다음 줄은 첫 줄이 아니다. 들여쓰기를 빼지 않고 다른 줄의 폭으로 잰다. 전에는 첫 줄 폭으로
재서, 들여쓴 문단에서 줄이 하나 더 생길 수 있었다.
- 쪽 수 추정에서 자기 글자 모양이 없는 글머리표·번호(`hh:paraHead@charPrIDRef` 없음)는 문단 끝(마지막 run)의 글자
모양으로 잰다. 그 크기로 머리 폭을 재고, 그 크기가 첫 줄 높이에도 들어간다. 전에는 문단 첫 글자의 모양으로 재서,
크기가 섞인 개요·번호 문단의 줄 나눔과 첫 줄 높이가 한/글과 달랐다.
- FormFit과 쪽 수 추정이 텍스트 내보내기가 쓰지 않는 번호 형식(`KOREAN_DIGIT`, `HANGUL` 등)의 문단 번호를 만나도
멈추지 않는다. 한/글이 재는 번호(`useInstWidth`)이면 그 숫자를 한글 한 글자 폭으로 잰다. 전에는 번호가 자기 글자
모양을 가진 문단에서 `KeyError`가 났다.
Expand Down
13 changes: 11 additions & 2 deletions src/hwpx/form_fit/measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2197,11 +2197,18 @@ def _face_names(root: Any, char_pr_id_ref: object) -> tuple[str, str]:


def text_style_from_refs(
document: object, para_pr_id_ref: object, char_pr_id_refs: "list[object]", label: str | None = None
document: object,
para_pr_id_ref: object,
char_pr_id_refs: "list[object]",
label: str | None = None,
end_char_pr_id_ref: object = None,
) -> TextStyle:
"""Hancom layout settings of a paragraph shape and the first resolvable
character shape among *char_pr_id_refs*; *label*, when given, is the number
Hancom draws before the paragraph (see :func:`paragraph_label`)."""
Hancom draws before the paragraph (see :func:`paragraph_label`).
*end_char_pr_id_ref* is the character shape of the paragraph's end (its
last run): a bullet or number label without a character shape of its own
takes that one, else the text's."""

ratio, spacing, use_font_space, hangul_face, glyph_face = 100.0, 0.0, False, "", ""
scripts: tuple[tuple[str, float, float], ...] = ()
Expand Down Expand Up @@ -2278,6 +2285,8 @@ def text_style_from_refs(
text, head = heading
own = head.get("charPrIDRef")
looks, em = style, height
if not (own and own != "4294967295" and root.char_property(own) is not None) and end_char_pr_id_ref is not None:
own = str(end_char_pr_id_ref) # a label without a shape of its own takes the paragraph end's
if own and own != "4294967295" and root.char_property(own) is not None and own != used:
looks = text_style_from_refs(document, None, [own])
em = int(_style_number((root.char_property(own).attributes or {}).get("height"), 1000.0))
Expand Down
37 changes: 24 additions & 13 deletions src/hwpx/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,29 +538,33 @@ def headed(self, paragraph: Any) -> bool:

def style(self, para_pr_id: Any, char_pr_ids: list[Any], paragraph: Any = None) -> Any:
"""FormFit's text style of a paragraph shape and its characters; with *paragraph*, a number's label
takes the room of the label Hancom draws for that paragraph."""
takes the room of the label Hancom draws for that paragraph, and a label without a character shape of
its own that of the paragraph's end."""

drawn = None
if paragraph is not None and self.numbered(para_pr_id):
if self._drawn is None:
self._drawn = _drawn_labels(self._root)
drawn = self._drawn.get(paragraph)
key = (str(para_pr_id), tuple(str(ref) for ref in char_pr_ids), drawn)
end = _end_ref(paragraph) if paragraph is not None else None
key = (str(para_pr_id), tuple(str(ref) for ref in char_pr_ids), drawn, end)
if key not in self._styles:
self._styles[key] = text_style_from_refs(self._lookups, para_pr_id, char_pr_ids, drawn)
self._styles[key] = text_style_from_refs(self._lookups, para_pr_id, char_pr_ids, drawn, end)
return self._styles[key]

def label_size(self, para_pr_id: Any) -> int:
"""The character size of a paragraph shape's bullet or number label in its own character shape
(``hh:paraHead@charPrIDRef``); 0 when it has none or takes its paragraph's."""
def label_size(self, para_pr_id: Any, end: Any = None) -> int:
"""The character size of a paragraph shape's bullet or number label: in its own character shape
(``hh:paraHead@charPrIDRef``), or without one in that of the paragraph's end (*end*); 0 when it has
none, or no shape of its own and no *end*."""

key = str(para_pr_id)
if key not in self._label_sizes:
label = paragraph_label(self._root, para_pr_id)
ref = label[1].get("charPrIDRef") if label is not None else None
own = ref is not None and ref != "4294967295" and self._root.char_property(ref) is not None
self._label_sizes[key] = self.char_height(ref) if own else 0
return self._label_sizes[key]
self._label_sizes[key] = self.char_height(ref) if own else -1 if label is not None else 0
size = self._label_sizes[key]
return size if size >= 0 else self.char_height(end) if end is not None else 0

def numbered(self, para_pr_id: Any) -> bool:
"""Whether a paragraph shape heads its paragraphs with a number (``NUMBER`` or ``OUTLINE``)."""
Expand Down Expand Up @@ -1794,15 +1798,22 @@ def _end_size(measure: _Measure, runs: list[Any], sizes: list[int]) -> int:
return size if size > min(sizes) else 0


def _end_ref(paragraph: Any) -> Any:
"""The character shape of a paragraph's end: its last run's, or None without runs."""

runs = paragraph.findall(f"{HP}run")
return runs[-1].get("charPrIDRef") if runs else None


def _head_size(measure: _Measure, paragraph: Any, sizes: list[int]) -> int:
"""The size a bullet or number label in its own character shape, or a run holding nothing but controls
before the text (a section's or columns' definition, a page number: an empty run takes no room), gives its
paragraph's first line: a character of that size there (it counts when it is larger than the smallest
character); 0 when not."""
"""The size a bullet or number label (in its own character shape, or without one in that of the
paragraph's end), or a run holding nothing but controls before the text (a section's or columns'
definition, a page number: an empty run takes no room), gives its paragraph's first line: a character of
that size there (it counts when it is larger than the smallest character); 0 when not."""

if not sizes:
return 0
size = measure.label_size(paragraph.get("paraPrIDRef"))
size = measure.label_size(paragraph.get("paraPrIDRef"), _end_ref(paragraph))
for run in paragraph.findall(f"{HP}run"):
if any(child.text or len(child) for child in run.findall(f"{HP}t")): # the text starts
break
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
15 changes: 15 additions & 0 deletions tests/test_form_fit_hancom_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,21 @@ def test_a_label_takes_its_room_off_the_lines() -> None:
assert indented_widths(10000, replace(labelled, head_hangs=False, indent=-1000)) == (8500, 9000)


def test_a_label_without_a_shape_of_its_own_takes_the_paragraph_ends() -> None:
# An outline paragraph (the template's level 2, "가." with 자동 내어 쓰기) whose text starts at 10 pt and ends
# at 20 pt: Hancom measures the label in the paragraph end's shape, about twice as wide as in the text's.
doc = HwpxDocument.new()
small = doc.ensure_run_style(font="함초롬바탕", size=10)
large = doc.ensure_run_style(font="함초롬바탕", size=20)

text = text_style_from_refs(doc.oxml, "3", [small])
end = text_style_from_refs(doc.oxml, "3", [small], None, large)

assert (text.head, text.head_hangs) == (1792, True)
assert (end.head, end.head_hangs) == (3580, True)
assert text_style_from_refs(doc.oxml, "3", [small], None, small) == text


def test_a_label_takes_its_glyphs_its_adjustment_and_its_gap() -> None:
head = {"useInstWidth": "0", "textOffsetType": "PERCENT", "textOffset": "50", "widthAdjust": "0"}
face = TextStyle(glyph_face="함초롬돋움", hangul_face="함초롬돋움")
Expand Down
6 changes: 6 additions & 0 deletions tests/test_layout_page_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,12 @@
# it, the character's line below its end
"pages_bullet_and_number_labels": 16, # fourteen blocks of rows under bullets and numbers of every
# label setting: each label takes its room off the lines
# Outline paragraphs (the template's levels 1 to 7, labels without a character shape of their own) of two
# to four runs, the first and the last of different sizes, some ending in an empty run: Hancom measures the
# label in the paragraph end's shape, and that size counts in the first line's height.
"pages_outline_labels_paragraph_end_shape": 2,
"pages_outline_labels_paragraph_end_shape_sizes": 2,
"pages_outline_labels_paragraph_end_shape_empty_run": 2,
"pages_bullet_in_its_own_20pt_shape": 1, # three-line paragraphs of 10 pt text under a bullet in its own
# 20 pt shape, at 160 %: the first line as tall as the bullet
"pages_bullet_in_its_own_8pt_shape": 1, # an 8 pt one: no line taller
Expand Down
Loading