diff --git a/backend/pdf/font.go b/backend/pdf/font.go index 94b1c1d..9b353ce 100644 --- a/backend/pdf/font.go +++ b/backend/pdf/font.go @@ -109,7 +109,7 @@ func (e *embedded) write(d *document, compress bool) error { // CFF outlines are embedded whole and described as OpenType, because // this package does not cut charstrings up — see internal/sfnt. dict = "/Subtype /CIDFontType0" - fileKey = "/FontFile3 " + fileKey = "/FontFile3" } stream := fmt.Sprintf("/Length1 %d", len(prog)) @@ -121,7 +121,7 @@ func (e *embedded) write(d *document, compress bool) error { xMin, yMin, xMax, yMax := f.BBox() desc := d.add([]byte(fmt.Sprintf( "<< /Type /FontDescriptor /FontName /%s /Flags 4 /FontBBox [%d %d %d %d] "+ - "/ItalicAngle %s /Ascent %d /Descent %d /CapHeight %d /StemV %d %s%d 0 R >>", + "/ItalicAngle %s /Ascent %d /Descent %d /CapHeight %d /StemV %d %s %d 0 R >>", name, k(xMin), k(yMin), k(xMax), k(yMax), fmtNum(f.ItalicAngle()), k(f.Ascent()), k(f.Descent()), k(f.CapHeight()), stemV(f.WeightClass()), fileKey, file))) diff --git a/backend/pdf/font_test.go b/backend/pdf/font_test.go index df13f98..b4c8815 100644 --- a/backend/pdf/font_test.go +++ b/backend/pdf/font_test.go @@ -1,6 +1,7 @@ package pdf_test import ( + "regexp" "strings" "testing" @@ -58,6 +59,35 @@ func TestAnEmbeddedFontIsACIDFont(t *testing.T) { } } +// The descriptor points at the font program by reference. A key run into the +// object number ("/FontFile22 0 R") is a different name followed by a stray +// "0 R": readers then treat the font as not embedded, and a PDF importer that +// re-serialises the dictionary scrambles it. +func TestTheDescriptorReferencesTheFontProgram(t *testing.T) { + for _, tc := range []struct { + name, key string + font []byte + }{ + {"TrueType", "/FontFile2", testTTF()}, + {"CFF", "/FontFile3", sfnttest.CFFFont()}, + } { + t.Run(tc.name, func(t *testing.T) { + doc := drawLabel(t, "AB", pdf.WithFont(tc.font, nil, nil), pdf.Uncompressed()) + m := regexp.MustCompile(regexp.QuoteMeta(tc.key) + ` (\d+) 0 R`).FindStringSubmatch(doc) + if m == nil { + t.Fatalf("the descriptor carries no %s reference", tc.key) + } + obj := regexp.MustCompile(`(?s)\n` + m[1] + ` 0 obj\n<<(.*?)>>\nstream`).FindStringSubmatch(doc) + if obj == nil { + t.Fatalf("%s points at object %s, which is not a stream", tc.key, m[1]) + } + if tc.key == "/FontFile2" && !strings.Contains(obj[1], "/Length1") { + t.Errorf("object %s is not the font program: %s", m[1], obj[1]) + } + }) + } +} + // The glyphs are numbered as they are first met, which is what makes writing // a PDF one pass: the content stream names an id before the subset that // defines it exists.