-
Notifications
You must be signed in to change notification settings - Fork 0
/
text.go
1460 lines (1351 loc) · 37.2 KB
/
text.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
########################################################################################
# _______ _______ _______ ___ ___ __ #
# ( ____ \( )( ___ ) / ) / ) / \ #
# | ( \/| () () || ( ) | / /) | / /) | \/) ) #
# | | | || || || (___) | / (_) (_ / (_) (_ | | #
# | | ____ | |(_)| || ___ | (____ _) (____ _) | | #
# | | \_ )| | | || ( ) | Game ) ( ) ( | | #
# | (___) || ) ( || ) ( | Master's | | _ | | _ __) (_ #
# (_______)|/ \||/ \| Assistant (_) (_) (_) (_) \____/ #
# #
########################################################################################
*/
//
// Package text provides text processing facilities used by GMA.
//
package text
import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func sum(ints ...int) int {
s := 0
for _, v := range ints {
s += v
}
return s
}
// ____ _ _ _
// | _ \ ___ _ __ ___ __ _ _ __ | \ | |_ _ _ __ ___ ___ _ __ __ _| |___
// | |_) / _ \| '_ ` _ \ / _` | '_ \ | \| | | | | '_ ` _ \ / _ \ '__/ _` | / __|
// | _ < (_) | | | | | | (_| | | | | | |\ | |_| | | | | | | __/ | | (_| | \__ \
// |_| \_\___/|_| |_| |_|\__,_|_| |_| |_| \_|\__,_|_| |_| |_|\___|_| \__,_|_|___/
//
type romanTableEntry struct {
i int
r string
}
func romanTable() []romanTableEntry {
return []romanTableEntry{
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
}
}
//
// ToRoman converts an integer value to a Roman numeral string.
// This will return "0" for a zero value.
//
func ToRoman(i int) (string, error) {
var roman strings.Builder
if i < 0 {
return "", fmt.Errorf("cannot represent negative values in Roman numerals")
}
if i == 0 {
return "0", nil
}
for _, r := range romanTable() {
for i >= r.i {
roman.WriteString(r.r)
i -= r.i
}
}
return roman.String(), nil
}
//
// FromRoman converts a Roman numeral string to integer.
// Accepts "0" as a zero value.
//
func FromRoman(roman string) (int, error) {
var v int
roman = strings.ToUpper(strings.TrimFunc(roman, unicode.IsSpace))
if roman == "0" {
return 0, nil
}
for roman != "" {
found := false
for _, r := range romanTable() {
if strings.HasPrefix(roman, r.r) {
v += r.i
roman = roman[len(r.r):]
found = true
break
}
}
if !found {
return 0, fmt.Errorf("Not a valid Roman numeral (what is %s?)", roman)
}
}
return v, nil
}
//==================================================================
// _____ _______ _______ __ __ _ ____ _ ___ _ ____
// |_ _| ____\ \/ /_ _| | \/ | / \ | _ \| |/ / | | | _ \
// | | | _| \ / | | | |\/| | / _ \ | |_) | ' /| | | | |_) |
// | | | |___ / \ | | | | | |/ ___ \| _ <| . \| |_| | __/
// |_| |_____/_/\_\ |_| |_| |_/_/ \_\_| \_\_|\_\\___/|_|
//
//
// Options to the Render function are tracked in
// this structure.
//
type renderOptSet struct {
formatter renderingFormatter
bulletSet []rune
compact bool
}
// AsPlainText may be added as an option to the Render function
// to select plain text output format.
func AsPlainText(o *renderOptSet) {
o.formatter = &renderPlainTextFormatter{}
}
// AsHTML may be added as an option to the Render function
// to select HTML output format.
func AsHTML(o *renderOptSet) {
o.formatter = &renderHTMLFormatter{}
}
// AsPostScript may be added as an option to the Render function
// to select PostScript output format.
func AsPostScript(o *renderOptSet) {
o.formatter = &renderPostScriptFormatter{}
}
//
// WithBullets may be added as an option to the Render function to
// specify a custom set of bullet characters
// to use for bulleted lists. The bullets passed
// to this option are used in order, then the list
// repeats over as necessary for additional levels.
//
// Example:
// formattedText, err := Render(srcText, AsPlainText, WithBullets('*', '-'))
// This will alternate between '*' and '-' as bullets at each level.
//
// While the default bullet(s) are chosen appropriately for each output format,
// no other processing is made to the runes passed here; they are used as-is
// in each case, but the following special characters are recognized and
// translated to something sensible in each output format:
// • U+2022 Standard bullet
// ‣ U+2023 Triangle bullet
// ⁃ U+2043 Hyphen bullet
// ○ U+25CB Unfilled circle bullet
// ☞ U+261E Pointing index bullet
// ★ U+2605 Star bullet
//
func WithBullets(bullets ...rune) func(*renderOptSet) {
return func(o *renderOptSet) {
o.bulletSet = bullets
}
}
//
// WithCompactText may be added as an option to the Render function to
// specify that a more compact rendering of text
// blocks in order to conserve paper real estate.
//
// Currently only supported for PostScript output.
//
// Example:
// ps, err := Render(srcText, AsPostScript, WithCompactText)
//
func WithCompactText(o *renderOptSet) {
o.compact = true
}
//
// Each output formatter must supply these methods
// which the Render function will invoke as it parses
// the marked up source text.
//
type renderingFormatter interface {
init(renderOptSet)
newPar()
process(text string)
finalize() string
setBold(on bool)
setItal(on bool)
newLine()
table(*textTable)
reference(displayName, linkName string)
bulletListItem(level int, bullet rune)
enumListItem(level, counter int)
}
//
// ____ _ _ _____ _
// | _ \| | __ _(_)_ _|_ _|____ _| |_
// | |_) | |/ _` | | '_ \| |/ _ \ \/ / __|
// | __/| | (_| | | | | | | __/> <| |_
// |_| |_|\__,_|_|_| |_|_|\___/_/\_\\__|
//
// Plain Text output formatter
//
type renderPlainTextFormatter struct {
buf strings.Builder
indent int
ital bool
bold bool
}
func (f *renderPlainTextFormatter) init(o renderOptSet) {}
func (f *renderPlainTextFormatter) setItal(b bool) {
f.ital = b
}
func (f *renderPlainTextFormatter) setBold(b bool) {
f.bold = b
}
func (f *renderPlainTextFormatter) process(text string) {
f.buf.WriteString(text)
}
func (f *renderPlainTextFormatter) finalize() string {
return f.buf.String()
}
func (f *renderPlainTextFormatter) reference(desc, link string) {
f.buf.WriteString(desc)
}
func (f *renderPlainTextFormatter) newLine() {
fmt.Fprintf(&f.buf, "\n%*s", f.indent*3, "")
}
func (f *renderPlainTextFormatter) newPar() {
f.buf.WriteString("\n\n")
f.indent = 0
}
func (f *renderPlainTextFormatter) bulletListItem(level int, bullet rune) {
if bullet == 0 {
bullet = '\u2022'
}
fmt.Fprintf(&f.buf, "\n%*s%c ", (level-1)*3, "", bullet)
f.indent = level
}
func (f *renderPlainTextFormatter) enumListItem(level, counter int) {
fmt.Fprintf(&f.buf, "\n%*s%s. ", (level-1)*3, "", enumVal(level, counter))
f.indent = level
}
func (f *renderPlainTextFormatter) table(t *textTable) {
//
// First pass: add up the widths of the non-spanning columns
//
colsize := make([]int, 0, 5)
for _, row := range t.rows {
for i, col := range row {
for len(colsize) <= i {
colsize = append(colsize, 0)
}
if col != nil && col.span == 0 {
colsize[i] = max(colsize[i], len(col.text))
}
}
}
//
// Now that we know how much room the normal column text
// requires, adjust as needed for spanning text.
//
for _, row := range t.rows {
for i, col := range row {
if col != nil && col.span > 0 {
alreadyAllocated := sum(colsize[i : i+col.span+1]...)
spaceNeeded := len(col.text) - 3*col.span
if spaceNeeded > alreadyAllocated {
add := spaceNeeded - alreadyAllocated
each := add / (col.span + 1)
for ci := i; ci <= i+col.span; ci++ {
colsize[ci] += each
add -= each
}
if add > 0 {
colsize[i] += add
}
}
}
}
}
//
// Now lay out the table data in these columns
//
f.buf.WriteRune('\n')
for _, c := range colsize {
f.buf.WriteRune('+')
for xx := 0; xx < c+2; xx++ {
f.buf.WriteRune('-')
}
}
f.buf.WriteString("+\n")
for _, row := range t.rows {
headerRow := false
for c := 0; c < len(row); c++ {
if row[c] != nil {
colwidth := sum(colsize[c:c+row[c].span+1]...) + 3*row[c].span
if row[c].header {
fmt.Fprintf(&f.buf, "| %-*s ", colwidth,
fmt.Sprintf("%-*s%s", (colwidth-len(row[c].text))/2, "", strings.ToUpper(row[c].text)))
headerRow = true
} else {
switch row[c].align {
case '>':
fmt.Fprintf(&f.buf, "| %*s ", colwidth, row[c].text)
case '^':
fmt.Fprintf(&f.buf, "| %-*s ", colwidth,
fmt.Sprintf("%-*s%s", (colwidth-len(row[c].text))/2, "", row[c].text))
default:
fmt.Fprintf(&f.buf, "| %-*s ", colwidth, row[c].text)
}
}
}
}
f.buf.WriteString("|\n")
if headerRow {
for _, c := range colsize {
f.buf.WriteRune('+')
for xx := 0; xx < c+2; xx++ {
f.buf.WriteRune('-')
}
}
f.buf.WriteString("+\n")
}
}
for _, c := range colsize {
f.buf.WriteRune('+')
for xx := 0; xx < c+2; xx++ {
f.buf.WriteRune('-')
}
}
f.buf.WriteString("+\n")
}
//
// _ _ _____ __ __ _
// | | | |_ _| \/ | |
// | |_| | | | | |\/| | |
// | _ | | | | | | | |___
// |_| |_| |_| |_| |_|_____|
//
// HTML output formatter
//
type renderHTMLFormatter struct {
buf strings.Builder
indent int
ital bool
bold bool
listStack []string
}
func (f *renderHTMLFormatter) init(o renderOptSet) {
f.buf.WriteString("<P>")
f.listStack = make([]string, 0, 4)
}
func (f *renderHTMLFormatter) cancelStyles() {
if f.ital {
f.setItal(false)
}
if f.bold {
f.setBold(false)
}
}
func (f *renderHTMLFormatter) setItal(b bool) {
if b {
f.buf.WriteString("<I>")
} else {
f.buf.WriteString("</I>")
}
f.ital = b
}
func (f *renderHTMLFormatter) setBold(b bool) {
if b {
f.buf.WriteString("<B>")
} else {
f.buf.WriteString("</B>")
}
f.bold = b
}
func (f *renderHTMLFormatter) process(text string) {
f.buf.WriteString(text)
}
func (f *renderHTMLFormatter) finalize() string {
f.endPar()
return f.buf.String()
}
func (f *renderHTMLFormatter) reference(desc, link string) {
fmt.Fprintf(&f.buf, "<A HREF=\"%s\">%s</A>", strings.ToUpper(link), desc)
}
func (f *renderHTMLFormatter) endPar() {
f.indent = 0
f.cancelStyles()
f.levelSet(0, "", "")
f.buf.WriteString("</P>")
}
func (f *renderHTMLFormatter) levelSet(level int, tag string, extra string) {
for level > len(f.listStack) {
if tag == "" {
tag = "UL"
}
if extra == "" {
fmt.Fprintf(&f.buf, "<%s>", tag)
} else {
fmt.Fprintf(&f.buf, "<%s %s>", tag, extra)
}
f.listStack = append(f.listStack, fmt.Sprintf("</%s>", tag))
}
for level < len(f.listStack) {
f.buf.WriteString(f.listStack[len(f.listStack)-1])
f.listStack = f.listStack[:len(f.listStack)-1]
}
}
func (f *renderHTMLFormatter) newLine() {
f.buf.WriteString("<BR/>")
}
func (f *renderHTMLFormatter) newPar() {
f.endPar()
f.buf.WriteString("<P>")
}
func (f *renderHTMLFormatter) bulletListItem(level int, bullet rune) {
if bullet == 0 {
f.levelSet(level, "UL", "")
} else {
var style string
switch bullet {
case '*', '\u2022':
style = "disc"
case '\u2023':
style = "\\2023"
case '\u2043', '-':
style = "-"
case '\u25cb', 'o':
style = "circle"
case '\u261e':
style = "\\261e"
case '\u2605':
style = "\\2605"
default:
style = fmt.Sprintf("\\%06x", bullet)
}
f.levelSet(level, "UL", "style='list-style-type:\""+style+"\";'")
}
f.buf.WriteString("<LI>")
}
func (f *renderHTMLFormatter) enumListItem(level, counter int) {
f.levelSet(level, "OL", fmt.Sprintf("style=\"list-style-type: %s;\"", enumType(level)))
f.buf.WriteString("<LI>")
}
func (f *renderHTMLFormatter) table(t *textTable) {
f.buf.WriteString("<TABLE BORDER=1>")
for _, row := range t.rows {
f.buf.WriteString("<TR>")
for _, col := range row {
if col != nil {
td := "TD"
al := "LEFT"
cs := ""
if col.header {
td = "TH"
}
if col.align == '^' {
al = "CENTER"
} else if col.align == '>' {
al = "RIGHT"
}
if col.span > 0 {
cs = fmt.Sprintf(" COLSPAN=%d", col.span+1)
}
fmt.Fprintf(&f.buf, "<%s ALIGN=%s%s>%s</%s>",
td, al, cs, col.text, td)
}
}
f.buf.WriteString("</TR>")
}
f.buf.WriteString("</TABLE>")
}
//
// ____ _ ____ _ _
// | _ \ ___ ___| |_/ ___| ___ _ __(_)_ __ | |_
// | |_) / _ \/ __| __\___ \ / __| '__| | '_ \| __|
// | __/ (_) \__ \ |_ ___) | (__| | | | |_) | |_
// |_| \___/|___/\__|____/ \___|_| |_| .__/ \__|
// |_|
//
// PostScript output formatter
//
type renderPostScriptFormatter struct {
buf strings.Builder
indent int
chunks []psChunk
curChunk []string
lastSetFont string
compact bool
ital bool
bold bool
needOutdent bool
}
type psChunk struct {
pre string
contents []string
post string
}
func (f *renderPostScriptFormatter) init(o renderOptSet) {
f.compact = o.compact
}
func psSimpleEscape(s string) string {
return strings.ReplaceAll(
strings.ReplaceAll(
strings.ReplaceAll(s, "\\", `\\`),
"(", `\(`),
")", `\)`)
}
func (f *renderPostScriptFormatter) fontChange() string {
newFont := "rm"
if f.bold && f.ital {
newFont = "bi"
} else if f.bold {
newFont = "bf"
} else if f.ital {
newFont = "it"
}
if newFont != f.lastSetFont {
f.lastSetFont = newFont
return fmt.Sprintf("{PsFF_%s}", newFont)
}
return "{}"
}
func (f *renderPostScriptFormatter) setItal(b bool) {
f.sendBuffer("{}")
f.ital = b
}
func (f *renderPostScriptFormatter) setBold(b bool) {
f.sendBuffer("{}")
f.bold = b
}
func (f *renderPostScriptFormatter) process(text string) {
sp := regexp.MustCompile(`^(\S*\s+)(.*)$`)
for sp.MatchString(text) {
pieces := sp.FindStringSubmatch(text)
f.curChunk = append(f.curChunk, pieces[1])
text = pieces[2]
}
if text != "" {
f.curChunk = append(f.curChunk, text)
}
}
//
// Convert a string value to a properly-formatted PostScript string,
// and substitute special character codes with PostScript equivalents.
//
func psStr(s string) string {
type specialChar struct {
pattern *regexp.Regexp
ps string
}
for _, sc := range []specialChar{
{regexp.MustCompile(`[()\\]`), `\$0`},
{regexp.MustCompile(`\+/-`), `\261`},
{regexp.MustCompile(`-(\d)`), `\055$1`},
{regexp.MustCompile(`---`), `\055\055`},
{regexp.MustCompile(`--`), `\055`},
{regexp.MustCompile(`(\d)x`), `$1\327`},
{regexp.MustCompile(`x(\d)`), `\327$1`},
{regexp.MustCompile(`\[x\]`), `\327`},
{regexp.MustCompile(`\[S\]`), `\247`},
{regexp.MustCompile(`-`), `\255`},
{regexp.MustCompile(`\[1\]`), `\271`},
{regexp.MustCompile(`\[2\]`), `\262`},
{regexp.MustCompile(`\[3\]`), `\263`},
{regexp.MustCompile(`\b1/2\b`), `\275`},
{regexp.MustCompile(`\b1/4\b`), `\274`},
{regexp.MustCompile(`\b3/4\b`), `\276`},
{regexp.MustCompile(`_1/2\b`), `\275`},
{regexp.MustCompile(`_1/4\b`), `\274`},
{regexp.MustCompile(`_3/4\b`), `\276`},
{regexp.MustCompile(`\^o`), `\260`},
{regexp.MustCompile(`\[c\]`), `\251`},
{regexp.MustCompile(`\[R\]`), `\256`},
{regexp.MustCompile(`AE`), `\306`},
{regexp.MustCompile(`ae`), `\346`},
{regexp.MustCompile(`\[<<\]`), `\253`},
{regexp.MustCompile(`\[>>\]`), `\273`},
{regexp.MustCompile(`\^\.`), `\267`},
{regexp.MustCompile(`\[/\]`), `\367`},
} {
s = sc.pattern.ReplaceAllString(s, sc.ps)
}
return s
}
func (f *renderPostScriptFormatter) finalize() string {
f.sendBuffer("{}")
f.setBold(false)
f.setItal(false)
f.buf.WriteString(" [ ")
for _, chunk := range f.chunks {
f.buf.WriteString(" [ ")
f.buf.WriteString(chunk.post)
f.buf.WriteString(" [ ")
for _, s := range chunk.contents {
f.buf.WriteString("(")
f.buf.WriteString(psStr(s))
f.buf.WriteString(")")
}
f.buf.WriteString(" ] ")
f.buf.WriteString(chunk.pre)
f.buf.WriteString(" ] ")
}
f.buf.WriteString(" ] ")
f.chunks = nil
return f.buf.String()
}
func (f *renderPostScriptFormatter) reference(desc, link string) {
f.toggleItal()
f.process(desc)
f.toggleItal()
}
func (f *renderPostScriptFormatter) newLine() {
if f.compact {
f.process(" ")
} else {
f.sendBuffer("{PsFF_nl}")
}
}
func (f *renderPostScriptFormatter) newPar() {
if f.compact {
f.process(" ")
} else {
if f.needOutdent {
f.sendBuffer("{PsFF_par 0 PsFF_ind}")
f.needOutdent = false
} else {
f.sendBuffer("{PsFF_par}")
}
}
}
func (f *renderPostScriptFormatter) bulletListItem(level int, bullet rune) {
var psb string
switch bullet {
case 0, '*', '\u2022':
psb = "^."
case '\u2023':
psb = ">"
case '\u2043', '-':
psb = "-"
case '\u25cb', 'o':
psb = "o"
case '\u261e':
psb = "[>>]"
case '\u2605':
psb = "*"
default:
psb = string(bullet)
}
if f.compact {
f.process(fmt.Sprintf(" (%c) ", bullet))
} else {
f.newLine()
f.chunks = append(f.chunks, psChunk{
pre: fmt.Sprintf("{ %d PsFF_ind }", level-1),
contents: []string{psb},
post: fmt.Sprintf("{ %d PsFF_ind }", level),
})
f.needOutdent = true
}
}
func (f *renderPostScriptFormatter) sendBuffer(end string) {
if f.curChunk != nil || end != "{}" {
f.chunks = append(f.chunks, psChunk{
pre: f.fontChange(),
contents: f.curChunk,
post: end,
})
f.curChunk = nil
}
}
func (f *renderPostScriptFormatter) toggleItal() {
f.setItal(!f.ital)
}
func (f *renderPostScriptFormatter) enumListItem(level, counter int) {
if f.compact {
f.process(fmt.Sprintf(" (%s) ", enumVal(level, counter)))
} else {
f.newLine()
f.chunks = append(f.chunks, psChunk{
pre: fmt.Sprintf("{ %d PsFF_ind }", level-1),
contents: []string{fmt.Sprintf("%s.", enumVal(level, counter))},
post: fmt.Sprintf("{ %d PsFF_ind }", level),
})
f.needOutdent = true
}
}
//
// For PostScript tables, we handle this by
// emitting a routine up front which estimates
// the horizontal space required by each column.
// this way we let the device, which knows its
// output parameters and font metrics, so all
// the math the other formatter classes do here
// will instead by shipped over to the output
// device and written in PostScript.
//
// This defines variables called /PsFF_Cw<n>
// which hold the size in points for column <n>
// of the table (0-origin).
//
// The code for this is essentially:
// [ <col <n> row 0> <col <n> row 1> ... ] {
// stringwidth pop dup PsFF_Cw<n> gt {
// /PsFF_Cw<n> exch def
// } { pop } ifelse
// } forall
//
// As the table cells are typeset, they are put
// into boxes of width PsFF_Cw<n> using the normal
// boxed text support we use elsewhere, via the
// PsFF_tXX procedures.
//
// For spanned columns, we will skip over the
// spans when doing the initial calculations,
// and then emit code for each span which adjusts
// the column widths:
//
// % span columns 1-3
// <text> stringwidth pop PsFF_Cw1 PsFF_Cw2 add
// PsFF_Cw3 add 2 PsFF_TcolSpn mul add gt {
// /PsFF_Cw1 PsFF_Cw1 <x> add def
// /PsFF_Cw2 PsFF_Cw2 <x> add def
// /PsFF_Cw3 PsFF_Cw3 <x> add def
// } if
//
// (note that PsFF_TcolSpn is a constant equal
// to the amount of space added in a table between
// columns--this needs to be added back into the
// size of spanned columns)
//
func (f *renderPostScriptFormatter) table(t *textTable) {
// Emit routine to calculate column widths, then emit
// code to render the table
if f.compact {
f.process(" [table] ")
return
}
f.sendBuffer("{PsFF_nl}")
f.bold = false
f.ital = false
var ps strings.Builder
ps.WriteString(`{PsFF_rm
%
% Data Table: calculate column widths
%
`)
for c := 0; c < t.numCols(); c++ {
fmt.Fprintf(&ps, "/PsFF_Cw%d 0 def\n[", c)
for _, row := range t.rows {
if row[c] != nil && row[c].span == 0 {
fmt.Fprintf(&ps, "(%s) ", psSimpleEscape(row[c].text))
}
}
fmt.Fprintf(&ps, `] {
stringwidth pop dup PsFF_Cw%d gt {
/PsFF_Cw%d exch def
} {
pop
} ifelse
} forall
`, c, c)
}
//
// Now adjust column widths for the spans
//
for r, row := range t.rows {
for i, col := range row {
if col != nil && col.span > 0 {
fmt.Fprintf(&ps, "\n%% span row %d, columns %d-%d:\n/PsFF__t__have PsFF_TcolSpn %d mul", r, i, i+col.span, col.span)
for j := i; j <= i+col.span; j++ {
fmt.Fprintf(&ps, " PsFF_Cw%d add", j)
}
fmt.Fprintf(&ps, " def\n/PsFF__t__need (%s) stringwidth pop def",
psSimpleEscape(col.text))
fmt.Fprintf(&ps, `
PsFF__t__need PsFF__t__have gt {
/PsFF__t__add PsFF__t__need PsFF__t__have sub def
/PsFF__t__each PsFF__t__add %d 1 add idiv def
`, col.span)
for n := i; n <= i+col.span; n++ {
fmt.Fprintf(&ps, " /PsFF_Cw%d PsFF_Cw%d PsFF__t__each add def\n", n, n)
ps.WriteString(" /PsFF__t__add PsFF__t__add PsFF__t__each sub def\n")
}
fmt.Fprintf(&ps, ` PsFF__t__add 0 gt {
/PsFF_Cw%d PsFF_Cw%d PsFF__t__add add def
} if
} if
`, i, i)
}
}
}
//
// now typeset the table itself.
//
for _, row := range t.rows {
for c, col := range row {
if col != nil {
fmt.Fprintf(&ps, " (%s) PsFF_Cw%d ", psSimpleEscape(col.text), c)
for span := c + 1; span <= c+col.span; span++ {
fmt.Fprintf(&ps, "PsFF_Cw%d add ", span)
}
if col.span > 0 {
fmt.Fprintf(&ps, "PsFF_TcolSpn %d mul add ", col.span)
}
var style, align rune
if col.header {
style = 'h'
} else {
style = 'd'
}
switch col.align {
case '<':
align = 'L'
case '^':
align = 'C'
case '>':
align = 'R'
default:
align = 'L'
}
fmt.Fprintf(&ps, "PsFF_t%c%c\n", style, align)
}
}
ps.WriteString("PsFF_nl\n")
}
ps.WriteString("}")
f.sendBuffer(ps.String())
}
// ___ _ ____
// |_ _|_ __ _ __ _ _| |_ | _ \ __ _ _ __ ___ ___ _ __
// | || '_ \| '_ \| | | | __| | |_) / _` | '__/ __|/ _ \ '__|
// | || | | | |_) | |_| | |_ | __/ (_| | | \__ \ __/ |
// |___|_| |_| .__/ \__,_|\__| |_| \__,_|_| |___/\___|_|
// |_|
//
// Incoming list items are marked with one of these, to be expanded
// later by the output formatter.
//
type listItem struct {
bullet rune // '*' for bullet lists or '#' for enumerated
level int // nesting level
}
//
// Representation of a table as a slice of rows, each of which
// is a slice of tableCells.
//
type textTable struct {
rows [][]*tableCell
}
//
// Count number of columns
//
func (t *textTable) numCols() int {
nc := 0
for _, r := range t.rows {
nc = max(nc, len(r))
}
return nc
}
//
// Add a new row to a textTable.
// Handles spanned columns.
//
func (t *textTable) addRow(cols []string) error {
if t.rows == nil {
t.rows = make([][]*tableCell, 0, 32)
}
row := make([]*tableCell, 0, 8)
var lastUsed *tableCell = nil
for _, col := range cols {
if strings.HasPrefix(col, "-") {
if lastUsed == nil {
return fmt.Errorf("table cell cannot span into the first column")
}
lastUsed.span++
row = append(row, nil)
} else {
lastUsed = newTableCell(col)
row = append(row, lastUsed)
}
}
t.rows = append(t.rows, row)
return nil
}
//
// Incoming table rows are represented in the input stream
// with one of these, which notes that this should be a table
// row and holds the markup text source from that line of input.
//
type tableRow struct {
src string
}
//
// This represents a specific cell in a textTable.
//
type tableCell struct {
text string // text that belongs in this cell
span int // number of columns to the right to take up
align rune // '<' (left), '^' (center), or '>' (right)
header bool // true if this is a header cell
}
//
// Create a new tableCell, figuring out alignment
// from context (based on leading and/or trailing space)
//
// Also identifies cells which start with '=' as headers.
//