Skip to content

Commit 15940d3

Browse files
committed
fix: numbered headings only deepen sub-numbers; always emit section tokens
- numbered-heading override regressed nesting (numbered sections jumped to level 1 while unnumbered siblings stayed level 2). Now a numbered prefix only adds depth for sub-numbers (3.1, 3.1.2), preserving the font-size structure for top-level headings. - SectionView.tokens drops omitempty so 0-token nodes (root/internal) still emit the field — SDK DocumentTree requires it.
1 parent 35f3adc commit 15940d3

2 files changed

Lines changed: 18 additions & 13 deletions

File tree

pkg/parser/pdf.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,12 @@ func (*PDF) Parse(_ context.Context, r io.Reader) (*ParsedDoc, error) {
135135
}
136136
lvl, isHeading := levelForSize[roundSize(row.fontSize)]
137137
if isHeading && looksLikeHeading(text) {
138-
// A numbered prefix ("3", "3.1", "3.1.2") is a more reliable
139-
// depth signal than font-size bucketing — use it to set the
140-
// level when present (1 + number of dot groups).
141-
if nlvl, ok := numberedHeadingLevel(text); ok {
142-
lvl = nlvl
138+
// A *sub-numbered* prefix ("3.1", "3.1.2") signals extra nesting
139+
// depth relative to the font-derived level. We only ever DEEPEN
140+
// (never change the base level), so top-level headings — numbered
141+
// "3" or not ("Abstract") — stay siblings at the same font level.
142+
if nd, ok := numberedHeadingDepth(text); ok && nd > 1 {
143+
lvl += nd - 1
143144
}
144145
current = &flat{level: lvl, title: text}
145146
flats = append(flats, current)
@@ -527,19 +528,23 @@ func isBoilerplateLine(s string) bool {
527528
// "3.1.2", or "3." followed by whitespace and the heading text.
528529
var numberedHeadingRe = regexp.MustCompile(`^(\d+(?:\.\d+)*)\.?\s+\S`)
529530

530-
// numberedHeadingLevel returns the heading depth implied by a leading
531+
// numberedHeadingDepth returns the nesting depth implied by a leading
531532
// section number: "3 Foo" → 1, "3.1 Bar" → 2, "3.1.2 Baz" → 3. Returns
532-
// ok=false when there is no numbered prefix. Levels are clamped to 6.
533-
func numberedHeadingLevel(s string) (int, bool) {
533+
// ok=false when there is no numbered prefix. Depth is clamped to 6.
534+
//
535+
// Callers use depth>1 only to deepen a heading relative to its font-size
536+
// level — never to set the absolute level — so numbered and unnumbered
537+
// top-level headings remain siblings.
538+
func numberedHeadingDepth(s string) (int, bool) {
534539
m := numberedHeadingRe.FindStringSubmatch(strings.TrimSpace(s))
535540
if m == nil {
536541
return 0, false
537542
}
538-
level := strings.Count(m[1], ".") + 1
539-
if level > 6 {
540-
level = 6
543+
depth := strings.Count(m[1], ".") + 1
544+
if depth > 6 {
545+
depth = 6
541546
}
542-
return level, true
547+
return depth, true
543548
}
544549

545550
func looksLikeHeading(s string) bool {

pkg/tree/tree.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ type SectionView struct {
105105
Title string `json:"title"`
106106
Summary string `json:"summary,omitempty"`
107107
Children []SectionID `json:"children,omitempty"`
108-
Tokens int `json:"tokens,omitempty"`
108+
Tokens int `json:"tokens"`
109109
}
110110

111111
// BuildView renders the tree as a flat list of SectionViews in depth-first

0 commit comments

Comments
 (0)