feat: warn when a PDF export replaces characters it cannot draw
The hand-written PDF writer uses the standard Courier fonts (WinAnsiEncoding), so any character outside CP1252 - a Polish, Greek, Cyrillic or CJK script - was silently written as '?'. Render now collects the characters it had to replace and returns them, and the export dialog lists them in a warning once the file is written. Embedding a TrueType font (e.g. Courier Prime) with a CID/ToUnicode map would fix the root cause; the warning keeps the zero-dependency trade-off honest in the meantime.
This commit is contained in:
1 parent
5e7ae97b57
commit
e80ad634ef
7 files changed
+125
-35
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"compress/zlib"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -39,9 +40,13 @@ func TestRender(t *testing.T) {
|
||||
pages := []layout.Page{layout.TitlePage(doc.TitlePage, doc.Settings.Paper)}
|
||||
pages = append(pages, layout.Paginate(doc, layout.Options{SceneNumbers: true}).Pages...)
|
||||
var buf bytes.Buffer
|
||||
if err := Render(&buf, pages, Options{Title: doc.TitlePage.Title, Author: doc.TitlePage.Author}); err != nil {
|
||||
replaced, err := Render(&buf, pages, Options{Title: doc.TitlePage.Title, Author: doc.TitlePage.Author})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(replaced) != 0 {
|
||||
t.Errorf("the French sample is fully representable, replaced = %q", string(replaced))
|
||||
}
|
||||
out := buf.String()
|
||||
if !strings.HasPrefix(out, "%PDF-1.4") || !strings.HasSuffix(out, "%%EOF\n") || !strings.Contains(out, "/Count 2") {
|
||||
t.Fatalf("malformed PDF:\n%.300s", out)
|
||||
@@ -61,7 +66,7 @@ func TestRevisionsAndA4(t *testing.T) {
|
||||
doc.Elements[1].Runs[0].Rev = 1
|
||||
var buf bytes.Buffer
|
||||
pages := layout.Paginate(doc, layout.Options{}).Pages
|
||||
if err := Render(&buf, pages, Options{Paper: doc.Settings.Paper, Revisions: doc.Settings.Revisions}); err != nil {
|
||||
if _, err := Render(&buf, pages, Options{Paper: doc.Settings.Paper, Revisions: doc.Settings.Revisions}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stream := uncompressed(t, buf.Bytes())
|
||||
@@ -74,7 +79,7 @@ func TestRevisionsAndA4(t *testing.T) {
|
||||
|
||||
// Without revisions in the options, nothing is marked.
|
||||
buf.Reset()
|
||||
if err := Render(&buf, pages, Options{Paper: doc.Settings.Paper}); err != nil {
|
||||
if _, err := Render(&buf, pages, Options{Paper: doc.Settings.Paper}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stream := uncompressed(t, buf.Bytes()); strings.Contains(stream, "Revision") || strings.Contains(stream, "(*)") {
|
||||
@@ -82,6 +87,34 @@ func TestRevisionsAndA4(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestReplacedChars: characters outside WinAnsi are written as '?' and
|
||||
// reported, so an export can warn instead of corrupting the text silently.
|
||||
func TestReplacedChars(t *testing.T) {
|
||||
src := "INT. ΚΑΦΕΝΕΙΟ - ΜΕΡΑ\n\nΚαλημέρα… Plain ASCII.\n"
|
||||
doc := fountain.Parse(src)
|
||||
pages := layout.Paginate(doc, layout.Options{}).Pages
|
||||
var buf bytes.Buffer
|
||||
replaced, err := Render(&buf, pages, Options{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := string(replaced)
|
||||
for _, r := range src { // every Greek letter is reported
|
||||
if r > 0x7f && r != '…' && !strings.ContainsRune(got, r) {
|
||||
t.Errorf("%q not reported in %q", r, got)
|
||||
}
|
||||
}
|
||||
if strings.ContainsRune(got, '…') { // WinAnsi has "…"
|
||||
t.Errorf("representable … reported in %q", got)
|
||||
}
|
||||
if !slices.IsSorted(replaced) {
|
||||
t.Errorf("replaced not sorted: %q", got)
|
||||
}
|
||||
if stream := uncompressed(t, buf.Bytes()); !strings.Contains(stream, "????????") {
|
||||
t.Errorf("Greek not written as '?':\n%s", stream)
|
||||
}
|
||||
}
|
||||
|
||||
// uncompressed returns the content streams of a PDF written by Render.
|
||||
func uncompressed(t *testing.T, pdf []byte) string {
|
||||
var out strings.Builder
|
||||
|
||||
Reference in new issue
Block a user