-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHTMLSTREAM
More file actions
2159 lines (1809 loc) · 111 KB
/
HTMLSTREAM
File metadata and controls
2159 lines (1809 loc) · 111 KB
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
(DEFINE-FILE-INFO :PACKAGE "INTERLISP" :READTABLE "INTERLISP" :BASE 10)
(FILECREATED "25-Mar-2026 15:59:44" {HTMLSTREAM}HTMLSTREAM.;97 108959
:CHANGES-TO (FUNCTIONS HTML.CREATE-ALL-MEDLEYFONT-FAMILIES HTML.FONT HTML.COLOR HTML.BACKCOLOR
HTML.LINEFEED \HTML.FONTCREATE \HTML.TOADDR HTMLFONT.GETCHARSET
HTML.OUTPUT-TEXT HTML.WRITE-PREFACE HTML.MOVETO HTML.XPOSITION)
(VARIABLES *HTML-STYLESHEET-FONT-FAMILY-DEFS* HTMLCHARSETFNS *WEB-FONT-FAMILIES*
*HTML-STANDARD-PAGE-WEB-FONT-FAMILIES* *WEB-FONT-FAMILY-NAMES*
*BROWSER-FONTS-MAP*)
(STRUCTURES HTML.FONTINFO)
:PREVIOUS-DATE "25-Feb-2026 13:53:32" {HTMLSTREAM}HTMLSTREAM.;92)
(PRETTYCOMPRINT HTMLSTREAMCOMS)
(RPAQQ HTMLSTREAMCOMS
((FILES BASE64 U-PNG)
(* ;; "handy macros and functions")
(FUNCTIONS DO-NOT \HTML-DBG \HTML.TOADDR)
(VARIABLES *CENTIPOINTS-PER-POINT* TAB-WIDTH-IN-CHARACTERS)
(RECORDS WEB-FONT-DESCR WEB-FONT-FACE WEB-FONT-METRICS WEB-CHARSET-METRICS)
(FUNCTIONS CLOSE-KNOTS HTML.INCHES-TO-POINTS HTML.INCHES-TO-CENTIPOINTS CP-TO-P ICP-TO-P
P-TO-CP PIXEL-TO-PT PT-TO-PIXEL \SF \HTML.SVG-Y)
(FUNCTIONS INSURE-COLOR-OR-SHADE)
(FUNCTIONS MAKE-FONT-METRICS-FILE-NAME MAKE-CHARSET-METRICS-FILE-NAME)
(VARIABLES *WEB-FONT-MAPPINGS* HTMLFONTCOERCIONS HTMLFONTDIRECTORIES HTMLFONTEXTENSIONS
HTMLCHARSETFNS *GOOGLE-FONTS-BASE-URL* *HTML-STANDARD-PAGE-WEB-FONT-FAMILIES*
*HTML-STYLESHEET-FONT-FAMILY-DEFS* *DEFAULT-WEB-FONT-METRICS-FILE-NAME*
*WEB-FONT-EXPANSIONS* *WEB-FONT-FAMILIES* *WEB-FONT-FAMILY-NAMES* *WEB-FONT-SIZES*
*WEB-FONT-SLOPES* *WEB-FONT-WEIGHTS* *BROWSER-FONTS-MAP* *WEB-CHARSET-METRICS-EXT*
*WEB-CHARSET-METRICS-DOT-EXT* *WEB-FONT-METRICS-EXT* *WEB-FONT-METRICS-DOT-EXT*
*HTML-ADD-FONT-DEFS*)
(VARIABLES *BROWSER-FONT-FILE-VERSION* *HTML.DEFAULT-PAGE-REGION* HTML.FONTCREATE.DEVICENAME
HTML.IMAGETYPE HTML.STATE.BETWEEN-PAGES HTML.STATE.CLOSED HTML.STATE.NON-TEXT
HTML.STATE.NON-TEXT-OUTPUT HTML.STATE.TEXT-OUTPUT HTML.STATES \HTMLSTREAM.FDEV
*XCCS-KNOWN-CHARSETS* *XCCS-UNDEFINED-CHARCODE* HTML-MEDLEY-CREDITS)
(FUNCTIONS HTML-FONT-METRICS-VERSION-OK? CHECK-BROWSER-METRICS-FORMAT CSS-CLASS-FROM
CSS-CLASS-FROM-FAMILY \HTML.READ-FONT-METRICS \HTML.READ-CHARSET-METRICS
\HTML.SVG-STROKE-DASHARRAY \HTML.SVG-BRUSH-WIDTH \HTML.SVG-BRUSH-CLASS \HTML.SVG-COLOR
)
(STRUCTURES HTML.IMAGEDATA HTML.FONTINFO)
(FUNCTIONS with-htmldata)
(FUNCTIONS HTML.OUTPUT-GRAPHICS HTML.OUTPUT-TEXT \HTML.MAYBE-START-PAGE \HTML.END-PAGE
\HTML.TAB-DELTA-X \HTML.CHARWIDTH HTML.WRITE-PREFACE CONVERT-FAMILY-TO-QUERY-PARAM
\HTML.MAKE-STYLESHEET-URL)
(FUNCTIONS OPENHTMLSTREAM HTML.BACKCOLOR HTML.BITBLT HTML.BLTSHADE HTML.BOTTOMMARGIN
HTML.CHARWIDTH HTML.CLIPPINGREGION HTML.CLOSEFN HTML.COLOR HTML.DRAWCIRCLE
HTML.DRAWCURVE HTML.DRAWELLIPSE HTML.DRAWLINE HTML.DRAWPOLYGON HTML.DRAWARC
HTML.FILLCIRCLE HTML.FILLPOLYGON HTML.FONT HTML.LEFTMARGIN HTML.LINEFEED HTML.MOVETO
HTML.NEWPAGE HTML.OPERATION HTML.OUTCHARFN HTML.RESET HTML.RIGHTMARGIN HTML.SCALE
HTML.SCALEDBITBLT HTML.SPACEFACTOR HTML.TERPRI HTML.TOPMARGIN HTML.XPOSITION
HTML.YPOSITION MAKE-HTML-IMAGETYPE \HTML.INIT-IMAGEDATA \HTMLSTREAM.INIT)
(FUNCTIONS WRITE-BASE64-PNG CONVERT-TO-PNG-BASE64)
(FUNCTIONS URL-ENCODE HTML-ENCODE)
(VARIABLES *HTML-DEBUG-FONTCREATE*)
(VARIABLES *HTML-FONT-METRIC-BASIS*)
(FUNCTIONS \HTML.FONTCREATE \HTML.FONTEXISTS? \HTML.FONTSAVAILABLE HTMLFONT.GETCHARSET
HTMLFONT.FILEP \HTML.CREATECHARSET \HTML.CHANGECHARSET MAKE-CHARSET-BIT-ARRAY
KNOWN-CHARSET? \HTML.WARN-CHARSET \HTML.ADD-FONT-DEFS \FIRST-COERCION
\HTML.COERCE-OR-IDENTITY \HTML.COERCE-FONT MAYBE-MARK-FONT-COMPLETE)
(* ;; "New Font World!")
(FUNCTIONS \HTML.FONTCREATE-NEW)
(P (\HTMLSTREAM.INIT))
(* ;; "MEDLEYFONT-related utility")
(VARIABLES *HTML.ALL-GENERATED-FONT-SIZES* *HTML.ALL-GENERATED-FONT-FACES*)
(FUNCTIONS HTML.CREATE-MEDLEYFONT-FAMILY HTML.CREATE-ALL-MEDLEYFONT-FAMILIES)
(* ;; "PRINTFILETYPES-related")
(VARIABLES *HTML-FILE-EXTENSIONS* *HTML-FINGERPRINTS*)
(FUNCTIONS HTMLFILEP MAKE-HTML-FROM-TEXT MAKE-HTML-FROM-TEDIT)
(ADDVARS [PRINTFILETYPES (HTML (TEST HTMLFILEP)
(EXTENSION (HTML))
(CONVERSION (TEXT MAKE-HTML-FROM-TEXT TEDIT
MAKE-HTML-FROM-TEDIT]
(FB.SEE.METHODS (HTMLFILEP ShellOpen)))
(* ;; "debugging support")
(FUNCTIONS \CLEAN-FONT-CACHE)))
(FILESLOAD BASE64 U-PNG)
(* ;; "handy macros and functions")
(DEFMACRO DO-NOT (&BODY BODY)
"Ignore the body, return NIL"
NIL)
(DEFMACRO \HTML-DBG (&REST REST)
"Produce HTML/font-related debug info if enabled"
`(CL:WHEN *HTML-DEBUG-FONTCREATE*
(CL:FORMAT T ,@REST)))
(CL:DEFUN \HTML.TOADDR (X)
"Convert the object to a string made from its address"
(CL:FORMAT NIL "~a/~o,~o" (TYPENAME X)
(\HILOC X)
(\LOLOC X)))
(CL:DEFCONSTANT *CENTIPOINTS-PER-POINT* 100
"There are 100 centipoints per point, by definition and by golly!")
(CL:DEFCONSTANT TAB-WIDTH-IN-CHARACTERS 8
"A TAB character will move this many characters")
(DECLARE%: EVAL@COMPILE
(PROPRECORD WEB-FONT-DESCR (:NAME :FACE :SIZE :HEIGHT :SLUG-WIDTH :MAX-ASCENT :MAX-DESCENT :CHARSETS))
(RECORD WEB-FONT-FACE (:WEIGHT :SLOPE :EXPANSION))
(PROPRECORD WEB-FONT-METRICS (:FAMILY :SIZE :WEIGHT :STYLE :HEIGHT :MAX-ASCENT :MAX-DESCENT :CHARSETS
:LISP-NAME :LISP-FACE :LISP-SIZE))
(PROPRECORD WEB-CHARSET-METRICS (:CHARSET :MAX-ASCENT :MAX-DESCENT :WIDTHS))
)
(CL:DEFUN CLOSE-KNOTS (KNOTS)
"Create a closed polygon by adding the first point to the end of the list"
(LET ((FIRST-KNOT (CAR KNOTS)))
(APPEND KNOTS (LIST FIRST-KNOT))))
(CL:DEFUN HTML.INCHES-TO-POINTS (INCHES)
"Convert inches to points"
(* ;; "Assume one inch contains 72 points")
(FIXR (TIMES 72 INCHES)))
(CL:DEFUN HTML.INCHES-TO-CENTIPOINTS (INCHES)
(FIXR (TIMES 72 *CENTIPOINTS-PER-POINT* INCHES)))
(DEFMACRO CP-TO-P (CENTIPOINTS)
`(FQUOTIENT ,CENTIPOINTS *CENTIPOINTS-PER-POINT*))
(DEFMACRO ICP-TO-P (CENTIPOINTS)
(* ;; "convert centipoints to an integer number of points")
`(FIXR (FQUOTIENT ,CENTIPOINTS *CENTIPOINTS-PER-POINT*)))
(DEFMACRO P-TO-CP (POINTS)
`(FTIMES ,POINTS *CENTIPOINTS-PER-POINT*))
(CL:DEFUN PIXEL-TO-PT (PIXELS)
"Convert some number of pixels to that number of points"
(* ;; "96 pixels = 72 points")
(FTIMES 0.75 PIXELS))
(CL:DEFUN PT-TO-PIXEL (POINTS)
"Convert some number of points to that number of pixels"
(* ;; "96 pixels = 72 points")
(FTIMES 1.333 POINTS))
(DEFMACRO \SF (FONT-NAME)
"Marker for a font name substituting for another"
FONT-NAME)
(CL:DEFUN \HTML.SVG-Y (DATA MEDLEY-Y)
"Return the SVG Y-coordinate for a Medley Y coordinate"
(- (\HTML-PAGE-HEIGHT DATA)
MEDLEY-Y))
(CL:DEFUN INSURE-COLOR-OR-SHADE (COLOR)
"Insure that the COLOR argument is a valid color or gray shade"
(* ;; "Use INSURE.RGB.COLOR for colors. Scalars 0 <= x <= 1 are gray shades.")
(* ;; "This is based on the logic in \PSC.COLOR.TO.RGB.")
(COND
((OR (EQ COLOR 0)
(EQ COLOR 1)
(AND (FLOATP COLOR)
(<= 0.0 COLOR 1.0))) (* ; "convert to an RGB triple")
(LET [(GRAY (FIXR (TIMES 255 COLOR]
(LIST GRAY GRAY GRAY)))
(T (INSURE.RGB.COLOR COLOR))))
(CL:DEFUN MAKE-FONT-METRICS-FILE-NAME (FAMILY SIZE WEIGHT SLOPE EXPANSION)
"Create the name of the file that holds the metrics for the given font"
(* ;; "The file name looks like <family>-<size>-<weightChar><slopeChar><expansionChar>.wfm")
(LET [(FACE-ABBREV (L-CASE (FONTFACETOATOM (create FONTFACE
WEIGHT _ WEIGHT
SLOPE _ SLOPE
EXPANSION _ EXPANSION]
(CL:FORMAT NIL "~A-~d-~A.~A" FAMILY SIZE FACE-ABBREV *WEB-FONT-METRICS-EXT*)))
(CL:DEFUN MAKE-CHARSET-METRICS-FILE-NAME (FAMILY SIZE WEIGHT SLOPE EXPANSION CHARSET-NUM)
"Create the name of the file that holds the metrics for the given charset"
(* ;; "The file name looks like c<octalCharset>'><family><size>-<weightChar><slopeChar><expansionChar>-c<octalCharset>.wcm")
(* ;; "e.g.,")
(* ;; "c0>NOTO-SANS10-MRR-c0.wcm")
(LET [(FACE-ABBREV (L-CASE (FONTFACETOATOM (create FONTFACE
WEIGHT _ WEIGHT
SLOPE _ SLOPE
EXPANSION _ EXPANSION]
(CL:FORMAT NIL "c~o>~A~d-~A-c~o.~A" CHARSET-NUM FAMILY SIZE FACE-ABBREV CHARSET-NUM
*WEB-CHARSET-METRICS-EXT*)))
(DEFGLOBALVAR *WEB-FONT-MAPPINGS*
'((HELVETICA . NOTO-SANS)
(HELVETICAD . NOTO-SANS-DISPLAY)
(TIMESROMAN . NOTO-SERIF)
(TIMESROMAND . NOTO-SERIF-DISPLAY)
(COURIER . NOTO-SANS-MONO)
(GACHA . NOTO-SANS-MONO)
(CLASSIC . NOTO-SERIF)
(MODERN . NOTO-SANS)
(CREAM . NOTO-SANS)
(TERMINAL . NOTO-SANS-MONO)
(LOGO . NOTO-SANS-MONO)
(OPTIMA . NOTO-SERIF)
(TITAN . NOTO-SANS-MONO))
"Map common font families to Web font families")
(CL:DEFPARAMETER HTMLFONTCOERCIONS
'[(HELVETICA NOTO-SANS)
(HELVETICAD NOTO-SANS-DISPLAY)
(TIMESROMAN NOTO-SERIF)
(TIMESROMAND NOTO-SERIF-DISPLAY)
(COURIER NOTO-SANS-MONO)
(GACHA NOTO-SANS-MONO)
(CLASSIC NOTO-SERIF)
(MODERN NOTO-SANS)
(CREAM NOTO-SANS)
(TERMINAL NOTO-SANS-MONO)
(LOGO NOTO-SANS-MONO)
(OPTIMA NOTO-SERIF)
(TITAN NOTO-SANS-MONO)
( (* (<= * 5))
(* 6)
)
( (* (<= 51 * 91))
(* 50)
)
( (* (< 92 *))
(* 92)
)
( (* (ODDP *))
(* (ADD1 *))
]
"Font coercion rules for COERCEFONTSPEC")
(CL:DEFVAR HTMLFONTDIRECTORIES '(".")
"The directory that holds .wfm and .wcm files")
(CL:DEFPARAMETER HTMLFONTEXTENSIONS '("medleyhtmlfont" "wcm")
"The extensions that identify HTML font metrics files")
(CL:DEFPARAMETER HTMLCHARSETFNS (LET [(ORIGINAL '((MEDLEYFONT MEDLEYFONT.FILEP MEDLEYFONT.GETCHARSET)
(HTMLFONT HTMLFONT.FILEP HTMLFONT.GETCHARSET]
(CDR ORIGINAL))
(* ;;
"%"comment out%" the MEDLEYFONT support, which was smashing the CSS-CLASS somehow")
"HTML charset functions. Ignore MEDLEYFONT for now.")
(CL:DEFVAR *GOOGLE-FONTS-BASE-URL* "https://fonts.googleapis.com/css2"
"The base of the Google Fonts font stylesheet URL")
(CL:DEFPARAMETER *HTML-STANDARD-PAGE-WEB-FONT-FAMILIES* '(NOTO-SANS NOTO-SANS-MONO NOTO-SANS-DISPLAY
NOTO-SERIF NOTO-SERIF-DISPLAY)
(* ;;
"these correspond to the property names in *HTML-STYLESHEET-FONT-FAMILY-DEFS*")
"The names of the standard font families to be included in the HTML pages we write")
(CL:DEFPARAMETER *HTML-STYLESHEET-FONT-FAMILY-DEFS*
`(NOTO-SANS ("Noto Sans" "Noto Sans TC" "Noto Sans JP" "Noto Sans KR" "Noto Sans Arabic"
"Noto Sans Hebrew" "Noto Sans Runic" "Noto Sans Georgian" "Noto Sans Armenian"
"Noto Sans Thai" "Noto Sans Lao" "Noto Sans Gurmukhi" "Noto Sans Bengali"
"Noto Sans Coptic" "Noto Sans Javanese" "Noto Sans Math" "Noto Sans Symbols"
"Noto Sans Symbols 2" "Noto Emoji")
NOTO-SANS-MONO
("Noto Sans Mono")
NOTO-SANS-DISPLAY
("Noto Sans Display")
NOTO-SERIF
("Noto Serif" "Noto Serif TC" "Noto Serif JP" "Noto Serif KR" "Noto Naskh Arabic"
"Noto Serif Hebrew" ,(\SF "Noto Sans Runic")
"Noto Serif Georgian" "Noto Serif Armenian" "Noto Serif Thai" "Noto Serif Lao"
"Noto Serif Devanagari" "Noto Serif Gurmukhi" "Noto Serif Bengali"
,(\SF "Noto Sans Coptic")
,(\SF "Noto Sans Javanese")
,(\SF "Noto Sans Math")
,(\SF "Noto Sans Symbols")
,(\SF "Noto Sans Symbols 2")
"Noto Emoji")
NOTO-SERIF-DISPLAY
("Noto Serif Display"))
"The font family styles we'll load into the page. Sync with FontStackDefinitions.")
(CL:DEFCONSTANT *DEFAULT-WEB-FONT-METRICS-FILE-NAME* "font-metrics.data"
"The usual name of the raw font metrics file we get from the browser")
(CL:DEFCONSTANT *WEB-FONT-EXPANSIONS* '(REGULAR)
"The Web font expansions we support")
(CL:DEFCONSTANT *WEB-FONT-FAMILIES* '((NOTO-SANS . "ns")
(NOTO-SANS-MONO . "nsm")
(NOTO-SANS-DISPLAY . "nsd")
(NOTO-SERIF . "nsf")
(NOTO-SERIF-DISPLAY . "nsfd"))
"A-list of supported Web font names, as atoms, with the CSS class name")
(CL:DEFCONSTANT *WEB-FONT-FAMILY-NAMES* '("Noto Sans" "Noto Sans Mono" "Noto Sans Display"
"Noto Serif" "Noto Serif Display")
(* ;; "keep this synchronized with *WEB-FONT-FAMILIES*")
"The names of the font families we're using, suitable for use in CSS.")
(CL:DEFCONSTANT *WEB-FONT-SIZES*
'(6 8 10 12 14 16 18 20 24 32 34 36 38 40 42 44 46 48 50 92)
"Sizes of the Web fonts we support, in points"
(* ;; "Keep this in sync with WebFontMetrics.Main.FONT_SIZES")
)
(CL:DEFCONSTANT *WEB-FONT-SLOPES* '(REGULAR ITALIC)
"The Web font slopes we support")
(CL:DEFCONSTANT *WEB-FONT-WEIGHTS* '(MEDIUM BOLD)
"The supported Web font weights")
(CL:DEFPARAMETER *BROWSER-FONTS-MAP* '((NOTO-SANS . "sans-serif")
(NOTO-SANS-MONO . "monospace")
(NOTO-SANS-DISPLAY . "sans-serif")
(NOTO-SERIF . "serif")
(NOTO-SERIF-DISPLAY . "serif"))
"Map each of our fonts to a browser default. The metrics for these characters may be incorrect.")
(CL:DEFCONSTANT *WEB-CHARSET-METRICS-EXT* "wcm"
"The filename extension for files that describe a charset's metrics")
(CL:DEFCONSTANT *WEB-CHARSET-METRICS-DOT-EXT* (CONCAT "." *WEB-CHARSET-METRICS-EXT*)
"The filename extension for files that describe a charset's metrics, prepended with a dot (.)")
(CL:DEFCONSTANT *WEB-FONT-METRICS-EXT* "wfm"
"The file extension for a web font metrics file")
(CL:DEFCONSTANT *WEB-FONT-METRICS-DOT-EXT* (CONCAT "." *WEB-FONT-METRICS-EXT*)
"The file extension for a web font metrics file, prepended with a dot (.)")
(CL:DEFVAR *HTML-ADD-FONT-DEFS* T
"Initialize font defs and font vars upon loading HTMLSTREAM?")
(CL:DEFCONSTANT *BROWSER-FONT-FILE-VERSION* 3
"The current version of the font-metrics.data file")
(CL:DEFCONSTANT *HTML.DEFAULT-PAGE-REGION*
(* ;; "the default region of an HTML page, in centipoints")
(create REGION
LEFT _ 0
BOTTOM _ 0
WIDTH _ (HTML.INCHES-TO-CENTIPOINTS 8.5)
HEIGHT _ (HTML.INCHES-TO-CENTIPOINTS 11.0)))
(CL:DEFCONSTANT HTML.FONTCREATE.DEVICENAME 'HTML)
(CL:DEFCONSTANT HTML.IMAGETYPE 'HTML)
(CL:DEFCONSTANT HTML.STATE.BETWEEN-PAGES :HTML.STATE.BETWEEN-PAGES)
(CL:DEFCONSTANT HTML.STATE.CLOSED :HTML.STATE.CLOSED)
(CL:DEFCONSTANT HTML.STATE.NON-TEXT :HTML.STATE.NON-TEXT)
(CL:DEFCONSTANT HTML.STATE.NON-TEXT-OUTPUT :HTML.STATE.NON-TEXT-OUTPUT)
(CL:DEFCONSTANT HTML.STATE.TEXT-OUTPUT :HTML.STATE.TEXT-OUTPUT)
(CL:DEFCONSTANT HTML.STATES '(HTML.STATE.BETWEEN-PAGES HTML.STATE.NON-TEXT-OUTPUT
HTML.STATE.TEXT-OUTPUT HTML.STATE.CLOSED)
"All HTML imagestream states, for informational use.")
(DEFGLOBALVAR \HTMLSTREAM.FDEV "The FDEV for HTML output")
(CL:DEFCONSTANT *XCCS-KNOWN-CHARSETS*
'(0 33 34 35 36 37 38 39 40 224 225 226 227 228 238 239 240 241)
"The set of defined XCCS character sets")
(CL:DEFCONSTANT *XCCS-UNDEFINED-CHARCODE* 61639
"XCCS defines 0xF0C7 (61639) as 'Replacement symbol (for undefined code points)'")
(CL:DEFCONSTANT HTML-MEDLEY-CREDITS
"<!-- Created by Medley Interlisp software. Details at interlisp.org. -->~%%"
"Embed this at the start of every HTML document. Contents must respect the rules for HTML/XML comments."
)
(CL:DEFUN HTML-FONT-METRICS-VERSION-OK? (VERSION-EXPR)
"Return T if VERSION-EXPR is a valid font or charset version expression"
(AND (LISTP VERSION-EXPR)
(EQ ':FORMAT (CAR VERSION-EXPR))
(EQUAL (CADR VERSION-EXPR)
*BROWSER-FONT-FILE-VERSION*)))
(CL:DEFUN CHECK-BROWSER-METRICS-FORMAT (VERSION)
(* ;; "check that the version looks like (format <number>)")
(if (NOT (HTML-FONT-METRICS-VERSION-OK? VERSION))
then (ERROR "File does not begin with expected format declaration" VERSION))
VERSION)
(CL:DEFUN CSS-CLASS-FROM (FAMILY SIZE FACE)
"Compute the CSS class names from the FAMILY, SIZE, and FACE"
(* ;; "the result will look like %"nsd10 bold italic%" or a variant")
[LET* [(FONT-CLASS-NAME-ROOT (CDR (FASSOC FAMILY *WEB-FONT-FAMILIES*)))
(SIZE-CLASS-NAME (CONCAT "sz" SIZE))
(WEIGHT-CLASS (COND
((EQ (CAR FACE)
'BOLD)
"bold")
(T NIL)))
(SLOPE-CLASS (COND
((EQ (CADR FACE)
'ITALIC)
"italic")
(T NIL)))
(SLOPE-AND-WEIGHT-CLASSES (COND
((AND (NULL WEIGHT-CLASS)
(NULL SLOPE-CLASS))
"")
((NULL WEIGHT-CLASS)
SLOPE-CLASS)
((NULL SLOPE-CLASS)
WEIGHT-CLASS)
(T (CONCAT WEIGHT-CLASS " " SLOPE-CLASS]
(CONCAT FONT-CLASS-NAME-ROOT " " SIZE-CLASS-NAME (COND
((EQUAL SLOPE-AND-WEIGHT-CLASSES "")
"")
(T (CONCAT " " SLOPE-AND-WEIGHT-CLASSES])
(CL:DEFUN CSS-CLASS-FROM-FAMILY (FAMILY)
"Compute the CSS class name from the FAMILY"
(CDR (FASSOC FAMILY *WEB-FONT-FAMILIES*)))
(CL:DEFUN \HTML.READ-FONT-METRICS (FAMILY FACE SIZE DIR)
"Read the font metrics file and return the WEB-FONT-DESCR"
(LET* ((WEIGHT (fetch (WEB-FONT-FACE :WEIGHT) of FACE))
(SLOPE (fetch (WEB-FONT-FACE :SLOPE) of FACE))
(EXPANSION (fetch (WEB-FONT-FACE :EXPANSION) of FACE))
(FILE-NAME (PACKFILENAME.STRING 'DIRECTORY DIR 'NAME (MAKE-FONT-METRICS-FILE-NAME FAMILY
SIZE WEIGHT SLOPE EXPANSION)))
(*PACKAGE* (CL:FIND-PACKAGE "IL")))
(CL:WITH-OPEN-STREAM (IN (OPENSTREAM FILE-NAME 'INPUT))
(CHECK-BROWSER-METRICS-FORMAT (READ IN))
(LET* ((DESCR (READ IN))
(FAMILY (fetch (WEB-FONT-DESCR :NAME) of DESCR))
(FACE (fetch (WEB-FONT-DESCR :FACE) of DESCR))
(SIZE (fetch (WEB-FONT-DESCR :SIZE) of DESCR))
(HEIGHT (fetch (WEB-FONT-DESCR :HEIGHT) of DESCR))
(MAX-ASCENT (fetch (WEB-FONT-DESCR :MAX-ASCENT) of DESCR))
(MAX-DESCENT (fetch (WEB-FONT-DESCR :MAX-DESCENT) of DESCR))
(SLUG-WIDTH (fetch (WEB-FONT-DESCR :SLUG-WIDTH) of DESCR))
(CHARSETS (fetch (WEB-FONT-DESCR :CHARSETS) of DESCR)))
(\HTML-DBG "font: ~A face: ~A size: ~A height: ~A #charsets: ~d~%%" FAMILY FACE
SIZE HEIGHT (LENGTH CHARSETS))
DESCR))))
(CL:DEFUN \HTML.READ-CHARSET-METRICS (FAMILY SIZE FACE CHARSET-NUM DIR)
"Read a file containing a charset's metrics and return the WEB-CHARSET-METRICS"
[LET* ((WEIGHT (fetch (WEB-FONT-FACE :WEIGHT) of FACE))
(SLOPE (fetch (WEB-FONT-FACE :SLOPE) of FACE))
(EXPANSION (fetch (WEB-FONT-FACE :EXPANSION) of FACE))
(FILE-NAME (PACKFILENAME.STRING 'DIRECTORY DIR 'NAME (MAKE-CHARSET-METRICS-FILE-NAME FAMILY
SIZE WEIGHT SLOPE EXPANSION
CHARSET-NUM)))
(*PACKAGE* (CL:FIND-PACKAGE "IL")))
(AND (INFILEP FILE-NAME)
(CL:WITH-OPEN-STREAM (IN (OPENSTREAM FILE-NAME 'INPUT))
(CHECK-BROWSER-METRICS-FORMAT (READ IN))
(LET* ((CHARSET (READ IN))
(MAX-ASCENT (fetch (WEB-CHARSET-METRICS :MAX-ASCENT) of CHARSET))
(MAX-DESCENT (fetch (WEB-CHARSET-METRICS :MAX-DESCENT) of CHARSET))
(WIDTHS (fetch (WEB-CHARSET-METRICS :WIDTHS) of CHARSET)))
(\HTML-DBG "charset family: ~A face: ~A size: ~A charset: ~d~%%" FAMILY
FACE SIZE CHARSET-NUM)
CHARSET])
(CL:DEFUN \HTML.SVG-STROKE-DASHARRAY (DASHING)
"Turn an array of dashes (on/off sequences) to an SVG stroke-dasharray attribute"
[if (NULL DASHING)
then ""
else
(* ;;
"If the DASHING list has an odd length, keep SVG from %"reflecting%" it by appending a 0")
[if (ODDP (LENGTH DASHING))
then (SETQ DASHING (APPEND DASHING (LIST 0]
(CL:FORMAT NIL " stroke-dasharray='~A'"
(CONCATLIST (for SENSOR on DASHING as ON-OFF in DASHING
collect (if (NOT (NUMBERP ON-OFF))
then (ERROR "Dashes must be numbers" ON-OFF))
(if (CDR SENSOR)
then (CONCAT ON-OFF " ")
else ON-OFF])
(CL:DEFUN \HTML.SVG-BRUSH-WIDTH (BRUSH-SPEC)
"Convert a brush specification to a width SVG can use"
(COND
((AND (LISTP BRUSH-SPEC)
(LITATOM (CAR BRUSH-SPEC))
(NUMBERP (CADR BRUSH-SPEC)))
(CADR BRUSH-SPEC))
((NUMBERP BRUSH-SPEC)
BRUSH-SPEC)
(T (ERROR "Not a valid brush:" BRUSH-SPEC))))
(CL:DEFUN \HTML.SVG-BRUSH-CLASS (BRUSH-SPEC)
"Convert the brush to a CSS class value."
(LET [(LINEJOIN-CLASS (COND
((AND (LISTP BRUSH-SPEC)
(LITATOM (CAR BRUSH-SPEC))
(NUMBERP (CADR BRUSH-SPEC)))
(SELECTQ (CAR BRUSH-SPEC)
(ROUND "joinr")
(SQUARE "joinm")
"joinm"))
(T "joinm")))
(LINECAP-CLASS (COND
((AND (LISTP BRUSH-SPEC)
(LITATOM (CAR BRUSH-SPEC))
(NUMBERP (CADR BRUSH-SPEC)))
(SELECTQ (CAR BRUSH-SPEC)
(ROUND "capr")
(SQUARE "caps")
"capb"))
(T "capb"]
(CONCAT LINEJOIN-CLASS " " LINECAP-CLASS)))
(CL:DEFUN \HTML.SVG-COLOR (COLOR)
"Given a Medley color, convert it to an RGB value for SVG, #rrggbb."
(LET ((TRIPLE (INSURE.RGB.COLOR COLOR))) (* ; "returns an RGB list")
(CL:FORMAT NIL "#~2,'0x~2,'0x~2,'0x" (CAR TRIPLE)
(CADR TRIPLE)
(CADDR TRIPLE))))
(CL:DEFSTRUCT (HTML.IMAGEDATA (:CONC-NAME \HTML-))
"Private data for HTMLSTREAMs"
BACKING-STREAM
FONT
CLIPPING-REGION
(SPACE-FACTOR 1.0)
IMAGE-OPERATION
(FG-COLOR (INSURE.RGB.COLOR 'BLACK))
(BG-COLOR (INSURE.RGB.COLOR 'WHITE))
(SVG-FG-COLOR (\HTML.SVG-COLOR 'BLACK)) (* ; "stored as %"#rrggbb%"")
(SVG-BG-COLOR (\HTML.SVG-COLOR 'WHITE)) (* ; "stored as %"#rrggbb%"")
(SCALE *CENTIPOINTS-PER-POINT*)
(PAGE-NUM 0)
(X-POSITION 0)
(Y-POSITION 0)
(LEFT-MARGIN 0)
(TOP-MARGIN 1024)
(RIGHT-MARGIN 1024)
(BOTTOM-MARGIN 0)
(LINEFEED 12)
(STATE HTML.STATE.BETWEEN-PAGES)
PAGE-HEIGHT PAGE-WIDTH TITLE)
(CL:DEFSTRUCT (HTML.FONTINFO (:CONC-NAME \HTMLFONT-)
[:PRINT-FUNCTION (LAMBDA (OBJ STREAM DEPTH)
(CL:FORMAT STREAM "<~a css: '~a' slug: ~a>"
(\HTML.TOADDR OBJ)
(\HTMLFONT-CSS-CLASS OBJ)
(\HTMLFONT-SLUG-WIDTH OBJ])
"Private data for an HTML FONTDESCRIPTOR"
(CHARSETS NIL)
(CSS-CLASS NIL)
(SLUG-WIDTH NIL)
(WARNED-CHARSETS NIL))
(DEFMACRO with-htmldata ((DATA-VAR-NAME STREAM)
&BODY
(BODY DECLS ENV))
`(LET [(,DATA-VAR-NAME (fetch (STREAM IMAGEDATA) of ,STREAM]
,@DECLS
,@BODY))
(CL:DEFUN HTML.OUTPUT-GRAPHICS (STREAM)
"We're going to do some graphics now"
(with-htmldata (DATA STREAM)
(\HTML.MAYBE-START-PAGE DATA STREAM)
(SELECTC (\HTML-STATE DATA)
(HTML.STATE.NON-TEXT-OUTPUT (* ; "this is the state we need")
)
(HTML.STATE.TEXT-OUTPUT (* ; "end the text section")
(CL:FORMAT (\HTML-BACKING-STREAM DATA)
"</text>~%%"))
NIL)
(* ;; "finally")
(CL:SETF (\HTML-STATE DATA)
HTML.STATE.NON-TEXT-OUTPUT)))
(CL:DEFUN HTML.OUTPUT-TEXT (STREAM)
"We're being asked to write text"
(with-htmldata (DATA STREAM)
(\HTML.MAYBE-START-PAGE DATA STREAM)
(LET* ((FONT (\HTML-FONT DATA))
(FONT-DATA (fetch (FONTDESCRIPTOR OTHERDEVICEFONTPROPS) of FONT)))
(SELECTC (\HTML-STATE DATA)
((LIST HTML.STATE.NON-TEXT-OUTPUT HTML.STATE.BETWEEN-PAGES)
(* ; "start a new text section")
(CL:FORMAT (\HTML-BACKING-STREAM DATA)
"<text class='~a' fill='~a' x='~d' y='~d'>"
(\HTMLFONT-CSS-CLASS FONT-DATA)
(\HTML-SVG-FG-COLOR DATA)
(\HTML-X-POSITION DATA)
(\HTML.SVG-Y DATA (\HTML-Y-POSITION DATA))))
(HTML.STATE.TEXT-OUTPUT (* ; "continue this text section")
)
NIL)
(* ;; "finally")
(CL:SETF (\HTML-STATE DATA)
HTML.STATE.TEXT-OUTPUT))))
(CL:DEFUN \HTML.MAYBE-START-PAGE (DATA STREAM)
(* ;; "We're going to do output of some sort, so start an SVG stanza if necessary")
[with-htmldata (DATA STREAM)
(if (EQ (\HTML-STATE DATA)
HTML.STATE.BETWEEN-PAGES)
then (LET ((BACKING (\HTML-BACKING-STREAM DATA)))
(CL:FORMAT BACKING
"<svg width='~dpt' height='~dpt' viewBox='~d ~d ~d ~d' style='background-color: ~a;'>~%%"
(CP-TO-P (\HTML-PAGE-WIDTH DATA))
(CP-TO-P (\HTML-PAGE-HEIGHT DATA))
0 0 (\HTML-PAGE-WIDTH DATA)
(\HTML-PAGE-HEIGHT DATA)
(\HTML-SVG-BG-COLOR DATA))
[CL:SETF (\HTML-TOP-MARGIN DATA)
(- (\HTML-PAGE-HEIGHT DATA)
(FONTPROP (\HTML-FONT DATA)
'HEIGHT]
(* ;; "Don't reset X, just Y")
(CL:SETF (\HTML-Y-POSITION DATA)
(\HTML-TOP-MARGIN DATA])
(CL:DEFUN \HTML.END-PAGE (DATA STREAM)
"End the current page"
(if (EQ (\HTML-STATE DATA)
HTML.STATE.TEXT-OUTPUT)
then (HTML.OUTPUT-GRAPHICS STREAM))
(CL:INCF (\HTML-PAGE-NUM DATA))
(HTML.RESET STREAM)
(LET ((BACKING (\HTML-BACKING-STREAM DATA)))
(CL:FORMAT BACKING "</svg>~%%"))
(CL:SETF (\HTML-STATE DATA)
HTML.STATE.BETWEEN-PAGES))
(CL:DEFUN \HTML.TAB-DELTA-X (HTML-DATA)
"Return the distance a TAB will move in the X dimension"
(LET [(TABSPACE (TIMES TAB-WIDTH-IN-CHARACTERS (ffetch FONTAVGCHARWIDTH of (\HTML-FONT HTML-DATA]
(IDIFFERENCE TABSPACE (IREMAINDER (IDIFFERENCE (\HTML-X-POSITION HTML-DATA)
(\HTML-LEFT-MARGIN HTML-DATA))
TABSPACE))))
(CL:DEFUN \HTML.CHARWIDTH (STREAM CHCODE)
"Raw character width accessor that coerces undefined characters to slug"
[with-htmldata (DATA STREAM)
(LET* [(FONT-INFO (fetch (FONTDESCRIPTOR OTHERDEVICEFONTPROPS) of (\HTML-FONT DATA)))
(CHARSET-INFO (\INSURECHARSETINFO (\HTML-FONT DATA)
(\CHARSET CHCODE]
(DO-NOT (PRINTOUT T "Widths: " (FETCH (CHARSETINFO WIDTHS) OF CHARSET-INFO)
T)
(PRINTOUT T "Did get widths" T))
(if (= CHCODE *XCCS-UNDEFINED-CHARCODE*)
then (\HTMLFONT-SLUG-WIDTH FONT-INFO)
else (\FGETCHARWIDTH (\HTML-FONT DATA)
CHCODE])
(CL:DEFUN HTML.WRITE-PREFACE (STREAM)
"Write the preamble to the file: <html><head>... etc."
(with-htmldata
(DATA STREAM)
(LET ((BACKING (\HTML-BACKING-STREAM DATA))
(TITLE (\HTML-TITLE DATA))
(FAMILY-VAR-NAMES NIL))
(CL:FLET ((FONT-DEF (VAR-NAME FAMILY)
(CL:FORMAT BACKING " const ~a = [" VAR-NAME)
(PUSH FAMILY-VAR-NAMES VAR-NAME)
[LET ((FAMILY-MEMBERS (LISTGET *HTML-STYLESHEET-FONT-FAMILY-DEFS* FAMILY)))
(for FONT in FAMILY-MEMBERS as TAIL on FAMILY-MEMBERS
do (if (CDR TAIL)
then (CL:FORMAT BACKING "%"~a%"," FONT)
else (CL:FORMAT BACKING "%"~a%"" FONT]
(CL:FORMAT BACKING "].map(singleQuote).join(%",%");~%%")))
(CL:FORMAT BACKING "<!DOCTYPE HTML>~%%")
(CL:FORMAT BACKING "<html>~%%")
(CL:FORMAT BACKING "<head>~%%")
(CL:FORMAT BACKING "<meta charset='utf-8'>~%%")
(CL:FORMAT BACKING "<title>~a</title>~%%" (HTML-ENCODE TITLE))
(CL:FORMAT BACKING
"<!-- Created by Medley Interlisp software. Details at interlisp.org. -->~%%")
(CL:FORMAT BACKING
"<link rel=%"preconnect%" href=%"https://fonts.googleapis.com%">~%%")
(CL:FORMAT BACKING
"<link rel=%"preconnect%" href=%"https://fonts.gstatic.com%" crossorigin>~%%")
[for FAMILY in *HTML-STANDARD-PAGE-WEB-FONT-FAMILIES*
do (CL:FORMAT BACKING "<link rel=%"stylesheet%" href=%"~a%">~%%"
(\HTML.MAKE-STYLESHEET-URL (LISTGET *HTML-STYLESHEET-FONT-FAMILY-DEFS*
FAMILY]
(CL:FORMAT BACKING "<script>~%%")
(CL:FORMAT BACKING " function singleQuote(fn) { return %"'%" + fn + %"'%"; }~%%")
(FONT-DEF "notoSans" 'NOTO-SANS)
(FONT-DEF "notoSansMono" 'NOTO-SANS-MONO)
(FONT-DEF "notoSansDisplay" 'NOTO-SANS-DISPLAY)
(FONT-DEF "notoSerif" 'NOTO-SERIF)
(FONT-DEF "notoSerifDisplay" 'NOTO-SERIF-DISPLAY)
(CL:FORMAT BACKING " const sizes = [")
(for SIZE in *WEB-FONT-SIZES* do (CL:FORMAT BACKING "~d, " SIZE))
(CL:FORMAT BACKING "];~%%")
(CL:FORMAT BACKING " const families = [")
(for FAMILY-VAR in FAMILY-VAR-NAMES do (CL:FORMAT BACKING "~a, " FAMILY-VAR))
(CL:FORMAT BACKING "];~%%")
(CL:FORMAT BACKING " const fontLoadPromises = [];~%%")
(CL:FORMAT BACKING " for (const family of families) {~%%")
(CL:FORMAT BACKING " for (const size of sizes) {~%%")
(CL:FORMAT BACKING
" fontLoadPromises.push(document.fonts.load(size+%"pt %"+family));~%%"
)
(CL:FORMAT BACKING " }~%%")
(CL:FORMAT BACKING " }~%%")
(CL:FORMAT BACKING
" Promise.all(fontLoadPromises).then(_ => { console.log('All fonts loaded');});~%%"
)
(CL:FORMAT BACKING "</script>~%%")
(* ;; "define styles")
(CL:FORMAT BACKING "<style>~%%")
(* ;; "all space chars in text blocks are significant:")
(CL:FORMAT BACKING "text { white-space: pre; }~%%")
(* ;;
"the page is scaled to use centipoints, so font sizes below are measured in centipoints, not points")
(* ;; "define font families as CSS classes")
(for FONT-FAMILY in *HTML-STANDARD-PAGE-WEB-FONT-FAMILIES*
do (CL:FORMAT BACKING ".~A { font-family: " (CDR (FASSOC FONT-FAMILY
*WEB-FONT-FAMILIES*)))
[LET ((FONTS (LISTGET *HTML-STYLESHEET-FONT-FAMILY-DEFS* FONT-FAMILY)))
(for FONT in FONTS as TAIL on FONTS
do (if (CDR TAIL)
then (CL:FORMAT BACKING "'~a', " FONT)
else (CL:FORMAT BACKING "'~a', ~a" FONT (CDR (FASSOC FONT-FAMILY
*BROWSER-FONTS-MAP*
]
(CL:FORMAT BACKING "; }~%%"))
(* ;; "define font sizes as CSS classes")
[for SIZE in *WEB-FONT-SIZES* do (CL:FORMAT BACKING ".sz~d { font-size: ~dpt; }~%%"
SIZE (FIXR (P-TO-CP SIZE]
(* ;; "define faces as CSS classes")
(CL:FORMAT BACKING ".bold { font-weight: bold; }~%%")
(CL:FORMAT BACKING ".italic { font-style: italic; }~%%")
(* ;; "define stroke-linejoin and stroke-linecap CSS classes")
(* ;; "linecaps")
(CL:FORMAT BACKING ".capb { stroke-linecap: butt; }~%%")
(CL:FORMAT BACKING ".capr { stroke-linecap: round; }~%%")
(CL:FORMAT BACKING ".caps { stroke-linecap: square; }~%%")
(* ;; "linejoins")
(CL:FORMAT BACKING ".joinm { stroke-linejoin: miter; }~%%")
(CL:FORMAT BACKING ".joinr { stroke-linejoin: round; }~%%")
(CL:FORMAT BACKING ".joinb { stroke-linejoin: bevel; }~%%")
(* ;; "end of the style definitions")
(CL:FORMAT BACKING "</style>~%%")
(CL:FORMAT BACKING "</head>~%%")
(CL:FORMAT BACKING "<body>~%%"))
BACKING)))
(CL:DEFUN CONVERT-FAMILY-TO-QUERY-PARAM (FONT-FAMILY-NAME &OPTIONAL (INITIAL-PARAM? NIL))
"Convert a font family name like 'Noto Sans' to a URL query param like '&family=Noto+Sans'"
(CL:FLET ((REPLACE-SPACES (STR)
(LET* ((STR-LENGTH (CL:LENGTH STR))
(NEW-STR (CL:MAKE-STRING STR-LENGTH)))
[for I from 0 to (CL:1- STR-LENGTH)
do (LET ((CH (CL:CHAR STR I)))
(CL:SETF (CL:CHAR NEW-STR I)
(if (EQP CH #\Space)
then #\+
else (CL:CHAR STR I]
NEW-STR)))
(CONCAT (if INITIAL-PARAM?
then "?"
else "&")
"family="
(REPLACE-SPACES FONT-FAMILY-NAME))))
(CL:DEFUN \HTML.MAKE-STYLESHEET-URL (FONT-FAMILY-NAMES)
"Return the stylesheet URL for downloading the given font family names from Google Fonts"
[LET [(FULL-URL (CONCAT *GOOGLE-FONTS-BASE-URL* (CONVERT-FAMILY-TO-QUERY-PARAM (CAR
FONT-FAMILY-NAMES
)
T]
(CONCAT FULL-URL (APPLY* #'CONCATLIST (MAPCAR (CDR FONT-FAMILY-NAMES)
#'CONVERT-FAMILY-TO-QUERY-PARAM])
(CL:DEFUN OPENHTMLSTREAM (FILENAME &OPTIONAL OPTIONS)
"Open and return an HTML imagestream"
(LET* ((TITLE (OR (LISTGET OPTIONS 'TITLE)
FILENAME))
(REGION (OR (LISTGET OPTIONS 'REGION)
*HTML.DEFAULT-PAGE-REGION*))
[BACKING (OPENSTREAM FILENAME 'OUTPUT NIL '((FORMAT :UTF-8-SLUG]
(IMAGEOPS (create IMAGEOPS))
(IMAGEDATA (MAKE-HTML.IMAGEDATA :BACKING-STREAM BACKING :TITLE (CL:FORMAT NIL "~a" TITLE)
:PAGE-WIDTH
(fetch (REGION WIDTH) of REGION)
:PAGE-HEIGHT
(fetch (REGION HEIGHT) of REGION)))
(HTMLSTREAM (create STREAM
FULLFILENAME _ (FULLNAME BACKING)
DEVICE _ \HTMLSTREAM.FDEV
ACCESS _ 'OUTPUT
OUTCHARFN _ #'HTML.OUTCHARFN
STRMBOUTFN _ #'\BUFFERED.BOUT (* ; "is this right?")
IMAGEOPS _ IMAGEOPS
USERCLOSEABLE _ T
USERVISIBLE _ T
IMAGEDATA _ IMAGEDATA)))
(* ;; "Make the backing file invisible")
(replace (STREAM USERVISIBLE) of BACKING with NIL)
(* ;; "install our OUTCHARFN. you can't do this in the create.")
(replace (STREAM OUTCHARFN) of HTMLSTREAM with #'HTML.OUTCHARFN)
(* ;; "Set the informational fields")
(replace IMFONTCREATE of IMAGEOPS with HTML.FONTCREATE.DEVICENAME)
(replace IMAGETYPE of IMAGEOPS with HTML.IMAGETYPE)
(* ;; "Set the functions")
(replace IMCLOSEFN of IMAGEOPS with #'HTML.CLOSEFN)
(replace IMDRAWLINE of IMAGEOPS with #'HTML.DRAWLINE)
(replace IMDRAWCURVE of IMAGEOPS with #'HTML.DRAWCURVE)
(replace IMDRAWCIRCLE of IMAGEOPS with #'HTML.DRAWCIRCLE)
(replace IMDRAWELLIPSE of IMAGEOPS with #'HTML.DRAWELLIPSE)
(replace IMDRAWARC of IMAGEOPS with #'HTML.DRAWARC)
(replace IMFILLPOLYGON of IMAGEOPS with #'HTML.FILLPOLYGON)
(replace IMDRAWPOLYGON of IMAGEOPS with #'HTML.DRAWPOLYGON)
(replace IMFILLCIRCLE of IMAGEOPS with #'HTML.FILLCIRCLE)
(replace IMBLTSHADE of IMAGEOPS with #'HTML.BLTSHADE)
(replace IMBITBLT of IMAGEOPS with #'HTML.BITBLT)
(replace IMSCALEDBITBLT of IMAGEOPS with #'HTML.SCALEDBITBLT)
(replace IMMOVETO of IMAGEOPS with #'HTML.MOVETO)
(replace IMCHARWIDTH of IMAGEOPS with #'HTML.CHARWIDTH)
(replace IMNEWPAGE of IMAGEOPS with #'HTML.NEWPAGE)
(replace IMTERPRI of IMAGEOPS with #'HTML.TERPRI)
(replace IMRESET of IMAGEOPS with #'HTML.RESET)
(replace IMCLIPPINGREGION of IMAGEOPS with #'HTML.CLIPPINGREGION)
(replace IMXPOSITION of IMAGEOPS with #'HTML.XPOSITION)
(replace IMYPOSITION of IMAGEOPS with #'HTML.YPOSITION)
(replace IMFONT of IMAGEOPS with #'HTML.FONT)
(replace IMLEFTMARGIN of IMAGEOPS with #'HTML.LEFTMARGIN)
(replace IMRIGHTMARGIN of IMAGEOPS with #'HTML.RIGHTMARGIN)
(replace IMTOPMARGIN of IMAGEOPS with #'HTML.TOPMARGIN)
(replace IMBOTTOMMARGIN of IMAGEOPS with #'HTML.BOTTOMMARGIN)
(replace IMLINEFEED of IMAGEOPS with #'HTML.LINEFEED)
(replace IMSCALE of IMAGEOPS with #'HTML.SCALE)
(replace IMSPACEFACTOR of IMAGEOPS with #'HTML.SPACEFACTOR)
(replace IMOPERATION of IMAGEOPS with #'HTML.OPERATION)
(replace IMBACKCOLOR of IMAGEOPS with #'HTML.BACKCOLOR)
(replace IMCOLOR of IMAGEOPS with #'HTML.COLOR)
(* ;; "Maybe implement these later:")
(replace IMWRITEPIXEL of IMAGEOPS with #'NILL)
(replace IMROTATE of IMAGEOPS with #'NILL)
(replace IMTRANSLATE of IMAGEOPS with #'NILL)
(replace IMSCALE2 of IMAGEOPS with #'NILL)
(replace IMPUSHSTATE of IMAGEOPS with #'NILL)
(replace IMPOPSTATE of IMAGEOPS with #'NILL)
(replace IMDEFAULTSTATE of IMAGEOPS with #'NILL)
(replace IMCHARWIDTHY of IMAGEOPS with #'NILL)
(* ;; "we can init the font now that the IMAGEOPS are complete")
(LET* [(USER-FONT (LISTGET OPTIONS 'FONT))
(DEFAULT-FONT (if USER-FONT
then (FONTCREATE USER-FONT HTMLSTREAM)
(* ; "right??!")
else (FONTCREATE 'NOTO-SANS 10 'MRR 0 'HTML]
(* ;; "Finish initializing the IMAGEDATA")
(\HTML.INIT-IMAGEDATA HTMLSTREAM DEFAULT-FONT)
(* ;; "write the preface")
(HTML.WRITE-PREFACE HTMLSTREAM)
(* ;; "return the stream")
HTMLSTREAM)))
(CL:DEFUN HTML.BACKCOLOR (STREAM NEW-COLOR)
"Set the bg color. Generate the SVG color and set it into the imagedata. Affects next page geenerated."
(with-htmldata (DATA STREAM)
(LET ((OLD-VALUE (\HTML-BG-COLOR DATA)))
[COND
(NEW-COLOR (LET ((EFFECTIVE-COLOR (INSURE-COLOR-OR-SHADE NEW-COLOR)))
(CL:WHEN (NEQ EFFECTIVE-COLOR OLD-VALUE)
(CL:SETF (\HTML-BG-COLOR DATA)
EFFECTIVE-COLOR)
(CL:SETF (\HTML-SVG-BG-COLOR DATA)
(\HTML.SVG-COLOR EFFECTIVE-COLOR))
(HTML.OUTPUT-GRAPHICS STREAM))]
OLD-VALUE)))
(CL:DEFUN HTML.BITBLT (SOURCEBITMAP SOURCELEFT SOURCEBOTTOM STREAM DESTINATIONLEFT DESTINATIONBOTTOM
WIDTH HEIGHT SOURCETYPE OPERATION TEXTURE CLIPPINGREGION
CLIPPEDSOURCELEFT CLIPPEDSOURCEBOTTOM)
(* ;; "blt the image expanded by a scale factor of 1")
(HTML.SCALEDBITBLT SOURCEBITMAP SOURCELEFT SOURCEBOTTOM STREAM DESTINATIONLEFT DESTINATIONBOTTOM
WIDTH HEIGHT SOURCETYPE OPERATION TEXTURE CLIPPINGREGION CLIPPEDSOURCELEFT
CLIPPEDSOURCEBOTTOM 1))
(CL:DEFUN HTML.BLTSHADE (TEXTURE STREAM DESTINATIONLEFT DESTINATIONBOTTOM WIDTH HEIGHT OPERATION
CLIPPINGREGION)
(* ;; "NOTE we ignore OPERATION")
(* ;; "in our monochrome graphics world, SHADEs are 4x4 TEXTUREs. Let's interpret the texture as a shade, meaning a gray value")
(* ;; "because we're dealing with 24-bit color, we can only represent 256 gray shades")
[LET* [(BLACK-BITS (CL:LOGCOUNT TEXTURE))
(BLACK-PROPORTION (/ BLACK-BITS 16))
(GRAY-VALUE (FIXR (TIMES BLACK-PROPORTION 255]
(with-htmldata (DATA STREAM)
(* ;; "clip region against CLIPPINGREGION")
(LET* [(BACKING (\HTML-BACKING-STREAM DATA))
(ORIGINAL-REGION (CREATE REGION
LEFT _ DESTINATIONLEFT