python-hwpx 3.4.1__py3-none-any.whl → 3.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hwpx/form_fit/engine.py CHANGED
@@ -17,13 +17,32 @@ from __future__ import annotations
17
17
 
18
18
  from dataclasses import dataclass, replace
19
19
 
20
- from .measure import SlotMetrics, estimate_text_width, measure
20
+ from .measure import (
21
+ GROSS_ROW_GROWTH_FACTOR,
22
+ MIN_ROW_GROWTH_LINES,
23
+ SlotMetrics,
24
+ estimate_text_width,
25
+ measure,
26
+ )
21
27
  from .policy import FitPolicy
22
28
  from .report import FitResult
23
29
 
24
30
  # Font ladder step (points) used when shrinking.
25
31
  _SHRINK_STEP_PT = 0.5
26
32
 
33
+ # The vertical room could not be measured (merged cell, no height, or an auto-grow
34
+ # floor); the fit is width-only and the oracle must confirm the vertical fit.
35
+ _HEIGHT_UNAVAILABLE_NOTE = (
36
+ "cell height is not a usable budget (merged/auto-grow); fit is width-only — "
37
+ "render oracle should confirm the vertical fit"
38
+ )
39
+ # The content needs more vertical room than the authored cell height, but not
40
+ # grossly — the row will grow modestly. Reported (never silent), deferred to oracle.
41
+ _ROW_GROWTH_NOTE = (
42
+ "value needs more lines than the cell height budgets; the row will grow — "
43
+ "render oracle should confirm no page shift"
44
+ )
45
+
27
46
 
28
47
  @dataclass(slots=True)
29
48
  class FitEngine:
@@ -63,7 +82,7 @@ class FitEngine:
63
82
  wrap_slot = replace(slot, max_lines=wrap_lines)
64
83
  measurement = measure(value, wrap_slot)
65
84
  if measurement.fits:
