Skip to content

Commit d148f4c

Browse files
authored
@ (#55)
Drop rotated/vertical text runs from PDF extraction Academic PDFs with a rotated left-margin stamp (arXiv: "arXiv:1706.03762v7 [cs.CL] 2 Aug 2023") had that vertical text read in reverse and it survived as junk headings/titles — the document title became "3202 guA" (reversed "Aug 2023") and sections like "]LC.sc[" (reversed "[cs.CL]") and "7v26730.6071:viXra". pdftable already classifies each word run: extractPDFRows now skips runs that are not Upright or whose reading Direction is vertical ("ttb"/"btt") via isRotatedRun. This removes margin stamps, page-edge watermarks, and rotated figure labels — page furniture, never body prose — while keeping upright LTR and RTL text untouched. Verified on the arXiv "Attention Is All You Need" PDF: title now "Attention Is All You Need", zero reversed-stamp sections, clean outline (Abstract / 1 Introduction / 2 Background / 3 Model Architecture). @
1 parent 7fb8432 commit d148f4c

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

pkg/parser/pdf.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,17 @@ func extractPDFRows(doc pdftable.Document) ([]pdfRow, error) {
11521152
return b
11531153
}
11541154
for _, wd := range words {
1155+
// Drop rotated / vertical text runs — arXiv left-margin
1156+
// stamps, page-edge watermarks, rotated figure labels. These
1157+
// are page furniture, not body prose, and pdftable reads a
1158+
// rotated run in an order that surfaces as reversed text
1159+
// ("3202 guA" for a rotated "Aug 2023", "]LC.sc[" for
1160+
// "[cs.CL]"), which then pollutes the outline with junk
1161+
// headings/titles. Upright left-to-right / right-to-left
1162+
// prose (including real RTL languages) is kept.
1163+
if isRotatedRun(wd.Upright, wd.Direction) {
1164+
continue
1165+
}
11551166
b := find(wd.Y1)
11561167
b.words = append(b.words, wd)
11571168
if wd.FontSize > b.maxFS {
@@ -1531,6 +1542,24 @@ func looksLikeHeading(s string) bool {
15311542
return true
15321543
}
15331544

1545+
// isRotatedRun reports whether a pdftable word run is rotated / vertical
1546+
// text — page furniture (margin stamps, watermarks, rotated labels)
1547+
// rather than body prose. pdftable marks such a run as not Upright and/or
1548+
// with a vertical reading Direction ("ttb"/"btt"); it reads them in an
1549+
// order that surfaces as reversed text, so dropping them keeps the outline
1550+
// clean. Upright horizontal prose in either LTR or RTL scripts is body
1551+
// text and is never dropped.
1552+
func isRotatedRun(upright bool, direction string) bool {
1553+
if !upright {
1554+
return true
1555+
}
1556+
switch strings.ToLower(direction) {
1557+
case "ttb", "btt":
1558+
return true
1559+
}
1560+
return false
1561+
}
1562+
15341563
// isBoldFont reports whether a PDF font name denotes a bold weight. SEC filing
15351564
// section headings are typically bold at body font size (not larger), so this is
15361565
// how we recover them — a size-only heuristic misses them entirely.

pkg/parser/rotated_text_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package parser
2+
3+
import "testing"
4+
5+
// TestIsRotatedRun locks the orientation filter that keeps rotated /
6+
// vertical page furniture (arXiv margin stamps, watermarks) out of the
7+
// extracted row stream while preserving upright prose in any script.
8+
func TestIsRotatedRun(t *testing.T) {
9+
cases := []struct {
10+
name string
11+
upright bool
12+
direction string
13+
want bool
14+
}{
15+
{"upright ltr prose is kept", true, "ltr", false},
16+
{"upright rtl prose is kept", true, "rtl", false},
17+
{"upright with empty direction is kept", true, "", false},
18+
{"not upright is dropped", false, "ltr", true},
19+
{"vertical top-to-bottom is dropped", true, "ttb", true},
20+
{"vertical bottom-to-top is dropped", true, "btt", true},
21+
{"vertical, case-insensitive", true, "TTB", true},
22+
{"not upright AND vertical is dropped", false, "btt", true},
23+
}
24+
for _, tc := range cases {
25+
t.Run(tc.name, func(t *testing.T) {
26+
if got := isRotatedRun(tc.upright, tc.direction); got != tc.want {
27+
t.Errorf("isRotatedRun(%v, %q) = %v, want %v", tc.upright, tc.direction, got, tc.want)
28+
}
29+
})
30+
}
31+
}

0 commit comments

Comments
 (0)