diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a51509..9b37f4bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,13 @@ 뒤 마지막 바이트에서 읽고, 바이너리 항목 번호 뒤 바이트는 읽지 않는다(저장할 때 0으로 쓴다). 전에는 그 바이트를 투명도로 옮겨, 거기에 255가 든 문서의 그림이 HWPX에서 보이지 않았다. 반전(`hp:pic@reverse`)은 그림 개체 속성의 24번 비트에서 읽는다(전에는 늘 0). HWP 5.0으로 저장할 때도 두 값을 같은 자리에 쓴다. +- 쪽 수 추정이 줄보다 넓은 글자처럼 취급한 표·그림을 한컴오피스 2024의 한/글처럼 놓는다. + - 표 앞의 조판 부호(책갈피·누름틀·머리말·쪽 번호 위치 등)와, 문단 첫머리에 앵커된 위아래 배치 개체는 표의 줄에 + 선다. 전에는 그 앞에 빈 줄 하나를 따로 두어 뒤 문단이 그만큼 내려갔다. + - 글자로 번호를 찍는 조판 부호(번호 넣기)는 전처럼 표 위에 자기 빈 줄을 둔다. + - 표 뒤의 조판 부호는 전처럼 표 아래 빈 줄에 선다. + - 어울림 개체 옆 좁은 띠에 든 이런 개체는 띠보다 넓어도 띠 안 그 자리에 놓인다. 전에는 개체 아래 전체 폭 줄로 + 내렸다. - 쪽 수 추정이 문단 위로 올려 놓은 도형(문단 기준 음수 `vertOffset`, 파일에는 부호 없는 32비트로 남는다)을 그만큼 위에 놓인 것으로 센다. 전에는 그 값을 큰 양수로 읽어 문단 줄이 40억 넘게 높아지고, 한 쪽짜리 문서가 세 쪽으로 나왔다. 한/글은 그 도형을 문단 위가 있었을 자리에서 그만큼 위에 놓고, 그 띠에 걸린 앞 줄(아래끝이 띠 위끝보다 아래인 줄)을 diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index 63844fe3..387876d6 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -2059,10 +2059,10 @@ def _apart_from_wide(measure: _Measure, text: str, widths: list[float], sizes: l metrics: tuple[tuple[int, int], ...], size: int, shape: _Shape, place: int) -> tuple[tuple[tuple[int, int], ...], int | None]: """Hancom lays an object set as a character that is wider than its line on that line alone: the object - placed top and bottom anchored before it at the paragraph's start, or after it at the paragraph's end, stands - on an empty line of its own above or below it, as tall as the paragraph's characters (*size*). *text* holds - the paragraph's characters and objects set as characters, not the anchored one, whose place in it is - *place*. (*metrics* with that line, its index.) + placed top and bottom anchored before it at the paragraph's start stands on that line, and one after it at + the paragraph's end on an empty line of its own below it, as tall as the paragraph's characters (*size*). + *text* holds the paragraph's characters and objects set as characters, not the anchored one, whose place in + it is *place*. (*metrics* with that line, its index.) Otherwise the anchor stands on the line holding the character before it (the first line when it comes first), as Hancom keeps it at the end of that line when the next one starts after it: (*metrics*, that line). @@ -2077,8 +2077,8 @@ def wide(index: int) -> bool: return index in objects and objects[index][0] > widths[min(line, len(widths) - 1)] empty = (size, _pitch(shape.kind, shape.value, size)) - if place == 0 and wide(0): # the anchor first: its empty line above - return (empty, *metrics), 0 + if place == 0 and wide(0): # the anchor first: on the object's line, no line of its own + return metrics, 0 if place == len(text) and wide(len(text) - 1): # the anchor last: its empty line below return (*metrics, empty), len(metrics) return metrics, max((number for number, start in enumerate(starts) if start < place), default=0) @@ -2278,9 +2278,13 @@ def _paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | Non pitch = _pitch(shape.kind, shape.value, size) own, own_pitch = size, pitch # the paragraph's own line, as tall as its characters count, size, pitch, table = _object_line(measure, objects[0], count, size, pitch, page.column_width, shape) - if controls is not None: # the controls before and after it on empty lines of their own, each as tall as + if controls is not None: # the controls after it on an empty line of their own, as tall as the largest + before, after = controls[1], controls[2] # characters of the runs holding them; those before it too + if not any(child.find(f"{HP}autoNum") is not None for run in before for child in run # when they draw + if _local(child) == "ctrl"): # a number, else they stand on the table's line + before = [] heights = [max(measure.char_height(run.get("charPrIDRef")) for run in side) if side else None - for side in controls[1:]] # the largest characters of the runs holding them + for side in (before, after)] empty = [None if height is None else (height, _pitch(shape.kind, shape.value, height)) for height in heights] cached = tuple(line for line in (empty[0], (size, pitch), empty[1]) if line is not None) @@ -2347,8 +2351,8 @@ def _controls_beside(obj: Any, runs: list[Any], width: float) -> tuple[Any, list """(*obj*'s run, the runs holding controls before it, those holding controls after it) when *obj* is a table set as a character wider than the line (*width*) and controls (``hp:ctrl``, a page number, a hidden page number, a field, a bookmark, a header and so on -- but not a column definition) stand beside it in its - paragraph, else ``None``: Hancom sets the controls before it on an empty line of their own, the table on the - next line and the controls after it on the line after that.""" + paragraph, else ``None``: Hancom sets the controls before it on the table's line (on an empty line of their + own above it when they show a number) and the controls after it on the line after that.""" if _local(obj) != "tbl" or obj.find(f"{HP}pos").get("treatAsChar") != "1" or _extent(obj, "width") <= width: return None @@ -2543,12 +2547,9 @@ def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wr if wrap is not None and wrap.push: # objects set as characters on a line reaching it go below it with it return _push(_paragraph(measure, page, paragraph), wrap, starts=False) if wrap is not None and objects and not placed and not _run_text(runs).strip(): - para = _paragraph(measure, page, paragraph) # one object set as a character, alone - if len(objects) > 1: + para = _paragraph(measure, page, paragraph) # one object set as a character, alone: beside it, even + if len(objects) > 1: # wider than the room raise _Unsupported("objects set as characters beside a square-wrapped object") - if _object_extent(objects[0], measure)[0] > _room_beside(wrap, page.column_width - shape.left - shape.right): - pushed, after = _push(para, replace(wrap, push=True), starts=False) # below it, the whole width - return replace(pushed, wrap_stays=wrap.stays, wrap_free=wrap.stays), after beside = sum(1 for line in range(para.lines) if para.span(0, line) < wrap.bottom) return replace(para, wrap_lines=beside, wrap_stays=wrap.stays, wrap_free=wrap.stays), \ wrap.lower(para.span(0, para.lines) + para.next) @@ -2756,9 +2757,9 @@ def _banded_object_lines(measure: _Measure, text: str, widths: list[float], size end: int, head: int) -> tuple[tuple[tuple[int, int], ...], int]: """(height, advance) of each line of text holding objects set as characters (*objects*: place -> width and height) beside a square-wrapped object's band, and how far the first line goes down. A line whose top is in - the band is as wide as the room beside the object, but the first one holding an object wider than that room - goes below the band, the whole width, the rest of the room left empty (its line before spaced that much - more). Every line taken as narrow at first, the lines are broken again until the narrow ones settle.""" + the band is as wide as the room beside the object, one holding an object wider than that room too (the object + alone on it, reaching past the room). Every line taken as narrow at first, the lines are broken again until + the narrow ones settle.""" advances = {index: width for index, (width, _) in objects.items()} or None mixed = sizes if len(set(sizes)) > 1 else None @@ -2781,15 +2782,7 @@ def _banded_object_lines(measure: _Measure, text: str, widths: list[float], size pitch = _pitch(shape.kind, shape.value, size) advance = pitch if shape.kind == "FIXED" else height + pitch - size if top < wrap.bottom and top + height > wrap.top: - room = widths[min(line, 1)] - wrap.cut - if any(objects[index][0] > room for index in span if index in objects): # below the band - if metrics: - metrics[-1] = (metrics[-1][0], metrics[-1][1] + wrap.bottom - top) - else: - lead = wrap.bottom - top - top = wrap.bottom - else: - found.add(line) + found.add(line) metrics.append((height, advance)) top += advance if found == narrow: diff --git a/tests/fixtures/hancom_saved/pages_bookmark_around_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_bookmark_around_a_table_wider_than_the_line.hwpx new file mode 100644 index 00000000..8ab0dbec Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_bookmark_around_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_bookmark_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_bookmark_then_a_table_wider_than_the_line.hwpx index 99d732f2..719dda4f 100644 Binary files a/tests/fixtures/hancom_saved/pages_bookmark_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_bookmark_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_click_here_field_around_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_click_here_field_around_a_table_wider_than_the_line.hwpx new file mode 100644 index 00000000..3e691bb1 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_click_here_field_around_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_click_here_field_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_click_here_field_then_a_table_wider_than_the_line.hwpx index 26dd5bca..4c48239c 100644 Binary files a/tests/fixtures/hancom_saved/pages_click_here_field_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_click_here_field_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_footer_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_footer_then_a_table_wider_than_the_line.hwpx index 2ad37dc5..16fd8b1f 100644 Binary files a/tests/fixtures/hancom_saved/pages_footer_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_footer_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_header_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_header_then_a_table_wider_than_the_line.hwpx index 826f2e2c..9193b6cf 100644 Binary files a/tests/fixtures/hancom_saved/pages_header_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_header_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_index_mark_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_index_mark_then_a_table_wider_than_the_line.hwpx index 30a7bbca..9068ad3f 100644 Binary files a/tests/fixtures/hancom_saved/pages_index_mark_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_index_mark_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_new_number_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_new_number_then_a_table_wider_than_the_line.hwpx index 818072ec..1c3b74a1 100644 Binary files a/tests/fixtures/hancom_saved/pages_new_number_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_new_number_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_page_hiding_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_page_hiding_then_a_table_wider_than_the_line.hwpx index e9dbb2f4..fa86c72e 100644 Binary files a/tests/fixtures/hancom_saved/pages_page_hiding_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_page_hiding_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_page_number_control_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_page_number_control_then_a_table_wider_than_the_line.hwpx index 9b92ad6c..b3b2e424 100644 Binary files a/tests/fixtures/hancom_saved/pages_page_number_control_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_page_number_control_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_page_number_then_a_table_wider_than_the_line.hwpx b/tests/fixtures/hancom_saved/pages_page_number_then_a_table_wider_than_the_line.hwpx index da271543..24c31d24 100644 Binary files a/tests/fixtures/hancom_saved/pages_page_number_then_a_table_wider_than_the_line.hwpx and b/tests/fixtures/hancom_saved/pages_page_number_then_a_table_wider_than_the_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_square_band_picture_as_character_among_text.hwpx b/tests/fixtures/hancom_saved/pages_square_band_picture_as_character_among_text.hwpx index 3ad0a4d3..9f37fbbe 100644 Binary files a/tests/fixtures/hancom_saved/pages_square_band_picture_as_character_among_text.hwpx and b/tests/fixtures/hancom_saved/pages_square_band_picture_as_character_among_text.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_square_band_table_as_character_alone.hwpx b/tests/fixtures/hancom_saved/pages_square_band_table_as_character_alone.hwpx index 57f866a5..ac8cd0b0 100644 Binary files a/tests/fixtures/hancom_saved/pages_square_band_table_as_character_alone.hwpx and b/tests/fixtures/hancom_saved/pages_square_band_table_as_character_alone.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_square_band_table_as_character_among_text.hwpx b/tests/fixtures/hancom_saved/pages_square_band_table_as_character_among_text.hwpx index d679fe53..967be599 100644 Binary files a/tests/fixtures/hancom_saved/pages_square_band_table_as_character_among_text.hwpx and b/tests/fixtures/hancom_saved/pages_square_band_table_as_character_among_text.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_wide_table_as_character_anchor_first.hwpx b/tests/fixtures/hancom_saved/pages_wide_table_as_character_anchor_first.hwpx index f1c51f08..8897d041 100644 Binary files a/tests/fixtures/hancom_saved/pages_wide_table_as_character_anchor_first.hwpx and b/tests/fixtures/hancom_saved/pages_wide_table_as_character_anchor_first.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_wide_table_as_character_tall_anchor_first.hwpx b/tests/fixtures/hancom_saved/pages_wide_table_as_character_tall_anchor_first.hwpx index e4227bff..f24794ac 100644 Binary files a/tests/fixtures/hancom_saved/pages_wide_table_as_character_tall_anchor_first.hwpx and b/tests/fixtures/hancom_saved/pages_wide_table_as_character_tall_anchor_first.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index f3ac879d..41f18f83 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -44,8 +44,8 @@ "pages_empty_paragraph_object_overlap_20pt_2000": 1, # An empty paragraph holding a table set as a character and a table placed top and bottom from its top (6000 # tall), the line 42520 wide: a character table wider than the line takes that line alone, the placed - # table's anchor going on an empty line of its own above it (anchor first) or below it (anchor last), the - # placed table at that line's top; one as wide as the line shares its line with the anchor. + # table's anchor going on that line (anchor first) or on an empty line of its own below it (anchor last), + # the placed table at that line's top; one as wide as the line shares its line with the anchor. # Five pictures 20000 x 11000 set as characters, each in its own paragraph (one page bare), with an effect: # Hancom lays each out as large as it and its glow (the radius on each side) or its outer shadow (moved its # distance in its direction, spread by its radius), the line that much taller @@ -68,7 +68,7 @@ "pages_para_shape_unsigned_offset_1500_h3000": 1, "pages_wide_table_as_character_anchor_first": 1, # 44000 x 3000 "pages_wide_table_as_character_anchor_last": 1, - "pages_wide_table_as_character_tall_anchor_first": 2, # 44000 x 60000: on the next page below the empty line + "pages_wide_table_as_character_tall_anchor_first": 3, # 44000 x 60000: on the next page's top, the anchor on its line "pages_wide_table_as_character_tall_anchor_last": 2, # the placed table's rows flowing on to the next page "pages_line_wide_table_as_character_anchor_first": 1, # 42520 x 3000 "pages_line_wide_table_as_character_anchor_last": 1, @@ -100,12 +100,12 @@ "pages_push_band_group_as_character_alone": 1, # the group and the polygon, no text # A picture wrapped square from an empty paragraph's left, 41631 x 27112, leaving 6557 beside it; the # next paragraph holds a table set as a character alone: - "pages_square_band_table_as_character_alone": 1, # 47058 wide: below the picture, the whole width + "pages_square_band_table_as_character_alone": 1, # 47058 wide: beside it all the same, reaching past the room "pages_square_band_narrow_table_as_character_alone": 1, # 5000 wide: beside it, and the lines after # ... or text whose lines in the room beside the picture are as wide as it, and an object set as a - # character among it: the first line holding one wider than the room goes below the picture, the whole - # width, the rest of the room left empty; one narrower stays beside it. - "pages_square_band_table_as_character_among_text": 2, # a table 47058 wide + # character among it: a line holding one stays beside the picture, one wider than the room alone on its + # line, reaching past it. + "pages_square_band_table_as_character_among_text": 1, # a table 47058 wide "pages_square_band_narrow_table_as_character_among_text": 1, # 5000 wide "pages_square_band_picture_as_character_among_text": 1, # a picture 47058 x 5000 # A picture 20000 x 27112 wrapped square in the middle of the column (10 pt text spaced 160%), flowing with @@ -417,16 +417,17 @@ "pages_table_as_character_200_short_then_three_12pt_spaces": 1, # of 12 pt: the third's line 1200 tall "pages_table_as_character_200_short_after_two_spaces": 1, # two before it: the table on the next line # A table set as a character (a row 1282 tall) 1000 wider than the column (42520), alone in its paragraph - # but for a page-number control: the two never share a line, the control's an empty line of the text. - "pages_page_number_then_a_table_wider_than_the_line": 1, # the control first: an empty line 1600 down, - # then the table's + # but for a page-number control: a control before it stands on the table's line, one after it on an empty + # line of the text under it. + "pages_page_number_then_a_table_wider_than_the_line": 1, # the control first: the table's line alone "pages_table_wider_than_the_line_then_a_page_number": 1, # the table first: the control's line under it "pages_page_number_then_a_table_that_fits": 1, # 1000 narrower than the column: one line "pages_table_wider_than_the_line_alone": 1, # no control: one line "pages_section_start_then_a_table_wider_than_the_line": 1, # the section's and columns' controls before # it: one line - # The same table beside one of the other controls: the same two lines, whichever the control. Before the - # table, an empty line 1600 down and the table's line under it; after it, the table's line first. + # The same table beside one of the other controls: the same lines, whichever the control. Before the + # table, on its line; after it, on an empty line under it -- but a number shown as a character before it + # takes an empty line 1600 down of its own, the table's line under it. "pages_page_hiding_then_a_table_wider_than_the_line": 1, # a hidden page number "pages_table_wider_than_the_line_then_a_page_hiding": 1, "pages_auto_number_then_a_table_wider_than_the_line": 1, # a page's number shown as a character @@ -445,6 +446,8 @@ "pages_table_wider_than_the_line_then_a_click_here_field": 1, "pages_index_mark_then_a_table_wider_than_the_line": 1, # an index mark "pages_table_wider_than_the_line_then_an_index_mark": 1, + "pages_bookmark_around_a_table_wider_than_the_line": 1, # before and after it: the table's line, the empty + "pages_click_here_field_around_a_table_wider_than_the_line": 1, # line under it # Tabs: a tab reaches the next default tab stop, every 4000 from the column's left edge (the first line # starting at the left margin and a first-line indent, a hanging indent off the other lines), and a line may # start on either side of one. 90 paragraphs of 바탕 10 pt Hangul, a tab, then two-syllable words, after 0-9