66
- return FitResult(
85
+ result = FitResult(
67
86
  ok=True,
68
87
  value=value,
69
88
  applied_value=value,
@@ -78,11 +97,15 @@ class FitEngine:
78
97
  warnings=list(measurement.notes),
79
98
  field_id=field_id,
80
99
  )
100
+ return self._apply_height_budget(result, value, slot, policy, wrap_lines, field_id)
81
101
 
82
102
  if policy.may_shrink:
83
103
  shrunk = self._try_shrink(value, slot, policy, wrap_lines)
84
104
  if shrunk is not None:
85
- return replace(shrunk, field_id=field_id)
105
+ shrunk = replace(shrunk, field_id=field_id)
106
+ return self._apply_height_budget(
107
+ shrunk, value, slot, policy, wrap_lines, field_id
108
+ )
86
109
 
87
110
  # Still overflowing → terminal overflow action (confidence-gated).
88
111
  return self._overflow(value, wrap_slot, policy, field_id, measurement)
@@ -212,6 +235,205 @@ class FitEngine:
212
235
  candidate = round(candidate - _SHRINK_STEP_PT, 4)
213
236
  return best
214
237
 
238
+ # -- vertical (row-height) budget ------------------------------------- #
239
+ def _apply_height_budget(
240
+ self,
241
+ result: FitResult,
242
+ value: str,
243
+ slot: SlotMetrics,
244
+ policy: FitPolicy,
245
+ wrap_lines: int,
246
+ field_id: str | None,
247
+ ) -> FitResult:
248
+ """Overlay the authored row-height budget on a width-fitting *result*.
249
+
250
+ The width ladder decides horizontal fit; this decides whether the value
251
+ also sits inside the cell's *vertical* room, so a filled row does not
252
+ silently grow and shift the page (the dominant M9 form-fill defect).
253
+
254
+ Honesty contract (mirrors the width path, grounded in the S-085 P0/P1
255
+ measurement): the authored ``cellSz.height`` is an unreliable proxy in wild
256
+ forms (auto-grow, tiny floors, merges), so a *modest* vertical overflow is
257
+ reported and deferred to the render oracle; only a *gross* balloon shrinks
258
+ or fails closed. ``allow_row_expand`` / ``expand_row`` opt out entirely.
259
+ """
260
+
261
+ if slot.available_height is None:
262
+ if slot.height_unavailable and result.ok:
263
+ result.warnings.append(_HEIGHT_UNAVAILABLE_NOTE)
264
+ return result
265
+ if policy.allow_row_expand or policy.mode == "expand_row":
266
+ return result
267
+
268
+ font = result.font_pt if result.font_pt is not None else slot.font_pt
269
+ wrapped = result.lines if result.lines is not None else 1
270
+ verdict = self._height_verdict(slot, wrapped, font)
271
+ if verdict == "fits":
272
+ return result
273
+ if verdict == "modest":
274
+ # S-085 P1 calibration round 2 (differential-measured): the modest
275
+ # band is where pages actually shift, so try to shrink INTO the
276
+ # budget first; only defer to the oracle when no font >= min can
277
+ # land the value fully inside the authored height.
278
+ if policy.may_shrink:
279
+ shrunk = self._try_shrink_for_height(
280
+ value, slot, policy, wrap_lines, field_id, accept_modest=False
281
+ )
282
+ if shrunk is not None:
283
+ return shrunk
284
+ result.overflow_detected = True
285
+ result.warnings.append(_ROW_GROWTH_NOTE)
286
+ return result
287
+
288
+ # Gross vertical balloon: shrink (prefer a full fit, else the least
289
+ # growth), else fail-closed.
290
+ if policy.may_shrink:
291
+ shrunk = self._try_shrink_for_height(
292
+ value, slot, policy, wrap_lines, field_id, accept_modest=True
293
+ )
294
+ if shrunk is not None:
295
+ return shrunk
296
+ return self._overflow_height(value, slot, policy, field_id, wrapped, font)
297
+
298
+ def _height_verdict(self, slot: SlotMetrics, wrapped_lines: int, font_pt: float) -> str:
299
+ """Classify vertical fit as ``fits`` / ``modest`` / ``gross``."""
300
+
301
+ budget = slot.height_lines(font_pt)
302
+ if budget is None or wrapped_lines <= budget:
303
+ return "fits"
304
+ optimistic = slot.height_lines_optimistic(font_pt) or budget
305
+ if (
306
+ wrapped_lines > optimistic * GROSS_ROW_GROWTH_FACTOR
307
+ and (wrapped_lines - optimistic) >= MIN_ROW_GROWTH_LINES
308
+ ):
309
+ return "gross"
310
+ return "modest"
311
+
312
+ def _try_shrink_for_height(
313
+ self,
314
+ value: str,
315
+ slot: SlotMetrics,
316
+ policy: FitPolicy,
317
+ wrap_lines: int,
318
+ field_id: str | None,
319
+ *,
320
+ accept_modest: bool,
321
+ ) -> FitResult | None:
322
+ """Largest font (>= min) that lands the value INSIDE the height budget.
323
+
324
+ Walks the shrink ladder preferring a full vertical fit ("fits"). When
325
+ ``accept_modest`` is true and no font achieves a full fit, the largest
326
+ candidate whose growth is merely *modest* (never gross) is returned as
327
+ the least-damage fallback, reported and deferred to the oracle.
328
+ """
329
+
330
+ ceiling = policy.max_font_pt or slot.font_pt
331
+ candidate = min(slot.font_pt, ceiling)
332
+ modest_candidate: tuple[float, int] | None = None
333
+ while candidate >= policy.min_font_pt - 1e-9:
334
+ trial = replace(slot, font_pt=candidate, max_lines=wrap_lines)
335
+ m = measure(value, trial)
336
+ if m.fits and m.confidence == "high":
337
+ verdict = self._height_verdict(slot, m.lines, candidate)
338
+ if verdict == "fits":
339
+ return self._shrunk_result(
340
+ value, slot, candidate, m.lines, wrap_lines, field_id,
341
+ modest=False,
342
+ )
343
+ if verdict == "modest" and modest_candidate is None:
344
+ modest_candidate = (candidate, m.lines)
345
+ candidate = round(candidate - _SHRINK_STEP_PT, 4)
346
+ if accept_modest and modest_candidate is not None:
347
+ font_pt, lines = modest_candidate
348
+ return self._shrunk_result(
349
+ value, slot, font_pt, lines, wrap_lines, field_id, modest=True
350
+ )
351
+ return None
352
+
353
+ def _shrunk_result(
354
+ self,
355
+ value: str,
356
+ slot: SlotMetrics,
357
+ candidate: float,
358
+ lines: int,
359
+ wrap_lines: int,
360
+ field_id: str | None,
361
+ *,
362
+ modest: bool,
363
+ ) -> FitResult:
364
+ changes: dict[str, object] = {}
365
+ warnings: list[str] = []
366
+ if candidate != slot.font_pt:
367
+ changes = {
368
+ "font_pt": round(candidate, 2),
369
+ "from_font_pt": round(slot.font_pt, 2),
370
+ }
371
+ warnings.append(
372
+ f"font shrunk {slot.font_pt:g}pt → {candidate:g}pt to fit the row height"
373
+ )
374
+ if wrap_lines > 1 and lines > 1:
375
+ changes["wrapped_lines"] = lines
376
+ if modest:
377
+ warnings.append(_ROW_GROWTH_NOTE)
378
+ return FitResult(
379
+ ok=True,
380
+ value=value,
381
+ applied_value=value,
382
+ applied_style_changes=changes,
383
+ lines=lines,
384
+ font_pt=round(candidate, 2),
385
+ confidence="high",
386
+ overflow_detected=modest,
387
+ warnings=warnings,
388
+ field_id=field_id,
389
+ )
390
+
391
+ def _overflow_height(
392
+ self,
393
+ value: str,
394
+ slot: SlotMetrics,
395
+ policy: FitPolicy,
396
+ field_id: str | None,
397
+ wrapped_lines: int,
398
+ font_pt: float,
399
+ ) -> FitResult:
400
+ """Terminal action for a gross vertical balloon that shrink cannot resolve."""
401
+
402
+ budget = slot.height_lines(font_pt)
403
+ action = policy.overflow
404
+ # Line-level truncation is not modelled; keep the value and warn (honest).
405
+ if action == "truncate":
406
+ action = "warn"
407
+ if action == "warn":
408
+ return FitResult(
409
+ ok=True,
410
+ value=value,
411
+ applied_value=value,
412
+ lines=wrapped_lines,
413
+ font_pt=font_pt,
414
+ overflow_detected=True,
415
+ confidence="high",
416
+ warnings=[
417
+ f"value needs ~{wrapped_lines} lines but the row height holds "
418
+ f"~{budget}; the row will balloon (overflow=warn)"
419
+ ],
420
+ field_id=field_id,
421
+ )
422
+ return FitResult(
423
+ ok=False,
424
+ value=value,
425
+ applied_value=value,
426
+ lines=wrapped_lines,
427
+ font_pt=font_pt,
428
+ overflow_detected=True,
429
+ confidence="high",
430
+ errors=[
431
+ f"FIELD_OVERFLOW: value needs ~{wrapped_lines} lines but the row height "
432
+ f"holds ~{budget}; shrink/shorten the value or allow row expansion"
433
+ ],
434
+ field_id=field_id,
435
+ )
436
+
215
437
  # -- terminal overflow action ----------------------------------------- #
216
438
  def _overflow(
217
439
  self,
hwpx/form_fit/measure.py CHANGED
@@ -53,6 +53,31 @@ _CLASS_UNCERTAINTY: dict[str, float] = {
53
53
  # multiplicative factor so it scales and also buys headroom on the advance error.
54
54
  DEFAULT_SAFETY = 0.93
55
55
 
56
+ # --- Vertical (line-height) model ------------------------------------------- #
57
+ # Per-line vertical advance as a multiple of the em (font height in HWPUNIT).
58
+ # HWP's application default is 160% line spacing, so a line occupies ~1.6 em; this
59
+ # is the advance used when a paragraph declares no explicit lineSpacing, and it
60
+ # matches ``layout.lint._LINE_SPACING``. Used as the *expected* pitch for the
61
+ # authored vertical budget (the point at which the row is warned it will grow).
62
+ DEFAULT_LINE_SPACING_RATIO = 1.6
63
+
64
+ # Tightest per-line advance Hancom is observed to use (a glyph box with no leading).
65
+ # Measured on the M9 wild-form corpus: two soft-wrapped lines render inside cells
66
+ # only ~2.0 em tall, so the real pitch bottoms out near 1.0 em. Used to compute the
67
+ # *optimistic* (most generous) height budget, so a vertical overflow is only treated
68
+ # as high confidence when content overflows even at this tightest pitch.
69
+ MIN_LINE_SPACING_RATIO = 1.0
70
+
71
+ # A vertical overflow is a *hard* balloon (shrink-or-refuse) only when content needs
72
+ # at least this multiple of the optimistic authored line budget AND this many extra
73
+ # lines in absolute terms. Below the gross threshold the row may grow only modestly
74
+ # and the render oracle arbitrates. Measure-first calibration on the M9 corpus: a
75
+ # hard cap at the optimistic budget mis-refuses 47% of known-good multi-line cells,
76
+ # whereas this gross gate mis-refuses 2% — it fires on page-shifting balloons only.
77
+ # (Same balloon philosophy as ``layout.lint._BALLOON_FACTOR``.)
78
+ GROSS_ROW_GROWTH_FACTOR = 2.0
79
+ MIN_ROW_GROWTH_LINES = 2
80
+
56
81
  Confidence = Literal["high", "low"]
57
82
 
58
83
 
@@ -189,11 +214,62 @@ class SlotMetrics:
189
214
  max_lines: int = 1
190
215
  raw_width: float | None = None # cellSz.width before margins (diagnostics)
191
216
  source: str = "cell"
217
+ # Vertical budget. ``available_height`` is the usable inner height (HWPUNIT)
218
+ # after top/bottom cell margins + the safety inset. ``None`` means the vertical
219
+ # room was not (or could not be) measured, and the fit stays width-only.
220
+ available_height: float | None = None
221
+ # Per-line advance as a multiple of the em, from the cell's declared paragraph
222
+ # line spacing (PERCENT). ``None`` falls back to ``DEFAULT_LINE_SPACING_RATIO``.
223
+ line_spacing_ratio: float | None = None
224
+ # A cell height existed but was unusable (merged row-span fragment, or an
225
+ # auto-grow floor shorter than one line). Records "height budget unavailable"
226
+ # so the fit reports width-only honestly rather than guessing a vertical fit.
227
+ height_unavailable: bool = False
192
228
 
193
229
  @property
194
230
  def capacity(self) -> float:
195
231
  return self.available_width * self.max_lines
196
232
 
233
+ def _line_ratio(self) -> float:
234
+ ratio = self.line_spacing_ratio
235
+ return ratio if ratio and ratio > 0 else DEFAULT_LINE_SPACING_RATIO
236
+
237
+ def line_height(self, font_pt: float | None = None) -> float:
238
+ """Expected per-line vertical advance in HWPUNIT at *font_pt*."""
239
+
240
+ pt = self.font_pt if font_pt is None else font_pt
241
+ return pt * 100.0 * self._line_ratio()
242
+
243
+ def height_lines(self, font_pt: float | None = None) -> int | None:
244
+ """Expected vertical line budget at *font_pt* (declared/default pitch).
245
+
246
+ ``None`` when the vertical room is unmeasured. Never less than 1 — a cell
247
+ always accommodates its first line; the risk we guard is *growth* past it.
248
+ """
249
+
250
+ if self.available_height is None:
251
+ return None
252
+ line_h = self.line_height(font_pt)
253
+ if line_h <= 0:
254
+ return None
255
+ return max(int(self.available_height // line_h), 1)
256
+
257
+ def height_lines_optimistic(self, font_pt: float | None = None) -> int | None:
258
+ """Most-generous vertical budget (tightest plausible pitch).
259
+
260
+ This is the basis for the *confidence* of a vertical overflow: content that
261
+ overflows even this budget is grossly too tall regardless of pitch error.
262
+ """
263
+
264
+ if self.available_height is None:
265
+ return None
266
+ pt = self.font_pt if font_pt is None else font_pt
267
+ ratio = min(self._line_ratio(), MIN_LINE_SPACING_RATIO)
268
+ line_h = pt * 100.0 * ratio
269
+ if line_h <= 0:
270
+ return None
271
+ return max(int(self.available_height // line_h), 1)
272
+
197
273
 
198
274
  @dataclass(slots=True)
199
275
  class Measurement:
@@ -291,6 +367,50 @@ def _cell_margin(cell_element: object) -> tuple[int, int]:
291
367
  return (0, 0)
292
368
 
293
369
 
370
+ def _cell_margin_vertical(cell_element: object) -> tuple[int, int]:
371
+ """Return (top, bottom) cellMargin in HWPUNIT, defaulting to 0."""
372
+
373
+ for child in cell_element: # type: ignore[attr-defined]
374
+ if _local_name(child.tag) == "cellMargin":
375
+ return (
376
+ int(child.get("top", "0") or 0),
377
+ int(child.get("bottom", "0") or 0),
378
+ )
379
+ return (0, 0)
380
+
381
+
382
+ def _first_para_line_spacing_ratio(cell: object, document: object) -> float | None:
383
+ """Per-line em multiple from the cell's first paragraph line spacing (PERCENT).
384
+
385
+ Only PERCENT spacing maps cleanly onto the em-relative line-height model; FIXED
386
+ / ATLEAST / BETWEENLINES are left to the conservative default so we never invent
387
+ a font-independent pitch (the shrink ladder varies the font).
388
+ """
389
+
390
+ try:
391
+ paragraphs = cell.paragraphs # type: ignore[attr-defined]
392
+ except Exception: # pragma: no cover - defensive
393
+ return None
394
+ for paragraph in paragraphs:
395
+ ref = getattr(paragraph, "para_pr_id_ref", None)
396
+ if ref is None or document is None:
397
+ continue
398
+ try:
399
+ prop = document.paragraph_property(ref) # type: ignore[attr-defined]
400
+ except Exception: # pragma: no cover - defensive
401
+ prop = None
402
+ spacing = getattr(prop, "line_spacing", None) if prop is not None else None
403
+ if spacing is None or not getattr(spacing, "value", None):
404
+ continue
405
+ if (getattr(spacing, "spacing_type", None) or "PERCENT").upper() != "PERCENT":
406
+ return None
407
+ try:
408
+ return int(spacing.value) / 100.0
409
+ except (TypeError, ValueError): # pragma: no cover - defensive
410
+ return None
411
+ return None
412
+
413
+
294
414
  def _first_run_font_pt(cell: object, document: object) -> float:
295
415
  """Resolve the cell's first run font size in points (default 10pt)."""
296
416
 
@@ -339,6 +459,14 @@ def resolve_slot_metrics(
339
459
  verified against Hancom's own ``lineSeg/@horzsize`` (±10 HWPUNIT on 82% of
340
460
  cells; the safety factor covers the rest plus paragraph indent, which is left
341
461
  to the HarfBuzz pass).
462
+
463
+ ``available_height`` follows the same philosophy — ``(cellSz.height - top -
464
+ bottom margin) * safety`` — but is recorded as *unavailable* (``None`` +
465
+ ``height_unavailable``) whenever the authored height is not a real ceiling:
466
+ a merged cell (its ``cellSz.height`` is a single-row fragment, not the spanned
467
+ height), a cell with no height, or an auto-grow floor shorter than one line
468
+ (Hancom simply grows the row past it). The fit then stays width-only rather
469
+ than guess a vertical fit.
342
470
  """
343
471
 
344
472
  raw_width = float(getattr(cell, "width", 0) or 0)
@@ -346,12 +474,35 @@ def resolve_slot_metrics(
346
474
  left, right = _cell_margin(element) if element is not None else (0, 0)
347
475
  inner = max(raw_width - left - right, 0.0) * safety
348
476
  resolved_pt = font_pt if font_pt is not None else _first_run_font_pt(cell, document)
477
+ line_ratio = _first_para_line_spacing_ratio(cell, document)
478
+
479
+ available_height: float | None = None
480
+ height_unavailable = False
481
+ raw_height = float(getattr(cell, "height", 0) or 0)
482
+ try:
483
+ row_span = int(getattr(cell, "span", (1, 1))[0])
484
+ except Exception: # pragma: no cover - defensive
485
+ row_span = 1
486
+ top, bottom = _cell_margin_vertical(element) if element is not None else (0, 0)
487
+ inner_h = max(raw_height - top - bottom, 0.0) * safety if raw_height > 0 else 0.0
488
+ # A cell authored shorter than one line at the tightest pitch is an auto-grow
489
+ # floor, not a ceiling (Hancom grows the row past it); a merged row-span's
490
+ # cellSz.height is only one of the spanned rows. Neither is a usable budget.
491
+ one_line = resolved_pt * 100.0 * MIN_LINE_SPACING_RATIO
492
+ if row_span <= 1 and inner_h >= one_line:
493
+ available_height = inner_h
494
+ else:
495
+ height_unavailable = True
496
+
349
497
  return SlotMetrics(
350
498
  available_width=inner,
351
499
  font_pt=resolved_pt,
352
500
  max_lines=max(max_lines, 1),
353
501
  raw_width=raw_width,
354
502
  source="cell",
503
+ available_height=available_height,
504
+ line_spacing_ratio=line_ratio,
505
+ height_unavailable=height_unavailable,
355
506
  )
356
507
 
357
508
 
@@ -360,6 +511,10 @@ __all__ = [
360
511
  "Measurement",
361
512
  "Confidence",
362
513
  "DEFAULT_SAFETY",
514
+ "DEFAULT_LINE_SPACING_RATIO",
515
+ "MIN_LINE_SPACING_RATIO",
516
+ "GROSS_ROW_GROWTH_FACTOR",
517
+ "MIN_ROW_GROWTH_LINES",
363
518
  "classify_char",
364
519
  "char_advance",
365
520
  "estimate_text_width",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 3.4.1
3
+ Version: 3.5.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -70,7 +70,7 @@ Dynamic: license-file
70
70
  `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
71
71
  “first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
72
72
 
73
- 현재 PyPI 공개 릴리스는 `python-hwpx 3.4.1`입니다. 일반
73
+ 현재 PyPI 공개 릴리스는 `python-hwpx 3.5.0`입니다. 일반
74
74
  `pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
75
75
  현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
76
76
  성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
@@ -94,8 +94,8 @@ hwpx/exam/parser.py,sha256=hA_oq_8i1XpmzJ8xw1L7R6LenxREKRvlOwMMXbs9Mpc,5026
94
94
  hwpx/exam/profile.py,sha256=pEYpgwoNUuMToHMJjiknHykjXs0UobGuYW67N_3VnEQ,4071
95
95
  hwpx/form_fit/__init__.py,sha256=l1HcFge4u_wL2vogB-VxSMrb-8h7zUT_IgTWMtnH9CM,1779
96
96
  hwpx/form_fit/apply.py,sha256=X2q7K-mLku4AXxm4-xP80QGzhBlpzpqA_Ade8aPXzNY,3450
97
- hwpx/form_fit/engine.py,sha256=6E_OItLwd9k8670bEKlqVQPGjtl3EyRzYdbmSVllvqo,11088
98
- hwpx/form_fit/measure.py,sha256=NZYa6GkoHOf_CIennIrYmuyWltPVh4p6u4_eBuiTb8M,12792
97
+ hwpx/form_fit/engine.py,sha256=N6twcIS-xjVdkCtVy03cHG8dGtRDtYV7pTRMhsPQ8rc,19799
98
+ hwpx/form_fit/measure.py,sha256=HZfcsCrvv4GOPIp8Tiz7BHpw_Hv0LGWrpkpV19PKFPg,20221
99
99
  hwpx/form_fit/policy.py,sha256=iXAatjS7ZycOdKgMCL4Q_kYg0qnogFPdRNlr_ecOvjc,3106
100
100
  hwpx/form_fit/report.py,sha256=h1NzQfbj1JYvZtKUi0DckJNWGnUHMZtQ3iCzbUxUjHA,3305
101
101
  hwpx/form_fit/seal.py,sha256=QsPzFagaEi7UKAXS-iFwiZs8rLG-_Fo9RixLc2OaqbU,17640
@@ -199,10 +199,10 @@ hwpx/visual/page_qa.py,sha256=AuJCySfLIdXjPeekhAc3YnrIWOsN0sI35Pnz8BZ6Pro,7278
199
199
  hwpx/visual/qa_contracts.py,sha256=1cjoiRTAWgi7NgE8SPc6bKxdoUfKx9nyou1mgC47TxY,10506
200
200
  hwpx/visual/qa_metrics.py,sha256=I7RdybN-f1Fvcv2j-ceDPoek51nOkMui1Zrd7TEX_C8,9838
201
201
  hwpx/visual/report.py,sha256=2RhXN1KBYOZTim9FNpeUhaaDHR7oFxI6Z2DLUkDIiwE,1717
202
- python_hwpx-3.4.1.dist-info/licenses/LICENSE,sha256=_ubz4wv-BkkT3l3gu-QuH7JGeVjuRYGZoZK95eNsCHU,9688
203
- python_hwpx-3.4.1.dist-info/licenses/NOTICE,sha256=k48h6EaGQE8Y1c0dS9sIOOcz4YqkbcImWClF7pBOgsg,2473
204
- python_hwpx-3.4.1.dist-info/METADATA,sha256=JO7hIzzT3sXVefC4Jux8vV4nRxLGiOcnVqTed4aXmPs,13561
205
- python_hwpx-3.4.1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
206
- python_hwpx-3.4.1.dist-info/entry_points.txt,sha256=PVUBTBQtBHiflQ9XBjfd004w-LrDibgZjwzxgu8Tx7w,480
207
- python_hwpx-3.4.1.dist-info/top_level.txt,sha256=R1iToqDh80Nf2oQhRjTN0rbN2X6kyDUizIocZjkhuxc,5
208
- python_hwpx-3.4.1.dist-info/RECORD,,
202
+ python_hwpx-3.5.0.dist-info/licenses/LICENSE,sha256=_ubz4wv-BkkT3l3gu-QuH7JGeVjuRYGZoZK95eNsCHU,9688
203
+ python_hwpx-3.5.0.dist-info/licenses/NOTICE,sha256=k48h6EaGQE8Y1c0dS9sIOOcz4YqkbcImWClF7pBOgsg,2473
204
+ python_hwpx-3.5.0.dist-info/METADATA,sha256=dokrEArymZrHXyslBq1OhwQwYg3NhnNSeFLpHx10ppk,13561
205
+ python_hwpx-3.5.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
206
+ python_hwpx-3.5.0.dist-info/entry_points.txt,sha256=PVUBTBQtBHiflQ9XBjfd004w-LrDibgZjwzxgu8Tx7w,480
207
+ python_hwpx-3.5.0.dist-info/top_level.txt,sha256=R1iToqDh80Nf2oQhRjTN0rbN2X6kyDUizIocZjkhuxc,5
208
+ python_hwpx-3.5.0.dist-info/RECORD,,