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