fix(magazine): dotted leaders and thin rules are not reported as missed text
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
19af236bcd
commit
32a23a237a
@ -38,6 +38,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
- **Articles nobody has reviewed are blue.** In the article list an unapproved, unrejected article has a blue background and edge and a *not reviewed* pill (approved keeps its green edge), so what still needs a person stands out.
|
||||
- **Overlapping boxes are fixed automatically.** Where a lower-ranked box runs over a higher-ranked one (headline > deck/byline > body > caption/sidebar/pull quote), the lower one is cut back to the largest part that no longer overlaps, unless that would leave under 40% of it (then both stay for a human). Hand-placed boxes and boxes of equal rank are never touched.
|
||||
- **Fewer false alarms and better fits:** a strip of solid ink beside a headline (a photo or filled panel behind it) is no longer counted as letters cut off; headline and deck boxes may grow up to 70 px sideways (body boxes 16 px); an inset pull-quote box that repeats text a box already holds becomes a duplicate.
|
||||
- **Rules and dotted leaders are not missed text.** The dots between a table-of-contents entry and its page number (and thin printed rules) are ink but no letters; the audit now ignores ink that is only a few pixel rows tall.
|
||||
- **Unboxed ink in or beside a picture, chart or advert is no longer reported as missed text** (labels of a chart, text set into a photo, an advert's own wording).
|
||||
|
||||
### Fixed
|
||||
|
||||
@ -2878,6 +2878,21 @@ _AUDIT_GRAPHIC_MARGIN = 10.0 # points: unboxed ink this close to a picture/ch
|
||||
_AUDIT_CLIP_STRIP_PX = 14 # how far outside a box edge to look for ink that belongs to it (~1-2 letters)
|
||||
|
||||
|
||||
_RULE_MAX_INK_ROWS = 6 # pixel rows with ink: a rule or dotted leader is a few, a text line is 8+
|
||||
|
||||
|
||||
def _is_rule_only(pixels, blob: Sequence[float], page_meta: dict[str, Any]) -> bool:
|
||||
"""True when the ink inside `blob` is only a thin rule or a dotted leader (the dots between a
|
||||
table-of-contents entry and its page number), which has no letters and nothing to narrate."""
|
||||
height, width = pixels.shape
|
||||
sx, sy = width / max(1.0, float(page_meta["width"])), height / max(1.0, float(page_meta["height"]))
|
||||
x, y, w, h = blob
|
||||
region = pixels[max(0, int(y * sy)):int((y + h) * sy) + 1, max(0, int(x * sx)):int((x + w) * sx) + 1]
|
||||
if region.size == 0:
|
||||
return False
|
||||
return int((region.sum(axis=1) >= 3).sum()) <= _RULE_MAX_INK_ROWS
|
||||
|
||||
|
||||
def _clipped_edges(pixels, page_meta: dict[str, Any], text_blocks: list[dict[str, Any]],
|
||||
boxes: list[Sequence[float]]) -> list[dict[str, Any]]:
|
||||
"""Text boxes whose edge cuts through text: ink in a thin strip just outside a side, ignoring
|
||||
@ -3534,6 +3549,8 @@ def audit_page_coverage(doc_dir: Path, layout: dict[str, Any], page_no: int) ->
|
||||
# graphic, not narratable text a box forgot: a blob in or beside one is not reported.
|
||||
if any(bbox_overlaps(blob, g, tol=_AUDIT_GRAPHIC_MARGIN) for g in graphic_boxes):
|
||||
continue
|
||||
if pixels is not None and _is_rule_only(pixels, blob, page_meta):
|
||||
continue # a printed rule or dotted leader, not letters
|
||||
kept_cells += len(rows)
|
||||
report["uncovered"].append(blob)
|
||||
report["uncovered_ratio"] = round(kept_cells / total, 4)
|
||||
|
||||
@ -3269,3 +3269,33 @@ class ClippedEdgeIgnoresSolidBackgroundsTests(unittest.TestCase):
|
||||
def test_text_running_past_the_edge_is_clipping_but_a_solid_panel_is_not(self):
|
||||
self.assertEqual([c["side"] for c in self.audit(False)], ["right"])
|
||||
self.assertEqual(self.audit(True), [])
|
||||
|
||||
|
||||
class LeaderLineIsNotMissedTextTests(unittest.TestCase):
|
||||
def audit(self, draw_fn):
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageDraw
|
||||
from core.magazine import audit_page_coverage
|
||||
d = Path(tempfile.mkdtemp(prefix="ttvc-rule-"))
|
||||
self.addCleanup(shutil.rmtree, d, True)
|
||||
(d / "pages").mkdir()
|
||||
im = Image.new("RGB", (600, 800), (248, 242, 244))
|
||||
draw_fn(ImageDraw.Draw(im))
|
||||
im.save(d / "pages" / "0001.png")
|
||||
layout = {"pages": [{"page": 1, "width": 600, "height": 800}], "images": [], "blocks": []}
|
||||
return audit_page_coverage(d, layout, 1)["uncovered"]
|
||||
|
||||
def test_a_dotted_leader_or_rule_is_not_reported_but_text_lines_are(self):
|
||||
def leader(draw):
|
||||
for x in range(200, 400, 6): # dots between a heading and its page number
|
||||
draw.rectangle([x, 300, x + 1, 302], fill=(93, 91, 92))
|
||||
self.assertEqual(self.audit(leader), [])
|
||||
|
||||
def rule(draw):
|
||||
draw.rectangle([100, 500, 400, 502], fill=(93, 91, 92))
|
||||
self.assertEqual(self.audit(rule), [])
|
||||
|
||||
def text(draw):
|
||||
for y in range(300, 360, 12):
|
||||
draw.rectangle([100, y, 300, y + 8], fill=(93, 91, 92))
|
||||
self.assertEqual(len(self.audit(text)), 1)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user