Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,27 @@ func TablesWithOptions(d *reader.Document, page int, o TableOptions) ([]Table, e
if err != nil {
return nil, err
}
return tablesIn(runs, page, o.withDefaults()), nil
return TablesInRuns(runs, page, o), nil
}

// TablesInRuns is [TablesWithOptions] over runs a caller chose, and page is
// only the number it will put in each [Table.Page].
//
// ⛔ It exists so that a caller who knows something this does not can take
// lines OUT before asking. A numbered heading above a table is the case that
// made it necessary: "2. Vue d'ensemble" leaves a gap after its number, the
// table below it leaves that same gap empty on every row, so the heading and
// the table became ONE two-column table — and the real four-column table
// under it was lost, because the heading's own text reached across every one
// of its boundaries.
//
// Nothing in the alignment says that line is a heading. Its SIZE says so, and
// what counts as a heading size is a property of the whole document, which is
// [go-pdfkit/document]'s business and not this package's.
//
// [go-pdfkit/document]: https://pkg.go.dev/github.com/go-pdfkit/document
func TablesInRuns(runs []Run, page int, o TableOptions) []Table {
return tablesIn(runs, page, o.withDefaults())
}

// withDefaults fills in the fields left at zero.
Expand Down
55 changes: 55 additions & 0 deletions tables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,58 @@ func TestLinesAndJoinAgreeWithAssemble(t *testing.T) {
t.Errorf("the first line reads %q", Join(lines[0]))
}
}

// ⛔ THE CASE THAT MADE TablesInRuns NECESSARY. A numbered heading leaves a gap
// after its number; the table below it leaves that same gap empty on every
// row; so the two become ONE two-column table, and the real four-column table
// is lost because the heading's own text reaches across every boundary of it.
//
// Nothing in the alignment says that line is a heading — its SIZE does, and
// what counts as a heading size is a property of the whole document. So the
// caller who knows takes the line out and asks again.
func TestAHeadingAboveATableSwallowsIt(t *testing.T) {
d := pageWith(t, strings.Join([]string{
// "2." at 20, its text at 40 and running to 170.
"BT /F1 12 Tf 20 180 Td (2.) Tj 20 0 Td (Vue d'ensemble technique) Tj ET",
"BT /F1 10 Tf 40 160 Td (Name) Tj 60 0 Td (Age) Tj 60 0 Td (City) Tj ET",
"BT /F1 10 Tf 40 145 Td (Ada) Tj 60 0 Td (36) Tj 60 0 Td (Caen) Tj ET",
"BT /F1 10 Tf 40 130 Td (Bob) Tj 60 0 Td (41) Tj 60 0 Td (Lyon) Tj ET",
}, "\n"), nil)

// Asked about the page, the heading is in and the table is two columns of
// nothing useful.
whole, err := Tables(d, 1)
if err != nil {
t.Fatal(err)
}
if len(whole) != 1 {
t.Fatalf("%d tables, want 1: %+v", len(whole), whole)
}
if n := len(whole[0].Rows[0]); n != 2 {
t.Errorf("%d columns with the heading in, want 2 — the defect: %q", n, whole[0].Rows)
}

// Asked about the runs below it, the real table comes back.
runs, err := Runs(d, 1)
if err != nil {
t.Fatal(err)
}
var below []Run
for _, r := range runs {
if r.Size < 12 {
below = append(below, r)
}
}
got := TablesInRuns(below, 1, TableOptions{})
if len(got) != 1 {
t.Fatalf("%d tables without the heading, want 1: %+v", len(got), got)
}
want := [][]string{{"Name", "Age", "City"}, {"Ada", "36", "Caen"}, {"Bob", "41", "Lyon"}}
if !reflect.DeepEqual(got[0].Rows, want) {
t.Errorf("without the heading the table reads %q, want %q", got[0].Rows, want)
}
// And the page number is the caller's to say, since runs do not carry one.
if got[0].Page != 1 {
t.Errorf("the table says it is on page %d", got[0].Page)
}
}
Loading