1if not modules then modules = { } end modules ['lpdf-ini'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to lpdf-ini.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files"
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
34local next, type, unpack, rawget = next, type, unpack, rawget
35local char, byte, gsub, sub, match, rep, gmatch, find = string.char, string.byte, string.gsub, string.sub, string.match, string.rep, string.gmatch, string.find
36local formatters, format = string.formatters, string.format
37local concat, sortedhash, sortedkeys, sort, count = table.concat, table.sortedhash, table.sortedkeys, table.sort, table.count
38local utfchar, utfsplit = utf.char, utf.split
39local random, round, max, abs, ceiling = math.random, math.round, math.max, math.abs, math.ceiling
40local setmetatableindex = table.setmetatableindex
41
42local pdfnull = lpdf.null
43local pdfdictionary = lpdf.dictionary
44local pdfarray = lpdf.array
45local pdfconstant = lpdf.constant
46local pdfstring = lpdf.string
47local pdfunicode = lpdf.unicode
48local pdfreference = lpdf.reference
49
50local pdfreserveobject = lpdf.reserveobject
51local pdfflushobject = lpdf.flushobject
52local pdfflushstreamobject = lpdf.flushstreamobject
53
54local report_fonts = logs.reporter("backend","fonts")
55
56local trace_fonts = false
57local trace_details = false
58
59local bpfactor <const> = number.dimenfactors.bp
60local ptfactor <const> = number.dimenfactors.pt
61
62trackers.register("backend.fonts", function(v) trace_fonts = v end)
63trackers.register("backend.fonts.details",function(v) trace_details = v end)
64
65local readers = fonts.handlers.otf.readers
66local getinfo = readers.getinfo
67
68local setposition = utilities.files.setposition
69local readstring = utilities.files.readstring
70local openfile = utilities.files.open
71local closefile = utilities.files.close
72
73local getmapentry = fonts.mappings.getentry
74
75
76
77
78
79local tocardinal1 = char
80
81local function tocardinal2(n)
82
83 return char((n>>8)&0xFF,(n>>0)&0xFF)
84end
85
86local function tocardinal3(n)
87
88 return char((n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
89end
90
91local function tocardinal4(n)
92
93 return char((n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
94end
95
96local function tointeger2(n)
97
98 return char((n>>8)&0xFF,(n>>0)&0xFF)
99end
100
101local function tointeger3(n)
102
103 return char((n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
104end
105
106local function tointeger4(n)
107
108 return char((n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
109end
110
111local function tocardinal8(n)
112 local l = n // 0x100000000
113 local r = n % 0x100000000
114
115
116 return char((l>>24)&0xFF,(l>>16)&0xFF,(l>>8)&0xFF,(l>>0)&0xFF,
117 (r>>24)&0xFF,(r>>16)&0xFF,(r>>8)&0xFF,(r>>0)&0xFF)
118end
119
120
121
122local tounicodedictionary, widtharray, lmtxregistry, collectindices, subsetname, includecidset, forcecidset, tocidsetdictionary
123
124do
125
126
127
128
129
130 local f_mapping_2 = formatters["<%02X> <%s>"]
131 local f_mapping_4 = formatters["<%04X> <%s>"]
132
133local tounicode_template <const> = [[
134%%!PS-Adobe-3.0 Resource-CMap
135%%%%DocumentNeededResources: ProcSet (CIDInit)
136%%%%IncludeResource: ProcSet (CIDInit)
137%%%%BeginResource: CMap (TeX-%s-0)
138%%%%Title: (TeX-%s-0 TeX %s 0)|
139%%%%Version: 1.000
140%%%%EndComments
141/CIDInit /ProcSet findresource begin
142 12 dict begin
143 begincmap
144 /CIDSystemInfo
145 << /Registry (TeX) /Ordering (%s) /Supplement 0 >>
146 def
147 /CMapName
148 /TeX-Identity-%s
149 def
150 /CMapType
151 2
152 def
153 1 begincodespacerange
154 <%s> <%s>
155 endcodespacerange
156 %i beginbfchar
157%s
158 endbfchar
159 endcmap
160 CMapName currentdict /CMap defineresource pop
161 end
162end
163%%%%EndResource
164%%%%EOF]]
165
166 local nounicode do
167
168
169
170
171
172
173
174 local tounicode = fonts.mappings.tounicode
175 local tounicodestring = tounicode
176 local random = math.random
177 local is_nothing = characters.is_nothing
178 local categories = characters.categories
179
180 directives.register("backend.pdf.nounicode", function(v)
181 tounicodestring = tounicode
182 nounicode = false
183 if type(v) == "string" then
184 local t = utfsplit(v)
185 local n = #t
186 if n > 0 then
187 for i=1,n do
188 t[i] = tounicode(t[i])
189 end
190 tounicodestring = function(u)
191 if is_nothing[categories[u]] then
192 return tounicode(u)
193 else
194 return t[random(1,n)]
195 end
196 end
197 nounicode = v
198 end
199 end
200 end)
201
202 tounicodedictionary = function(details,indices,maxindex,name,wide)
203 local mapping = { }
204 local length = 0
205 if maxindex > 0 then
206 local f_mapping = wide and f_mapping_4 or f_mapping_2
207 for index=1,maxindex do
208 local data = indices[index]
209 if data then
210 length = length + 1
211 mapping[length] = f_mapping(index,tounicodestring(data.unicode))
212 end
213 end
214 end
215 local name = gsub(name,"%+","-")
216 local first = wide and "0000" or "00"
217 local last = wide and "FFFF" or "FF"
218 local blob = format(tounicode_template,name,name,name,name,name,first,last,length,concat(mapping,"\n"))
219 return blob
220 end
221
222 end
223
224 widtharray = function(details,indices,maxindex,units,correction)
225 local widths = pdfarray()
226 if maxindex > 0 then
227 local length = 0
228 local factor = 10000 / (units * (correction or 1))
229 local lastindex = -1
230 local sublist = nil
231 for index=1,maxindex do
232 local data = indices[index]
233 if data then
234 local width = data.width
235 if width then
236 if correction then
237
238 width = round(width * factor) / 10
239 end
240 else
241 width = 0
242 end
243 if index == lastindex + 1 then
244 sublist[#sublist+1] = width
245 else
246 if sublist then
247 length = length + 1
248 widths[length] = sublist
249 end
250 sublist = pdfarray { width }
251 length = length + 1
252 widths[length] = index
253 end
254 lastindex = index
255 end
256 end
257 length = length + 1
258 widths[length] = sublist
259 end
260 return widths
261 end
262
263 local includebackmap = true
264 local nofregistries = 0
265
266 directives.register("backend.pdf.includebackmap", function(v)
267
268 includebackmap = v
269 end)
270
271 function lpdf.noffontregistries()
272 return nofregistries > 0 and nofregistries or nil
273 end
274
275 lmtxregistry = function(details,include,blobs,minindex,maxindex)
276 if includebackmap and maxindex > 0 then
277 local backmap = pdfarray()
278 local streamhash = details.hash
279 local properties = details.properties
280 local filename = file.basename(properties.filename)
281 local filetype = file.suffix(filename)
282 local fontname = properties.name or file.nameonly(filename)
283 local instance = properties.instance or ""
284 if filename and (filetype == "otf" or filetype == "ttf") then
285 local streams = details.streams
286 if streams then
287 local fontheader = streams.fontheader
288 if fontheader then
289 local streams = streams.streams
290 if streams then
291 local subfont = details.properties.subfont or 1
292 local length = 0
293 local lastindex = -1
294 local sublist = nil
295 for index=minindex,maxindex do
296 local idx = include[index]
297 if idx then
298 local data = blobs[index]
299 if index == lastindex + 1 then
300 sublist[#sublist+1] = idx
301 else
302 if sublist then
303 length = length + 1
304 backmap[length] = sublist
305 end
306 sublist = pdfarray { idx }
307 length = length + 1
308 backmap[length] = index
309 end
310 lastindex = index
311 end
312 end
313 length = length + 1
314 backmap[length] = sublist
315 if instance == "" then
316 instance = nil
317 elseif find(instance,"=") then
318 local i = fonts.handlers.otf.readers.helpers.axistofactors(instance)
319 instance = pdfdictionary(i)
320 end
321 if subfont == 1 then
322 subfont = nil
323 end
324 nofregistries = nofregistries + 1
325 return pdfreference(pdfflushobject(pdfdictionary {
326 IndexMap = pdfreference(pdfflushobject(backmap)) or nil,
327 StreamHash = streamhash or nil,
328 FileName = filename,
329 FontName = fontname,
330 SubFont = subfont,
331 Instance = instance,
332 Version = fontheader.fontversion,
333 GlyphCount = #streams + (streams[0] and 1 or 0),
334 FontMode = fonts.mode(),
335 NoUnicode = nounicode and pdfunicode(nounicode) or nil,
336 } ))
337 end
338 end
339 end
340 end
341 end
342 end
343
344
345
346
347 collectindices = function(descriptions,indices,used,hash)
348 local minindex = 0xFFFF
349 local maxindex = 0
350 local reverse = { }
351 local copies = { }
352 local include = { }
353 local resolved = { }
354
355 setmetatable(used,nil)
356 for unicode, data in next, descriptions do
357 local index = data.index
358 reverse[index or unicode] = data
359 if used[index] and data.dupindex then
360 copies[index] = data.dupindex
361 end
362 end
363 for index, usedindex in next, indices do
364 if usedindex > maxindex then
365 maxindex = usedindex
366 end
367 if usedindex < minindex then
368 minindex = usedindex
369 end
370 include[usedindex] = copies[index] or index
371 resolved[usedindex] = reverse[index]
372 end
373 if minindex > maxindex then
374 minindex = maxindex
375 end
376 if trace_details then
377 report_fonts("embedded: hash %a, minindex %i, maxindex %i",hash,minindex,maxindex)
378 for k, v in sortedhash(include) do
379 local d = resolved[k]
380 if d and d.dupindex then
381 report_fonts(" 0x%04X : 0x%04X (duplicate 0x%05X)",k,v,d.index)
382 else
383 report_fonts(" 0x%04X : 0x%04X",k,v)
384 end
385 end
386 end
387 return resolved, include, minindex, maxindex
388 end
389
390 includecidset = false
391 forcecidset = false
392
393 function lpdf.setincludecidset(v)
394
395
396
397 includecidset = v
398 end
399
400 directives.register("backend.pdf.forcecidset",function(v)
401 forcecidset = v
402 end)
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 local function tocidset(indices,min,max,forcenotdef)
419 if not max then
420 max = count(indices)
421 end
422 local b = { }
423 local m = (max // 8) + 1
424 for i=0,m do
425 b[i] = 0
426 end
427 if forcenotdef then
428 b[0] = b[0] | (1 << 7)
429 end
430 for i in next, indices do
431 local bi = i // 8
432 local ni = i % 8
433 b[bi] = b[bi] | (1 << (7-ni))
434 end
435 return char(unpack(b,0,#b))
436 end
437
438 lpdf.tocidset = tocidset
439
440 tocidsetdictionary = function(indices,min,max)
441 if lpdf.majorversion() > 1 then
442 return nil
443 elseif includecidset or forcecidset then
444 local b = tocidset(indices,min,max,true)
445 return pdfreference(pdfflushstreamobject(b))
446 end
447 end
448
449
450
451
452
453
454
455 local prefixes = { }
456
457 subsetname = function(name)
458 local prefix
459 while true do
460 prefix = utfchar(random(65,90),random(65,90),random(65,90),random(65,90),random(65,90),random(65,90))
461 if not prefixes[prefix] then
462 prefixes[prefix] = true
463 break
464 end
465 end
466 return prefix .. "+" .. name
467 end
468
469end
470
471
472
473local mainwriters = { }
474
475do
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 local streams = utilities.streams
501 local openstring = streams.openstring
502 local readcardinal2 = streams.readcardinal2
503
504
505 local otfreaders = fonts.handlers.otf.readers
506
507 local function readcardinal4(f)
508 local a = readcardinal2(f)
509 local b = readcardinal2(f)
510 if a and b then
511 return a * 0x10000 + b
512 end
513 end
514
515
516
517 local tablereaders = { }
518 local tablewriters = { }
519 local tablecreators = { }
520 local tableloaders = { }
521
522 local openfontfile, closefontfile, makefontfile, makemetadata do
523
524 local details = {
525 details = true,
526 platformnames = true,
527 platformextras = true,
528 }
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547 local function checksum(data)
548 local s = openstring(data)
549 local n = 0
550 local d = #data
551 while true do
552 local a = readcardinal2(s)
553 local b = readcardinal2(s)
554 if b then
555 n = (n + a * 0x10000 + b) % 0x100000000
556 else
557 break
558 end
559 end
560 return n
561 end
562
563 openfontfile = function(details)
564 return {
565 offset = 0,
566 order = { },
567 used = { },
568 details = details,
569 streams = details.streams,
570 }
571 end
572
573 closefontfile = function(fontfile)
574 for k, v in next, fontfile do
575 fontfile[k] = nil
576 end
577 end
578
579 local metakeys = {
580 "uniqueid", "version",
581 "copyright", "license", "licenseurl",
582 "manufacturer", "vendorurl",
583 "family", "subfamily",
584 "typographicfamily", "typographicsubfamily",
585 "fullname", "postscriptname",
586 }
587
588 local template <const> = [[
589<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
590 <x:xmpmeta xmlns:x="adobe:ns:meta/">
591 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
592 <rdf:Description rdf:about="" xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/">
593
594%s
595
596 </rdf:Description>
597 </rdf:RDF>
598 </x:xmpmeta>
599<?xpacket end="w"?>]]
600
601 makemetadata = function(fontfile)
602 local names = fontfile.streams.names
603 local list = { }
604 local f_name = formatters["<pdfx:%s>%s</pdfx:%s>"]
605 for i=1,#metakeys do
606 local m = metakeys[i]
607 local n = names[m]
608 if n then
609 list[#list+1] = f_name(m,n,m)
610 end
611 end
612 return format(template,concat(list,"\n"))
613 end
614
615 makefontfile = function(fontfile)
616 local order = fontfile.order
617 local used = fontfile.used
618 local count = 0
619 for i=1,#order do
620 local tag = order[i]
621 local data = fontfile[tag]
622 if data and #data > 0 then
623 count = count + 1
624 else
625 fontfile[tag] = false
626 end
627 end
628 local offset = 12 + (count * 16)
629 local headof = 0
630 local list = {
631 ""
632 }
633 local i = 1
634 local k = 0
635 while i <= count do
636 i = i << 1
637 k = k + 1
638 end
639 local searchrange = i << 3
640 local entryselector = k - 1
641 local rangeshift = (count << 4) - (i << 3)
642 local index = {
643 tocardinal4(0x00010000),
644 tocardinal2(count),
645 tocardinal2(searchrange),
646 tocardinal2(entryselector),
647 tocardinal2(rangeshift),
648 }
649
650 local ni = #index
651 local nl = #list
652 for i=1,#order do
653 local tag = order[i]
654 local data = fontfile[tag]
655 if data then
656 local csum = checksum(data)
657 local dlength = #data
658 local length = ((dlength + 3) // 4) * 4
659 local padding = length - dlength
660 nl = nl + 1 ; list[nl] = data
661 for i=1,padding do
662 nl = nl + 1 ; list[nl] = "\0"
663 end
664 if #tag == 3 then
665 tag = tag .. " "
666 end
667 ni = ni + 1 ; index[ni] = tag
668 ni = ni + 1 ; index[ni] = tocardinal4(csum)
669 ni = ni + 1 ; index[ni] = tocardinal4(offset)
670 ni = ni + 1 ; index[ni] = tocardinal4(dlength)
671 used[i] = offset
672 if tag == "head" then
673 headof = offset
674 end
675 offset = offset + length
676 end
677 end
678 list[1] = concat(index)
679 local off = #list[1] + headof + 1 + 8
680 list = concat(list)
681 local csum = (0xB1B0AFBA - checksum(list)) % 0x100000000
682 list = sub(list,1,off-1) .. tocardinal4(csum) .. sub(list,off+4,#list)
683 return list
684 end
685
686 local function register(fontfile,name)
687 local u = fontfile.used
688 local o = fontfile.order
689 if not u[name] then
690 o[#o+1] = name
691 u[name] = true
692 end
693 end
694
695 local function create(fontfile,name)
696 local t = { }
697 fontfile[name] = t
698 return t
699 end
700
701 local function write(fontfile,name)
702 local t = fontfile[name]
703 if not t then
704 return
705 end
706 register(fontfile,name)
707 if type(t) == "table" then
708 if t[0] then
709 fontfile[name] = concat(t,"",0,#t)
710 elseif #t > 0 then
711 fontfile[name] = concat(t)
712 else
713 fontfile[name] = false
714 end
715 end
716 end
717
718 tablewriters.head = function(fontfile)
719 register(fontfile,"head")
720 local t = fontfile.streams.fontheader
721 fontfile.head = concat {
722 tocardinal4(t.version),
723 tocardinal4(t.fontversionnumber),
724 tocardinal4(t.checksum),
725 tocardinal4(t.magic),
726 tocardinal2(t.flags),
727 tocardinal2(t.units),
728 tocardinal8(t.created),
729 tocardinal8(t.modified),
730 tocardinal2(t.xmin),
731 tocardinal2(t.ymin),
732 tocardinal2(t.xmax),
733 tocardinal2(t.ymax),
734 tocardinal2(t.macstyle),
735 tocardinal2(t.smallpixels),
736 tocardinal2(t.directionhint),
737 tocardinal2(t.indextolocformat),
738 tocardinal2(t.glyphformat),
739 }
740 end
741
742 tablewriters.hhea = function(fontfile)
743 register(fontfile,"hhea")
744 local t = fontfile.streams.horizontalheader
745 local n = t and fontfile.nofglyphs or 0
746 fontfile.hhea = concat {
747 tocardinal4(t.version),
748 tocardinal2(t.ascender),
749 tocardinal2(t.descender),
750 tocardinal2(t.linegap),
751 tocardinal2(t.maxadvancewidth),
752 tocardinal2(t.minleftsidebearing),
753 tocardinal2(t.minrightsidebearing),
754 tocardinal2(t.maxextent),
755 tocardinal2(t.caretsloperise),
756 tocardinal2(t.caretsloperun),
757 tocardinal2(t.caretoffset),
758 tocardinal2(t.reserved_1),
759 tocardinal2(t.reserved_2),
760 tocardinal2(t.reserved_3),
761 tocardinal2(t.reserved_4),
762 tocardinal2(t.metricdataformat),
763 tocardinal2(n)
764 }
765 end
766
767 tablewriters.vhea = function(fontfile)
768 local t = fontfile.streams.verticalheader
769 local n = t and fontfile.nofglyphs or 0
770 register(fontfile,"vhea")
771 fontfile.vhea = concat {
772 tocardinal4(t.version),
773 tocardinal2(t.ascender),
774 tocardinal2(t.descender),
775 tocardinal2(t.linegap),
776 tocardinal2(t.maxadvanceheight),
777 tocardinal2(t.mintopsidebearing),
778 tocardinal2(t.minbottomsidebearing),
779 tocardinal2(t.maxextent),
780 tocardinal2(t.caretsloperise),
781 tocardinal2(t.caretsloperun),
782 tocardinal2(t.caretoffset),
783 tocardinal2(t.reserved_1),
784 tocardinal2(t.reserved_2),
785 tocardinal2(t.reserved_3),
786 tocardinal2(t.reserved_4),
787 tocardinal2(t.metricdataformat),
788 tocardinal2(n)
789 }
790 end
791
792 tablewriters.maxp = function(fontfile)
793 register(fontfile,"maxp")
794 local t = fontfile.streams.maximumprofile
795 local n = fontfile.nofglyphs
796
797
798
799 local version = tocardinal4(0x00010000)
800 local count = tocardinal2(n)
801 if t.points then
802 fontfile.maxp = concat {
803 version, count,
804 tocardinal2(t.points),
805 tocardinal2(t.contours),
806 tocardinal2(t.compositepoints),
807 tocardinal2(t.compositecontours),
808 tocardinal2(t.zones),
809 tocardinal2(t.twilightpoints),
810 tocardinal2(t.storage),
811 tocardinal2(t.functiondefs),
812 tocardinal2(t.instructiondefs),
813 tocardinal2(t.stackelements),
814 tocardinal2(t.sizeofinstructions),
815 tocardinal2(t.componentelements),
816 tocardinal2(t.componentdepth),
817 }
818 else
819 local zero = tocardinal2(0)
820 fontfile.maxp = concat {
821 version, count, zero, zero, zero, zero, zero,
822 zero, zero, zero, zero, zero, zero, zero, zero,
823 }
824 end
825 end
826
827 tablecreators.loca = function(fontfile) return create(fontfile,"loca") end
828 tablewriters .loca = function(fontfile) return write (fontfile,"loca") end
829
830 tablecreators.glyf = function(fontfile) return create(fontfile,"glyf") end
831 tablewriters .glyf = function(fontfile) return write (fontfile,"glyf") end
832
833 tablecreators.hmtx = function(fontfile) return create(fontfile,"hmtx") end
834 tablewriters .hmtx = function(fontfile) return write (fontfile,"hmtx") end
835
836 tablecreators.vmtx = function(fontfile) return create(fontfile,"vmtx") end
837 tablewriters .vmtx = function(fontfile) return write (fontfile,"vmtx") end
838
839 tableloaders .cmap = function(fontfile) return read (fontfile,"cmap") end
840 tablewriters .cmap = function(fontfile) return write (fontfile,"cmap") end
841
842 tableloaders .name = function(fontfile) return read (fontfile,"name") end
843 tablewriters .name = function(fontfile) return write (fontfile,"name") end
844
845 tableloaders .post = function(fontfile) return read (fontfile,"post") end
846 tablewriters .post = function(fontfile) return write (fontfile,"post") end
847
848 end
849
850 local sharedmetadata = setmetatableindex(function(t,fontmeta)
851 local v = fontmeta or false
852 if fontmeta then
853 v = pdfreference(pdfflushstreamobject(fontmeta))
854 end
855 t[fontmeta] = v
856 return v
857 end)
858
859 mainwriters["truetype"] = function(details)
860
861 local fontfile = openfontfile(details)
862 local basefontname = details.basefontname
863 local streams = details.streams
864 local blobs = streams.streams
865 local fontheader = streams.fontheader
866 local horizontalheader = streams.horizontalheader
867 local verticalheader = streams.verticalheader
868 local maximumprofile = streams.maximumprofile
869 local names = streams.names
870 local descriptions = details.rawdata.descriptions
871 local metadata = details.rawdata.metadata
872 local indices = details.indices
873 local metabbox = { fontheader.xmin, fontheader.ymin, fontheader.xmax, fontheader.ymax }
874 local indices,
875 include,
876 minindex,
877 maxindex = collectindices(descriptions,indices,details.used,details.hash)
878 local glyphstreams = tablecreators.glyf(fontfile)
879 local locations = tablecreators.loca(fontfile)
880 local horizontals = tablecreators.hmtx(fontfile)
881 local verticals = tablecreators.vmtx(fontfile)
882
883 local zero2 = tocardinal2(0)
884 local zero4 = tocardinal4(0)
885
886 local horizontal = horizontalheader.nofmetrics > 0
887 local vertical = verticalheader.nofmetrics > 0
888
889 local streamoffset = 0
890 local lastoffset = zero4
891 local g, h, v = 0, 0, 0
892
893
894
895 if minindex > 0 then
896 local blob = blobs[0]
897 if blob and #blob > 0 then
898 locations[0] = lastoffset
899 g = g + 1 ; glyphstreams[g] = blob
900 h = h + 1 ; horizontals [h] = zero4
901 if vertical then
902 v = v + 1 ; verticals[v] = zero4
903 end
904 streamoffset = streamoffset + #blob
905 lastoffset = tocardinal4(streamoffset)
906 else
907 report_fonts("missing .notdef in font %a",basefontname)
908 end
909
910 for index=1,minindex-1 do
911 locations[index] = lastoffset
912 h = h + 1 ; horizontals[h] = zero4
913 if vertical then
914 v = v + 1 ; verticals[v] = zero4
915 end
916 end
917 end
918
919 for index=minindex,maxindex do
920 locations[index] = lastoffset
921 local data = indices[index]
922 if data then
923 local blob = blobs[include[index]]
924 if blob and #blob > 0 then
925 g = g + 1 ; glyphstreams[g] = blob
926 h = h + 1 ; horizontals [h] = tocardinal2(round(data.width or 0))
927 h = h + 1 ; horizontals [h] = tocardinal2(round(data.boundingbox[1]))
928 if vertical then
929 v = v + 1 ; verticals[v] = tocardinal2(round(data.height or 0))
930 v = v + 1 ; verticals[v] = tocardinal2(round(data.boundingbox[3]))
931 end
932 streamoffset = streamoffset + #blob
933 lastoffset = tocardinal4(streamoffset)
934 else
935 h = h + 1 ; horizontals[h] = zero4
936 if vertical then
937 v = v + 1 ; verticals[v] = zero4
938 end
939 report_fonts("missing blob for index %i in font %a",index,basefontname)
940 end
941 else
942 h = h + 1 ; horizontals[h] = zero4
943 if vertical then
944 v = v + 1 ; verticals[v] = zero4
945 end
946 end
947 end
948
949 locations[maxindex+1] = lastoffset
950
951 local nofglyphs = maxindex + 1
952
953 fontheader.checksum = 0
954 fontheader.indextolocformat = 1
955 maximumprofile.nofglyphs = nofglyphs
956
957 fontfile.format = "tff"
958 fontfile.basefontname = basefontname
959 fontfile.nofglyphs = nofglyphs
960
961 tablewriters.head(fontfile)
962 tablewriters.hhea(fontfile)
963 if vertical then
964 tablewriters.vhea(fontfile)
965 end
966 tablewriters.maxp(fontfile)
967
968 tablewriters.loca(fontfile)
969 tablewriters.glyf(fontfile)
970
971 tablewriters.hmtx(fontfile)
972 if vertical then
973 tablewriters.vmtx(fontfile)
974 end
975
976 local fontdata = makefontfile(fontfile)
977 local fontmeta = makemetadata(fontfile)
978
979 fontfile = closefontfile(fontfile)
980
981 local units = metadata.units
982 local basefont = pdfconstant(basefontname)
983 local widths = widtharray(details,indices,maxindex,units,1)
984 local object = details.objectnumber
985 local tounicode = tounicodedictionary(details,indices,maxindex,basefontname,true)
986 local tocidset = tocidsetdictionary(indices,minindex,maxindex)
987 local metabbox = metadata.boundingbox or { 0, 0, 0, 0 }
988 local fontbbox = pdfarray { unpack(metabbox) }
989 local ascender = metadata.ascender
990 local descender = metadata.descender
991
992 local capheight = metadata.capheight or fontbbox[4]
993 local stemv = metadata.weightclass
994 local italicangle = metadata.italicangle
995 local xheight = metadata.xheight or fontbbox[4]
996
997 if stemv then
998 stemv = (stemv/65)^2 + 50
999 end
1000
1001 local function scale(n)
1002 if n then
1003 return round((n) * 10000 / units) / 10
1004 else
1005 return 0
1006 end
1007 end
1008
1009 local registry = lmtxregistry(details,include,blobs,minindex,maxindex)
1010 local reserved = pdfreserveobject()
1011 local child = pdfdictionary {
1012 Type = pdfconstant("Font"),
1013 Subtype = pdfconstant("CIDFontType2"),
1014 BaseFont = basefont,
1015 FontDescriptor = pdfreference(reserved),
1016 W = pdfreference(pdfflushobject(widths)),
1017 LMTX_Registry = registry or nil,
1018 CIDToGIDMap = pdfconstant("Identity"),
1019 CIDSystemInfo = pdfdictionary {
1020 Registry = pdfstring("Adobe"),
1021 Ordering = pdfstring("Identity"),
1022 Supplement = 0,
1023 }
1024 }
1025 local descendants = pdfarray {
1026 pdfreference(pdfflushobject(child)),
1027 }
1028 local descriptor = pdfdictionary {
1029 Type = pdfconstant("FontDescriptor"),
1030 FontName = basefont,
1031 Flags = 4,
1032 FontBBox = fontbbox,
1033
1034 Ascent = scale(ascender),
1035 Descent = scale(descender),
1036
1037 ItalicAngle = round(italicangle or 0),
1038 CapHeight = scale(capheight),
1039 StemV = scale(stemv),
1040 XHeight = scale(xheight),
1041 FontFile2 = pdfreference(pdfflushstreamobject(fontdata)),
1042 CIDSet = tocidset,
1043 LMTX_Metadata = sharedmetadata[fontmeta] or nil,
1044 }
1045 local parent = pdfdictionary {
1046 Type = pdfconstant("Font"),
1047 Subtype = pdfconstant("Type0"),
1048 Encoding = pdfconstant(details.properties.writingmode == "vertical" and "Identity-V" or "Identity-H"),
1049 BaseFont = basefont,
1050 DescendantFonts = descendants,
1051 ToUnicode = pdfreference(pdfflushstreamobject(tounicode)),
1052 }
1053 pdfflushobject(reserved,descriptor)
1054 pdfflushobject(object,parent)
1055
1056
1057
1058
1059
1060
1061
1062
1063 end
1064
1065 do
1066
1067
1068 local details = {
1069 details = true,
1070 platformnames = true,
1071 platformextras = true,
1072 }
1073
1074 tablecreators.cff = function(fontfile)
1075 fontfile.charstrings = { }
1076 fontfile.charmappings = { }
1077 fontfile.cffstrings = { }
1078 fontfile.cffhash = { }
1079 return fontfile.charstrings , fontfile.charmappings
1080 end
1081
1082 local todictnumber, todictreal, todictinteger, todictoffset do
1083
1084 local maxnum = 0x7FFFFFFF
1085 local minnum = - 0x7FFFFFFF - 1
1086 local epsilon = 1.0e-5
1087
1088 local int2tag = "\28"
1089 local int4tag = "\29"
1090 local realtag = "\30"
1091
1092 todictinteger = function(n)
1093 if not n then
1094 return char(139 & 0xFF)
1095 elseif n >= -107 and n <= 107 then
1096 return char((n + 139) & 0xFF)
1097 elseif n >= 108 and n <= 1131 then
1098 n = 0xF700 + n - 108
1099 return char((n >> 8) & 0xFF, n & 0xFF)
1100 elseif n >= -1131 and n <= -108 then
1101 n = 0xFB00 - n - 108
1102 return char((n >> 8) & 0xFF, n & 0xFF)
1103 elseif n >= -32768 and n <= 32767 then
1104
1105 return char(28,(n>>8)&0xFF,(n>>0)&0xFF)
1106 else
1107
1108 return char(29,(n>>24)&0xFF,(n>>16)&0xFF,(n>>8)&0xFF,(n>>0)&0xFF)
1109 end
1110 end
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130 todictoffset = function(n)
1131 return int4tag .. tointeger4(n)
1132 end
1133
1134 local z = byte("0")
1135 local dp = 10
1136 local ep = 11
1137 local em = 12
1138 local mn = 14
1139 local es = 15
1140
1141 local fg = formatters["%g"]
1142
1143 todictreal = function(v)
1144 local s = fg(v)
1145 local t = { [0] = realtag }
1146 local n = 0
1147 local e = false
1148 for s in gmatch(s,".") do
1149 if s == "e" or s == "E" then
1150 e = true
1151 elseif s == "+" then
1152
1153 elseif s == "-" then
1154 n = n + 1
1155 if e then
1156 t[n] = em
1157 e = false
1158 else
1159 t[n] = mn
1160 end
1161 else
1162 if e then
1163 n = n + 1
1164 t[n] = ep
1165 e = false
1166 end
1167 n = n + 1
1168 if s == "." then
1169 t[n] = dp
1170 else
1171 t[n] = byte(s) - z
1172 end
1173 end
1174 end
1175 n = n + 1
1176 t[n] = es
1177 if (n % 2) ~= 0 then
1178 n = n + 1
1179 t[n] = es
1180 end
1181 local j = 0
1182 for i=1,n,2 do
1183 j = j + 1
1184 t[j] = char(t[i]*0x10+t[i+1])
1185 end
1186 t = concat(t,"",0,j)
1187 return t
1188 end
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257 todictnumber = function(n)
1258 if not n or n == 0 then
1259 return todictinteger(0)
1260 elseif (n > maxnum or n < minnum or (abs(n - round(n)) > epsilon)) then
1261 return todictreal(n)
1262 else
1263 return todictinteger(n)
1264 end
1265 end
1266
1267 end
1268
1269 local todictkey = char
1270
1271 local function todictstring(fontfile,value)
1272 if not value then
1273 value = ""
1274 end
1275 local s = fontfile.cffstrings
1276 local h = fontfile.cffhash
1277 local n = h[value]
1278 if not n then
1279 n = #s + 1
1280 s[n] = value
1281 h[value] = n
1282 end
1283 return todictinteger(390+n)
1284 end
1285
1286 local function todictboolean(b)
1287 return todictinteger(b and 1 or 0)
1288 end
1289
1290 local function todictdeltas(t)
1291 local r = { }
1292 for i=1,#t do
1293
1294 r[i] = todictnumber(t[i]+(t[i-1] or 0))
1295 end
1296 return concat(r)
1297 end
1298
1299 local function todictarray(t)
1300 local r = { }
1301 for i=1,#t do
1302 r[i] = todictnumber(t[i])
1303 end
1304 return concat(r)
1305 end
1306
1307 local function writestring(target,source,offset,what)
1308 target[#target+1] = source
1309
1310 return offset + #source
1311 end
1312
1313 local function writetable(target,source,offset,what)
1314 source = concat(source)
1315 target[#target+1] = source
1316
1317 return offset + #source
1318 end
1319
1320 local function writeindex(target,source,offset,what)
1321 local n = #source
1322 local t = #target
1323 t = t + 1 ; target[t] = tocardinal2(n)
1324 if n > 0 then
1325 local data = concat(source)
1326 local size = #data
1327 local offsetsize, tocardinal
1328 if size < 0xFF then
1329 offsetsize, tocardinal = 1, tocardinal1
1330 elseif size < 0xFFFF then
1331 offsetsize, tocardinal = 2, tocardinal2
1332 elseif size < 0xFFFFFF then
1333 offsetsize, tocardinal = 3, tocardinal3
1334 elseif size < 0xFFFFFFFF then
1335 offsetsize, tocardinal = 4, tocardinal4
1336 end
1337
1338 offset = offset + 2 + 1 + (n + 1) * offsetsize + size
1339
1340 t = t + 1 ; target[t] = tocardinal1(offsetsize)
1341
1342 local offset = 1
1343 t = t + 1 ; target[t] = tocardinal(offset)
1344 for i=1,n do
1345 offset = offset + #source[i]
1346 t = t + 1 ; target[t] = tocardinal(offset)
1347 end
1348 t = t + 1 ; target[t] = data
1349 else
1350
1351 offset = offset + 2
1352 end
1353
1354 return offset
1355 end
1356
1357 tablewriters.cff = function(fontfile)
1358
1359 local streams = fontfile.streams
1360 local cffinfo = streams.cffinfo or { }
1361 local names = streams.names or { }
1362 local fontheader = streams.fontheader or { }
1363 local basefontname = fontfile.basefontname
1364
1365 local offset = 0
1366 local dictof = 0
1367 local target = { }
1368
1369 local charstrings = fontfile.charstrings
1370 local nofglyphs = #charstrings + 1
1371
1372 local fontbbox = fontfile.fontbbox
1373 local defaultwidth = cffinfo.defaultwidth or 0
1374 local nominalwidth = cffinfo.nominalwidth or 0
1375 local bluevalues = cffinfo.bluevalues
1376 local otherblues = cffinfo.otherblues
1377 local familyblues = cffinfo.familyblues
1378 local familyotherblues = cffinfo.familyotherblues
1379 local bluescale = cffinfo.bluescale
1380 local blueshift = cffinfo.blueshift
1381 local bluefuzz = cffinfo.bluefuzz
1382 local stdhw = cffinfo.stdhw
1383 local stdvw = cffinfo.stdvw
1384 local stemsnaph = cffinfo.stemsnaph
1385 local stemsnapv = cffinfo.stemsnapv
1386
1387 if defaultwidth == 0 then defaultwidth = nil end
1388 if nomimalwidth == 0 then nominalwidth = nil end
1389 if bluevalues then bluevalues = todictarray(bluevalues) end
1390 if otherblues then otherblues = todictarray(otherblues) end
1391 if familyblues then familyblues = todictarray(familyblues) end
1392 if familyotherblues then familyotherblues = todictarray(familyotherblues) end
1393 if bluescale then bluescale = todictnumber(bluescale) end
1394 if blueshift then blueshift = todictnumber(blueshift) end
1395 if bluefuzz then bluefuzz = todictnumber(bluefuzz) end
1396 if stemsnaph then stemsnaph = todictarray(stemsnaph) end
1397 if stemsnapv then stemsnapv = todictarray(stemsnapv) end
1398 if stdhw then stdhw = todictnumber(stdhw) end
1399 if stdvw then stdvw = todictnumber(stdvw) end
1400
1401 local fontversion = todictstring(fontfile,fontheader.fontversion or "uknown version")
1402 local familyname = todictstring(fontfile,cffinfo.familyname or names.family or basefontname)
1403 local fullname = todictstring(fontfile,cffinfo.fullname or basefontname)
1404 local weight = todictstring(fontfile,cffinfo.weight or "Normal")
1405 local fontbbox = todictarray(fontbbox)
1406 local strokewidth = todictnumber(cffinfo.strokewidth)
1407 local monospaced = todictboolean(cffinfo.monospaced)
1408 local italicangle = todictnumber(cffinfo.italicangle)
1409 local underlineposition = todictnumber(cffinfo.underlineposition)
1410 local underlinethickness = todictnumber(cffinfo.underlinethickness)
1411 local charstringtype = todictnumber(2)
1412
1413 local ros = todictstring(fontfile,"Adobe")
1414 .. todictstring(fontfile,"Identity")
1415 .. todictnumber(0)
1416 local cidcount = todictnumber(fontfile.nofglyphs)
1417 local fontname = todictstring(fontfile,basefontname)
1418 local fdarrayoffset = todictoffset(0)
1419 local fdselectoffset = todictoffset(0)
1420 local charstringoffset = todictoffset(0)
1421 local charsetoffset = todictoffset(0)
1422 local privateoffset = todictoffset(0)
1423
1424 local defaultwidthx = todictnumber(defaultwidth)
1425 local nominalwidthx = todictnumber(nominalwidth)
1426
1427 local private = ""
1428 .. (bluevalues and (bluevalues .. todictkey( 6)) or "")
1429 .. (otherblues and (otherblues .. todictkey( 7)) or "")
1430 .. (familyblues and (familyblues .. todictkey( 8)) or "")
1431 .. (familyotherblues and (familyotherblues .. todictkey( 9)) or "")
1432 .. (bluescale and (bluescale .. todictkey(12, 9)) or "")
1433 .. (blueshift and (blueshift .. todictkey(12,10)) or "")
1434 .. (bluefuzz and (bluefuzz .. todictkey(12,11)) or "")
1435 .. (stdhw and (stdhw .. todictkey(10)) or "")
1436 .. (stdvw and (stdvw .. todictkey(11)) or "")
1437 .. (stemsnaph and (stemsnaph .. todictkey(12,12)) or "")
1438 .. (stemsnapv and (stemsnapv .. todictkey(12,13)) or "")
1439 .. (defaultwidthx and (defaultwidthx .. todictkey(20)) or "")
1440 .. (nominalwidthx and (nominalwidthx .. todictkey(21)) or "")
1441 local privatesize = todictnumber(#private)
1442 local privatespec = privatesize .. privateoffset
1443
1444
1445
1446 local header =
1447 tocardinal1(1)
1448 .. tocardinal1(0)
1449 .. tocardinal1(4)
1450 .. tocardinal1(4)
1451
1452 offset = writestring(target,header,offset,"header")
1453
1454
1455
1456 local names = {
1457 basefontname,
1458 }
1459
1460 offset = writeindex(target,names,offset,"names")
1461
1462
1463
1464 local topvars =
1465 charstringoffset .. todictkey(17)
1466 .. charsetoffset .. todictkey(15)
1467 .. fdarrayoffset .. todictkey(12,36)
1468 .. fdselectoffset .. todictkey(12,37)
1469 .. privatespec .. todictkey(18)
1470
1471 local topdict = {
1472 ros .. todictkey(12,30)
1473 .. cidcount .. todictkey(12,34)
1474 .. familyname .. todictkey( 3)
1475 .. fullname .. todictkey( 2)
1476 .. weight .. todictkey( 4)
1477 .. fontbbox .. todictkey( 5)
1478 .. monospaced .. todictkey(12, 1)
1479 .. italicangle .. todictkey(12, 2)
1480 .. underlineposition .. todictkey(12, 3)
1481 .. underlinethickness .. todictkey(12, 4)
1482 .. charstringtype .. todictkey(12, 6)
1483
1484 .. strokewidth .. todictkey(12, 8)
1485 .. topvars
1486 }
1487
1488 offset = writeindex(target,topdict,offset,"topdict")
1489 dictof = #target
1490
1491
1492
1493 offset = writeindex(target,fontfile.cffstrings,offset,"strings")
1494
1495
1496
1497 offset = writeindex(target,{},offset,"globals")
1498
1499
1500
1501
1502
1503
1504
1505 charsetoffset = todictoffset(offset)
1506 offset = writetable(target,fontfile.charmappings,offset,"charsets")
1507
1508
1509
1510
1511
1512
1513
1514 local fdselect =
1515 tocardinal1(3)
1516 .. tocardinal2(1)
1517
1518 .. tocardinal2(0)
1519 .. tocardinal1(0)
1520
1521
1522 .. tocardinal2(fontfile.sparsemax)
1523
1524 fdselectoffset = todictoffset(offset)
1525 offset = writestring(target,fdselect,offset,"fdselect")
1526
1527
1528
1529 charstringoffset = todictoffset(offset)
1530 offset = writeindex(target,charstrings,offset,"charstrings")
1531
1532
1533
1534
1535
1536
1537
1538 privateoffset = todictoffset(offset)
1539 privatespec = privatesize .. privateoffset
1540 offset = writestring(target,private,offset,"private")
1541
1542 local fdarray = {
1543 fontname .. todictkey(12,38)
1544 .. privatespec .. todictkey(18)
1545 }
1546 fdarrayoffset = todictoffset(offset)
1547 offset = writeindex(target,fdarray,offset,"fdarray")
1548
1549 topdict = target[dictof]
1550 topdict = sub(topdict,1,#topdict-#topvars)
1551 topvars =
1552 charstringoffset .. todictkey(17)
1553 .. charsetoffset .. todictkey(15)
1554 .. fdarrayoffset .. todictkey(12,36)
1555 .. fdselectoffset .. todictkey(12,37)
1556 .. privatespec .. todictkey(18)
1557 target[dictof] = topdict .. topvars
1558
1559 target = concat(target)
1560
1561
1562
1563
1564
1565
1566 return target
1567 end
1568 end
1569
1570
1571
1572 mainwriters["opentype"] = function(details)
1573
1574 local fontfile = openfontfile(details)
1575 local basefontname = details.basefontname
1576 local streams = details.streams
1577 local blobs = streams.streams
1578 local fontheader = streams.fontheader
1579 local maximumprofile = streams.maximumprofile
1580 local names = streams.names
1581 local descriptions = details.rawdata.descriptions
1582 local metadata = details.rawdata.metadata
1583 local indices = details.indices
1584 local used = details.used
1585 local usedfonts = details.usedfonts
1586 local metabbox = { fontheader.xmin, fontheader.ymin, fontheader.xmax, fontheader.ymax }
1587 local correction = 1
1588 if not descriptions or not next(descriptions) then
1589
1590
1591
1592
1593
1594 if true then
1595 descriptions = { }
1596 setmetatable(indices,nil)
1597 setmetatable(used,nil)
1598 for u in next, usedfonts do
1599 local param = fonts.hashes.parameters[u]
1600 local chars = fonts.hashes.characters[u]
1601 local units = 1000
1602 correction = param.size / 1000
1603
1604 local factor = 1000 / (units * correction)
1605 if false then
1606 for k, v in sortedhash(chars) do
1607 if descriptions[k] then
1608 local w1 = descriptions[k].width
1609 local w2 = round((v.advance or v.width or 0) * factor)
1610 if w1 ~= w2 then
1611 local w = v.advance or v.width or 0
1612
1613
1614
1615
1616
1617 end
1618 else
1619 descriptions[k] = {
1620 index = v.index,
1621 width = round((v.advance or v.width or 0) * factor),
1622 unicode = v.unicode,
1623 }
1624 end
1625 end
1626 else
1627 for k, v in next, chars do
1628 if descriptions[k] then
1629
1630 else
1631 local index = v.index
1632 if indices[index] or used[index] then
1633 descriptions[k] = {
1634 index = index,
1635 width = round((v.advance or v.width or 0) * factor),
1636 unicode = v.unicode,
1637 }
1638 end
1639 end
1640 end
1641 end
1642 end
1643 correction = false
1644 else
1645
1646
1647 descriptions = details.fontdata.characters
1648 correction = details.fontdata.parameters.size / 1000
1649 correction = correction * bpfactor / ptfactor
1650 end
1651 metadata = { }
1652 end
1653
1654 local indices,
1655 include,
1656 minindex,
1657 maxindex = collectindices(descriptions,indices,used,details.hash)
1658 local streamoffset = 0
1659 local glyphstreams,
1660 charmappings = tablecreators.cff(fontfile)
1661
1662 local zero2 = tocardinal2(0)
1663 local zero4 = tocardinal4(0)
1664
1665
1666
1667 local blob = blobs[0] or "\14"
1668 local sparsemax = 1
1669 local lastoffset = zero4
1670 glyphstreams[sparsemax] = blob
1671 charmappings[sparsemax] = tocardinal1(0)
1672 streamoffset = streamoffset + #blob
1673 lastoffset = tocardinal4(streamoffset)
1674 if minindex == 0 then
1675 minindex = 1
1676 end
1677
1678 for index=minindex,maxindex do
1679 local idx = include[index]
1680 if idx then
1681 local blob = blobs[idx]
1682 if not blob then
1683 blob = "\14"
1684 end
1685 sparsemax = sparsemax + 1
1686 glyphstreams[sparsemax] = blob
1687 charmappings[sparsemax] = tocardinal2(index)
1688 streamoffset = streamoffset + #blob
1689 lastoffset = tocardinal4(streamoffset)
1690 end
1691 end
1692
1693 fontfile.nofglyphs = maxindex + 1
1694 fontfile.sparsemax = sparsemax
1695 fontfile.format = "cff"
1696 fontfile.basefontname = basefontname
1697 fontfile.fontbbox = metabbox
1698
1699 local fontdata = tablewriters.cff(fontfile)
1700 local fontmeta = makemetadata(fontfile)
1701
1702 fontfile = closefontfile(fontfile)
1703
1704 local units = fontheader.units or metadata.units
1705 if not units or units ~= 1000 then
1706
1707
1708 report_fonts("width units in %a are %i, forcing 1000 instead",basefontname,units)
1709 units = 1000
1710 end
1711
1712 local basefont = pdfconstant(basefontname)
1713 local widths = widtharray(details,indices,maxindex,units,correction)
1714 local object = details.objectnumber
1715 local tounicode = tounicodedictionary(details,indices,maxindex,basefontname,true)
1716 local tocidset = tocidsetdictionary(indices,minindex,maxindex)
1717 local fontbbox = pdfarray { unpack(metabbox) }
1718 local ascender = metadata.ascender or 0
1719 local descender = metadata.descender or 0
1720
1721 local capheight = metadata.capheight or fontbbox[4]
1722 local stemv = metadata.weightclass
1723 local italicangle = metadata.italicangle
1724 local xheight = metadata.xheight or fontbbox[4]
1725 if stemv then
1726 stemv = (stemv/65)^2 + 50
1727 else
1728
1729 end
1730
1731 local function scale(n)
1732 if n then
1733 return round((n) * 10000 / units) / 10
1734 else
1735 return 0
1736 end
1737 end
1738
1739 local registry = lmtxregistry(details,include,blobs,minindex,maxindex)
1740 local reserved = pdfreserveobject()
1741 local child = pdfdictionary {
1742 Type = pdfconstant("Font"),
1743 Subtype = pdfconstant("CIDFontType0"),
1744 BaseFont = basefont,
1745 FontDescriptor = pdfreference(reserved),
1746 W = pdfreference(pdfflushobject(widths)),
1747 LMTX_Registry = registry or nil,
1748 CIDSystemInfo = pdfdictionary {
1749 Registry = pdfstring("Adobe"),
1750 Ordering = pdfstring("Identity"),
1751 Supplement = 0,
1752 }
1753 }
1754 local descendants = pdfarray {
1755 pdfreference(pdfflushobject(child)),
1756 }
1757 local fontstream = pdfdictionary {
1758 Subtype = pdfconstant("CIDFontType0C"),
1759 }
1760 local descriptor = pdfdictionary {
1761 Type = pdfconstant("FontDescriptor"),
1762 FontName = basefont,
1763 Flags = 4,
1764 FontBBox = fontbbox,
1765
1766 Ascent = scale(ascender),
1767 Descent = scale(descender),
1768
1769 ItalicAngle = round(italicangle or 0),
1770 CapHeight = scale(capheight),
1771 StemV = scale(stemv),
1772 XHeight = scale(xheight),
1773 CIDSet = tocidset,
1774 FontFile3 = pdfreference(pdfflushstreamobject(fontdata,fontstream())),
1775 LMTX_Metadata = sharedmetadata[fontmeta] or nil,
1776 }
1777 local parent = pdfdictionary {
1778 Type = pdfconstant("Font"),
1779 Subtype = pdfconstant("Type0"),
1780 Encoding = pdfconstant(details.properties.writingmode == "vertical" and "Identity-V" or "Identity-H"),
1781 BaseFont = basefont,
1782 DescendantFonts = descendants,
1783 ToUnicode = pdfreference(pdfflushstreamobject(tounicode)),
1784 }
1785 pdfflushobject(reserved,descriptor)
1786 pdfflushobject(object,parent)
1787 end
1788
1789 mainwriters["type1"] = function(details)
1790
1791
1792
1793
1794 local s = details.streams
1795 local m = details.rawdata.metadata
1796 if m then
1797 local h = s.horizontalheader
1798 local c = s.cffinfo
1799 local n = s.names
1800 h.ascender = m.ascender or h.ascender
1801 h.descender = m.descender or h.descender
1802 n.copyright = m.copyright or n.copyright
1803 n.family = m.familyname or n.familyname
1804 n.fullname = m.fullname or n.fullname
1805 n.fontname = m.fontname or n.fontname
1806 n.subfamily = m.subfamilyname or n.subfamilyname
1807 n.version = m.version or n.version
1808 setmetatableindex(h,m)
1809 setmetatableindex(c,m)
1810 setmetatableindex(n,m)
1811 end
1812 mainwriters["opentype"](details)
1813 end
1814
1815 do
1816
1817 local version = 0.003
1818 local cache = containers.define("fonts","converted",version,true)
1819 local loaded = { }
1820
1821 local loadpk = fonts.handlers.tfm.readers.loadpk
1822 local findpk = resolvers.findpk
1823 local pktocff = fonts.handlers.otf.readers.potracetocff
1824
1825 local function loadstreams(filename,details,resolution,settings)
1826
1827 filename = file.removesuffix(filename)
1828 local fullname = findpk(filename,resolution)
1829 local instance = fullname and fullname ~= "" and loadpk(fullname)
1830 if not instance then
1831 report_fonts("unable to locate %a",filename)
1832 return
1833 end
1834 local resolution = instance.resolution
1835 local streams = { }
1836 local glyphs = instance.glyphs or { }
1837 local settings = setting or { }
1838 local xmin = false
1839 local ymin = false
1840 local xmax = false
1841 local ymax = false
1842 local count = 0
1843
1844 for i=0,255 do
1845 local glyph = glyphs[i]
1846 if glyph then
1847 local llx, lly, urx, ury, cff = pktocff(glyph,settings,resolution,i)
1848 streams[i] = cff
1849 if not xmin then
1850 xmin = llx
1851 ymin = lly
1852 xmax = urx
1853 ymax = ury
1854 else
1855 if xmin < llx then llx = xmin end
1856 if ymin < lly then lly = ymin end
1857 if xmax < urx then urx = xmax end
1858 if ymax < ury then ury = ymax end
1859 end
1860 count = count + 1
1861
1862 end
1863 end
1864
1865 local filename = details.filename or "unknown"
1866 return {
1867 resolution = resolution,
1868 format = "opentype",
1869 filename = filename,
1870 fullname = fullname,
1871 streams = streams or { },
1872 names = {
1873 family = filename,
1874 fontname = filename,
1875 fullname = filename,
1876 notice = "Converted from MetaFont bitmaps by ConTeXt LMTX.",
1877 version = "1.00",
1878 },
1879 fontheader = {
1880 fontversion = "2.004",
1881 units = 1000,
1882 xmin = xmin or 0,
1883 ymin = ymin or 0,
1884 xmax = xmax or 0,
1885 ymax = ymax or 0,
1886 },
1887 cffinfo = {
1888 familyname = filename,
1889 fullname = filename,
1890
1891
1892
1893
1894
1895 bluescale = 0,
1896 blueshift = 0,
1897 bluefuzz = 0,
1898 expansionfactor = 0,
1899 },
1900 horizontalheader = {
1901 ascender = 0,
1902 descender = 0,
1903 },
1904 maximumprofile = {
1905 nofglyphs = count,
1906 },
1907 }
1908 end
1909
1910 mainwriters["pkcff"] = function(details)
1911 local filename = details.filename
1912 local properties = details.properties
1913 local resolution = properties.resolution or 7200
1914 local extrahash = properties.extrahash or resolution
1915 local cachename = filename .. "-" .. extrahash
1916 local converted = loaded[cachename]
1917 if not converted then
1918 converted = containers.read(cache,cachename)
1919 if not converted or converted.version ~= version or converted.resolution ~= resolution or converted.extrahash ~= extrahash then
1920 converted = loadstreams(filename,details,resolution,properties.potrace)
1921 if converted then
1922 converted.version = version
1923
1924 converted.extrahash = extrahash
1925 containers.write(cache,cachename,converted)
1926 converted = containers.read(cache,cachename)
1927 loaded[cachename] = converted
1928 end
1929 end
1930 end
1931 details.streams = converted
1932 mainwriters["type1"](details)
1933 end
1934
1935 end
1936
1937 do
1938
1939
1940
1941 local methods = { }
1942
1943 local pdfimage = lpdf.epdf.image
1944 local openpdf = pdfimage.open
1945 local closepdf = pdfimage.close
1946 local copypage = pdfimage.copy
1947
1948 local embedimage = images.embed
1949
1950 local f_glyph = formatters["G%d"]
1951 local f_char = formatters["BT /V%d 1 Tf [<%04X>] TJ ET"]
1952 local f_width = formatters["%N 0 d0"]
1953 local f_index = formatters["I%d"]
1954 local f_image_xy = formatters["%N 0 d0 1 0 0 1 %N %N cm /%s Do"]
1955 local f_image_c = formatters["/%s Do"]
1956 local f_image_c_xy = formatters["%N 0 0 %N %N %N cm /%s Do"]
1957 local f_image_w = formatters["%N 0 d0 %s"]
1958
1959 local f_stream = formatters["%N 0 d0 %s"]
1960 local f_stream_c = formatters["%N 0 0 0 0 0 d1 %s"]
1961local f_stream_cc = formatters["%N 0 %N %N %N %N d1 %s"]
1962 local f_stream_d = formatters["%N 0 d0 1 0 0 1 0 %N cm %s"]
1963
1964
1965 local f_image_xy = formatters["%N 0 d0 1 0 0 1 %N %N cm /Artifact BMC /%s Do EMC"]
1966 local f_image_c = formatters["/Artifact BMC /%s Do EMC"]
1967 local f_image_c_xy = formatters["%N 0 0 %N %N %N cm /Artifact BMC /%s Do EMC"]
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977 local c_notdef = nil
1978 local r_notdef = nil
1979 local w_notdef = nil
1980 local fontbbox = nil
1981
1982
1983
1984
1985
1986
1987 local version = 0.011
1988 local cache = containers.define("fonts","traced",version,true)
1989 local loaded = { }
1990
1991 function methods.pk(filename,details)
1992 local resolution = details.properties.resolution or 7200
1993 local extrahash = details.properties.extrahash or resolution
1994 local potraced = details.fontdata.potraced
1995 local pkfullname = resolvers.findpk(filename,resolution)
1996 if not pkfullname or pkfullname == "" then
1997 report_fonts("no pk file found: %a @ %i",filename,resolution)
1998 return
1999 end
2000 local readers = fonts.handlers.tfm.readers
2001 local result = readers.loadpk(pkfullname)
2002 if not result or result.error then
2003 return
2004 end
2005
2006 resolution = result.resolution
2007 local widthfactor = resolution / 72
2008 local scalefactor = 72 / resolution / 10
2009 local factor = widthfactor / 65536
2010 * (details.parameters.designsize / details.parameters.size)
2011 local pktopdf = nil
2012
2013 if potraced then
2014 local cachename = filename .. "-" .. extrahash
2015 local traced = loaded[cachename]
2016 if not traced then
2017 traced = containers.read(cache,cachename)
2018 if not traced or traced.version ~= version or traced.resolution ~= resolution or traced.extrahash ~= extrahash then
2019 local streams = { }
2020 local convert = readers.potracedtopdf
2021
2022 local descriptions = details.fontdata.descriptions
2023 local indices = { }
2024 local widths = { }
2025 for unicode, data in next, descriptions do
2026 local di = data.index
2027 if di then
2028 indices[di] = data
2029 end
2030 end
2031
2032 local settings = details.properties.potrace
2033 for index, glyph in sortedhash(result.glyphs) do
2034 streams[index], widths[index] = convert(glyph,indices[index],factor,settings)
2035 end
2036 traced = {
2037 version = version,
2038 resolution = resolution,
2039 streams = streams,
2040 widths = widths,
2041 extrahash = extrahash,
2042 }
2043 containers.write(cache,cachename,traced)
2044 traced = containers.read(cache,cachename)
2045 loaded[cachename] = traced
2046 end
2047 end
2048 local streams = traced.streams
2049 local widths = traced.widths
2050 pktopdf = function(glyph,data)
2051 local index = glyph.index
2052 return streams[index], widths[index]
2053 end
2054 else
2055 local convert = readers.pktopdf
2056 pktopdf = function (glyph,data)
2057 return convert(glyph,data,factor)
2058 end
2059 end
2060 return result.glyphs, scalefactor, pktopdf, false, false
2061 end
2062
2063
2064
2065 local used = setmetatableindex("table")
2066
2067 function lpdf.registerfontmethod(name,f)
2068 if type(f) == "function" and not methods[name] then
2069 report_fonts("registering font method %a, continue on your own risk",name)
2070 methods[name] = f
2071 end
2072 end
2073
2074 function methods.pdf(filename,details)
2075 local properties = details.properties
2076 local pdfshapes = properties.indexdata[1]
2077 local filename = pdfshapes.filename
2078 local pdfdoc = openpdf(filename)
2079 local xforms = pdfdictionary()
2080 local nofglyphs = 0
2081 if pdfdoc then
2082 local scale = 10 * details.parameters.size/details.parameters.designsize
2083 local units = details.parameters.units
2084 local factor = units * bpfactor / scale
2085 local fixdepth = pdfshapes.fixdepth
2086 local useddoc = used[pdfdoc]
2087 local function pdftopdf(glyph,data)
2088 local width = (data.width or 0) * factor
2089 local image = useddoc[glyph]
2090 local reference = nil
2091 if not image then
2092 image = embedimage(copypage(pdfdoc,glyph))
2093 if not image then
2094 report_fonts("bad image in file %a",filenameme)
2095 return
2096 end
2097 nofglyphs = nofglyphs + 1
2098 local name = f_glyph(nofglyphs)
2099 local stream = nil
2100 if fixdepth then
2101 local depth = data.depth or 0
2102 if depth ~= 0 then
2103 local d = data.dropin.descriptions[data.index]
2104 local b = d.boundingbox
2105 local l = b[1]
2106 local r = b[3]
2107 local w = r - l
2108 local scale = w / d.width
2109 local x = l
2110
2111 local y = - (d.depth or 0)
2112 local scale = w / (image.width * bpfactor)
2113 stream = f_image_c_xy(scale,scale,x,y,name)
2114 end
2115 end
2116 if not stream then
2117 stream = f_image_c(name)
2118 end
2119 useddoc[glyph] = image
2120 image.embedded_name = name
2121 image.embedded_stream = stream
2122 image.embedded_reference = pdfreference(image.objnum)
2123 end
2124 xforms[image.embedded_name] = image.embedded_reference
2125 return f_image_w(width,image.embedded_stream), width
2126 end
2127 local function closepdf()
2128
2129 end
2130 local function getresources()
2131 return pdfdictionary { XObject = xforms }
2132 end
2133 return pdfshapes, 1/units, pdftopdf, closepdf, getresources
2134 end
2135 end
2136
2137
2138
2139
2140 function methods.box(filename,details)
2141 local properties = details.properties
2142 local boxes = properties.indexdata[1]
2143 local xforms = pdfdictionary()
2144 local nofglyphs = 0
2145 local scale = 10 * details.parameters.size/details.parameters.designsize
2146 scale = scale * (7200/7227)
2147 local units = details.parameters.units
2148 local function boxtopdf(image,data)
2149 nofglyphs = nofglyphs + 1
2150 local scale = units / scale
2151 local width = (data.width or 0) * bpfactor * scale
2152 local height = (data.height or 0) * bpfactor * scale
2153 local depth = (data.depth or 0) * bpfactor * scale
2154 local name = f_glyph(nofglyphs)
2155 local stream = f_image_c_xy(scale,scale,0,-depth,name)
2156 image.embedded_name = name
2157 image.embedded_stream = stream
2158 image.embedded_reference = pdfreference(image.objnum)
2159 xforms[name] = image.embedded_reference
2160
2161 if image.expose then
2162 return f_stream_cc(width,-10,-depth-10,width+10,height+10,stream), width
2163 else
2164 return f_image_w(width,stream), width
2165 end
2166 end
2167 local function wrapup()
2168 end
2169 local function getresources()
2170 return pdfdictionary { XObject = xforms }
2171 end
2172 return boxes, 1/units, boxtopdf, wrapup, getresources
2173 end
2174
2175
2176
2177 local decompress = gzip.decompress
2178 local metapost = metapost
2179 local simplemprun = metapost.simple
2180 local setparameterset = metapost.setparameterset
2181
2182 function methods.mps(filename,details)
2183 local properties = details.properties
2184 local parameters = details.parameters
2185 local mpshapes = properties.indexdata[1]
2186 if mpshapes then
2187 local scale = 10 * parameters.size/parameters.designsize
2188 local units = mpshapes.units or parameters.units
2189 local factor = units * bpfactor / scale
2190 local fixdepth = mpshapes.fixdepth
2191 local usecolor = mpshapes.usecolor
2192 local specification = mpshapes.specification or { }
2193 local shapedefinitions = mpshapes.shapes
2194 local instance = mpshapes.instance
2195
2196 simplemprun(instance,"begingroup;",true,true)
2197 setparameterset("mpsfont",specification)
2198 specification.scale = specification.scale or scale
2199 specification.parameters = parameters
2200 specification.properties = properties
2201 specification.parentdata = details.fontdata.parentdata
2202
2203
2204 if shapedefinitions then
2205 local preamble = shapedefinitions.parameters.preamble
2206 if preamble then
2207 simplemprun(instance,preamble,true,true)
2208 end
2209 end
2210
2211 local function mpstopdf(mp,data)
2212 local width = data.width
2213 if decompress then
2214 mp = decompress(mp)
2215 end
2216 local pdf = simplemprun(instance,mp,true)
2217 local width = width * factor
2218 if usecolor then
2219 return f_stream_c(width,pdf), width
2220 elseif fixdepth then
2221 local depth = data.depth or 0
2222 local height = data.height or 0
2223 if depth ~= 0 or height ~= 0 then
2224 return f_stream_d(width,(-height-depth)*factor,pdf), width
2225 end
2226 end
2227 return f_stream(width,pdf), width
2228 end
2229
2230 local function resetmps()
2231 setparameterset("mpsfont")
2232 simplemprun(instance,"endgroup;",true,true)
2233 specification.parameters = nil
2234 specification.properties = nil
2235 specification.parentdata = nil
2236
2237
2238 end
2239
2240 local function getresources()
2241 return lpdf.collectedresources {
2242 serialize = false,
2243 }
2244 end
2245
2246 return mpshapes, 1/units, mpstopdf, resetmps, getresources
2247 end
2248 end
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258 local files = utilities.files
2259 local openfile = files.open
2260 local closefile = files.close
2261 local setposition = files.setposition
2262 local readstring = files.readstring
2263
2264 function methods.png(filename,details)
2265 local properties = details.properties
2266 local pngshapes = properties.indexdata[1]
2267 if pngshapes then
2268 local parameters = details.parameters
2269 local png = properties.png
2270 local hash = png.hash
2271 local xforms = pdfdictionary()
2272 local nofglyphs = 0
2273 local scale = 10 * parameters.size/parameters.designsize
2274 local factor = bpfactor / scale
2275
2276 local units = 1000
2277 local filehandle = openfile(details.filename,true)
2278 local function pngtopdf(glyph,data)
2279
2280 local offset = glyph.o
2281 local size = glyph.s
2282 local pdfdata = nil
2283 if offset and size then
2284 setposition(filehandle,offset)
2285 local blob = readstring(filehandle,size)
2286 local info = graphics.identifiers.png(blob,"string")
2287 info.enforcecmyk = pngshapes.enforcecmyk
2288 local image = lpdf.injectors.png(info,"string")
2289 local width = (data.width or 0) * factor
2290 if image then
2291 embedimage(image)
2292 nofglyphs = nofglyphs + 1
2293 local xoffset = (glyph.x or 0) / units
2294 local yoffset = (glyph.y or 0) / units
2295 local name = f_glyph(nofglyphs)
2296 xforms[name] = pdfreference(image.objnum)
2297 pdfdata = f_image_xy(width,xoffset,yoffset,name)
2298 end
2299 end
2300 return pdfdata or f_stream(width), width
2301 end
2302 local function closepng()
2303 if filehandle then
2304 closefile(filehandle)
2305 end
2306 pngshapes = nil
2307 end
2308 local function getresources()
2309 return pdfdictionary { XObject = xforms }
2310 end
2311 return pngshapes, 1, pngtopdf, closepng, getresources
2312 end
2313 end
2314
2315 local function registercolors(hash)
2316 local kind = hash.kind
2317 local data = hash.data
2318 local direct = lpdf.fonts.color_direct
2319 local indirect = lpdf.fonts.color_indirect
2320 if kind == "font" then
2321 return setmetatableindex(function(t,k)
2322 local h = data[k]
2323 local v = h and direct(h[1]/255,h[2]/255,h[3]/255) or false
2324 t[k] = v
2325 return v
2326 end)
2327 elseif kind == "user" then
2328 return setmetatableindex(function(t,k)
2329 local list = data[k]
2330 local v
2331 if list then
2332 local kind = list.kind
2333 if kind == "values" then
2334 local d = list.data
2335 v = direct(d.r or 0,d.g or 0,d.b or 0)
2336 elseif kind == "attributes" then
2337 v = indirect(list.color,list.transparency)
2338 else
2339 v = false
2340 end
2341 else
2342 v = false
2343 end
2344 t[k] = v
2345 return v
2346 end)
2347 else
2348 return { }
2349 end
2350 end
2351
2352
2353
2354 local usedcharacters = lpdf.usedcharacters
2355
2356 function methods.color(filename,details)
2357 local colrshapes = details.properties.indexdata[1]
2358 local colrvalues = details.properties.indexdata[2]
2359 local usedfonts = { }
2360 local function colrtopdf(description,data)
2361
2362 local colorlist = description.colors
2363 if colorlist then
2364 local dropdata = data.dropin
2365 local dropid = dropdata.properties.id
2366 local dropunits = dropdata.parameters.units
2367 local descriptions = dropdata.descriptions
2368 local directcolors = registercolors(colrvalues)
2369 local fontslots = usedcharacters[dropid]
2370 usedfonts[dropid] = dropid
2371 local w = description.width or 0
2372 local s = #colorlist
2373 local l = false
2374 local t = { f_width(w) }
2375 local n = 1
2376 local d = #colrvalues
2377 for i=1,s do
2378 local entry = colorlist[i]
2379 local class = entry.class or d
2380 if class then
2381
2382 local c = directcolors[class]
2383 if c and l ~= c then
2384 n = n + 1 ; t[n] = c
2385 l = c
2386 end
2387 end
2388 local e = descriptions[entry.slot]
2389 if e then
2390 n = n + 1 ; t[n] = f_char(dropid,fontslots[e.index])
2391 end
2392 end
2393
2394
2395 t = concat(t," ")
2396 return t, w / dropunits
2397 end
2398 end
2399 local function getresources()
2400 return lpdf.collectedresources {
2401 serialize = false,
2402 fonts = usedfonts,
2403 fontprefix = "V",
2404 }
2405 end
2406 return colrshapes, 1, colrtopdf, false, getresources
2407 end
2408
2409 mainwriters["type3"] = function(details)
2410 local properties = details.properties
2411 local basefontname = properties.basefontname or properties.name
2412 local askedmethod = properties.method or "pk"
2413 local method = methods[askedmethod] or methods.pk
2414 if not method or not basefontname or basefontname == "" then
2415 return
2416 end
2417 local glyphs, scalefactor, glyphtopdf, reset, getresources = method(basefontname,details)
2418 if not glyphs then
2419 return
2420 end
2421 local parameters = details.parameters
2422 local object = details.objectnumber
2423 local factor = parameters.factor
2424 local fontmatrix = pdfarray { scalefactor, 0, 0, scalefactor, 0, 0 }
2425 local indices,
2426 include,
2427 minindex,
2428 maxindex = collectindices(details.fontdata.characters,details.indices,details.used,details.hash)
2429 local widths = pdfarray()
2430 local differences = pdfarray()
2431 local charprocs = pdfdictionary()
2432 local basefont = pdfconstant(basefontname)
2433 local fontbbox = nil
2434 local d = 0
2435 local w = 0
2436 local forcenotdef = minindex > 0
2437 local lastindex = -0xFF
2438 if forcenotdef then
2439 widths[0] = 0
2440 minindex = 0
2441 lastindex = 0
2442 d = 2
2443 if not c_notdef then
2444 w_notdef = 0
2445 c_notdef = pdfconstant(".notdef")
2446 r_notdef = pdfreference(pdfflushstreamobject("0 0 d0"))
2447 end
2448 differences[1] = w_notdef
2449 differences[2] = c_notdef
2450 charprocs[".notdef"] = r_notdef
2451 end
2452 for i=1,maxindex-minindex+1 do
2453 widths[i] = 0
2454 end
2455 local wd = 0
2456 local ht = 0
2457 local dp = 0
2458 for index, data in sortedhash(indices) do
2459 local name = f_index(index)
2460 local glyph = glyphs[include[index]]
2461 if glyph then
2462 local stream, width = glyphtopdf(glyph,data)
2463 if stream then
2464 if type(glyph) == "table" then
2465 local gwd = glyph.width or 0
2466 local ght = glyph.height or 0
2467 local gdp = glyph.depth or 0
2468 if gwd > wd then wd = gwd end
2469 if ght > ht then ht = ght end
2470 if gdp > dp then dp = gdp end
2471 end
2472 if index - 1 ~= lastindex then
2473 d = d + 1 differences[d] = index
2474 end
2475 lastindex = index
2476 d = d + 1 differences[d] = pdfconstant(name)
2477 charprocs[name] = pdfreference(pdfflushstreamobject(stream))
2478 widths[index-minindex+1] = width
2479 end
2480 else
2481 report_fonts("missing glyph %i in type3 font %a",index,basefontname)
2482 end
2483 end
2484
2485
2486
2487 fontbbox = pdfarray { 0, 0, 0, 0 }
2488
2489
2490
2491
2492 local encoding = pdfdictionary {
2493 Type = pdfconstant("Encoding"),
2494 Differences = differences,
2495 }
2496 local tounicode = tounicodedictionary(details,indices,maxindex,basefontname,false)
2497 local resources = getresources and getresources()
2498 if not resources or not next(resources) then
2499
2500 resources = nil
2501 end
2502 local descriptor = pdfdictionary {
2503
2504 Type = pdfconstant("FontDescriptor"),
2505 FontName = basefont,
2506 Flags = 4,
2507 ItalicAngle = 0,
2508 }
2509 local parent = pdfdictionary {
2510 Type = pdfconstant("Font"),
2511 Subtype = pdfconstant("Type3"),
2512 Name = basefont,
2513 FontBBox = fontbbox,
2514 FontMatrix = fontmatrix,
2515 CharProcs = pdfreference(pdfflushobject(charprocs)),
2516 Encoding = pdfreference(pdfflushobject(encoding)),
2517 FirstChar = minindex,
2518 LastChar = maxindex,
2519 Widths = pdfreference(pdfflushobject(widths)),
2520 FontDescriptor = pdfreference(pdfflushobject(descriptor)),
2521 Resources = resources,
2522 ToUnicode = tounicode and pdfreference(pdfflushstreamobject(tounicode)),
2523 }
2524 pdfflushobject(object,parent)
2525 if reset then
2526 reset()
2527 end
2528 end
2529
2530 end
2531
2532end
2533
2534
2535
2536local usedfonts = fonts.hashes.identifiers
2537local noffonts = 0
2538
2539
2540
2541
2542
2543local getstreamhash = fonts.handlers.otf.getstreamhash
2544local loadstreamdata = fonts.handlers.otf.loadstreamdata
2545
2546local objects = setmetatableindex(lpdf.usedfontobjects,function(t,k)
2547 local v
2548 if type(k) == "number" then
2549 local h = getstreamhash(k)
2550 v = rawget(t,h)
2551 if not v then
2552 v = pdfreserveobject()
2553 t[h] = v
2554 end
2555 if trace_fonts then
2556 report_fonts("font id %i bound to hash %s and object %i",k,h,v)
2557 end
2558 else
2559
2560
2561 v = pdfreserveobject()
2562 t[k] = v
2563 end
2564 return v
2565end)
2566
2567local n = 0
2568
2569local names = setmetatableindex(lpdf.usedfontnames,function(t,k)
2570 local v
2571 if type(k) == "number" then
2572 local h = getstreamhash(k)
2573 v = rawget(t,h)
2574 if not v then
2575 n = n + 1
2576 v = n
2577 t[h] = v
2578 end
2579 if trace_fonts then
2580 report_fonts("font id %i bound to hash %s and name %i",k,h,n)
2581 end
2582 end
2583 t[k] = v
2584 return v
2585end)
2586
2587function lpdf.flushfonts()
2588
2589 local mainfonts = { }
2590
2591 statistics.starttiming(objects)
2592
2593
2594
2595
2596
2597
2598
2599 for fontid, used in sortedhash(lpdf.usedcharacters) do
2600
2601
2602 local hash = getstreamhash(fontid)
2603 if hash then
2604 local parent = mainfonts[hash]
2605 if not parent then
2606 local fontdata = usedfonts[fontid]
2607 local rawdata = fontdata.shared and fontdata.shared.rawdata
2608 local resources = fontdata.resources
2609 local properties = fontdata.properties
2610 local parameters = fontdata.parameters
2611 if not rawdata then
2612
2613
2614
2615 for xfontid, xfontdata in next, fonts.hashes.identifiers do
2616 if fontid ~= xfontid then
2617 local xhash = getstreamhash(xfontid)
2618 if hash == xhash then
2619 rawdata = xfontdata.shared and xfontdata.shared.rawdata
2620 if rawdata then
2621 resources = xfontdata.resources
2622 properties = xfontdata.properties
2623 parameters = xfontdata.parameters
2624 break
2625 end
2626 end
2627 end
2628 end
2629 end
2630
2631 parent = {
2632 hash = hash,
2633 fontdata = fontdata,
2634 filename = (resources and resources.filename) or properties.filename or "unset",
2635 indices = { },
2636 usedfonts = { [fontid] = true },
2637 used = used,
2638 rawdata = rawdata,
2639 properties = properties,
2640 parameters = parameters,
2641 streams = { },
2642 objectnumber = objects[hash],
2643 basefontname = subsetname(properties.psname or properties.name or "unset"),
2644 name = names[hash],
2645 }
2646 mainfonts[hash] = parent
2647 noffonts = noffonts + 1
2648
2649 end
2650 if parent then
2651 parent.usedfonts[fontid] = true
2652 local indices = parent.indices
2653 for k, v in next, used do
2654 indices[k] = v
2655 end
2656 end
2657 end
2658 end
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712 for hash, details in sortedhash(mainfonts) do
2713
2714 local filename = details.filename
2715 if next(details.indices) then
2716 local properties = details.properties
2717 local bitmap = properties.usedbitmap
2718 local method = properties.method
2719 local format = properties.format
2720 if trace_fonts then
2721 if method then
2722 report_fonts("embedding %a hashed as %a using method %a",filename,hash,method)
2723 else
2724 report_fonts("embedding %a hashed as %a",filename,hash)
2725 end
2726 end
2727 if bitmap or method then
2728 local format = "type3"
2729 local writer = mainwriters[format]
2730 if trace_fonts then
2731 report_fonts("using main writer %a",format)
2732 end
2733 writer(details)
2734 elseif format == "pkcff" then
2735 local writer = mainwriters[format]
2736 if trace_fonts then
2737 report_fonts("using main writer %a",format)
2738 end
2739 writer(details)
2740 else
2741 local writer = mainwriters[format]
2742 if writer then
2743 if trace_fonts then
2744 report_fonts("using main writer %a",format)
2745 end
2746
2747
2748
2749
2750 local streams = loadstreamdata(details.fontdata)
2751 if streams and streams.fontheader and streams.names then
2752 details.streams = streams
2753 writer(details)
2754 details.streams = { }
2755 elseif trace_fonts then
2756
2757 report_fonts("no streams in %a",filename)
2758 end
2759
2760 else
2761 report_fonts("no %a writer for %a",format,filename)
2762 end
2763 end
2764 else
2765
2766 end
2767 if trace_fonts then
2768 local indices = details.indices
2769 if indices and next(indices) then
2770 report_fonts("embedded indices: % t",sortedkeys(details.indices))
2771 end
2772 end
2773 mainfonts[details.hash] = false
2774 end
2775
2776 statistics.stoptiming(objects)
2777
2778end
2779
2780statistics.register("font embedding time",function()
2781 if noffonts > 0 then
2782 return format("%s seconds, %s fonts", statistics.elapsedtime(objects),noffonts)
2783 end
2784end)
2785
2786
2787
2788function lpdf.getfontobjectnumber(k)
2789 return objects[k]
2790end
2791
2792function lpdf.getfontname(k)
2793 return names[k]
2794end
2795
2796lpdf.registerdocumentfinalizer(lpdf.flushfonts,1,"wrapping up fonts")
2797 |