font-ttf.lua /size: 66 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['font-ttf'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-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-- This version is different from previous in the sense that we no longer store
11-- contours but keep points and contours (endpoints) separate for a while
12-- because later on we need to apply deltas and that is easier on a list of
13-- points.
14
15-- The code is a bit messy. I looked at the ff code but it's messy too. It has
16-- to do with the fact that we need to look at points on the curve and control
17-- points in between. This also means that we start at point 2 and have to look
18-- at point 1 when we're at the end. We still use a ps like storage with the
19-- operator last in an entry. It's typical code that evolves stepwise till a
20-- point of no comprehension.
21
22-- For deltas we need a rather complex loop over points that can have holes and
23-- be less than nofpoints and even can have duplicates and also the x and y value
24-- lists can be shorter than etc. I need fonts in order to complete this simply
25-- because I need to visualize in order to understand (what the standard tries
26-- to explain).
27
28-- 0 point then none applied
29-- 1 points then applied to all
30-- otherwise inferred deltas using nearest
31--    if no lower point then use highest referenced point
32--    if no higher point then use lowest referenced point
33--    factor = (target-left)/(right-left)
34--    delta  = (1-factor)*left + factor * right
35
36local next, type, unpack = next, type, unpack
37local band, rshift = bit32.band, bit32.rshift
38local sqrt, round, abs, min, max = math.sqrt, math.round, math.abs, math.min, math.max
39local char, rep = string.char, string.rep
40local concat = table.concat
41local idiv = number.idiv
42local setmetatableindex = table.setmetatableindex
43
44local report            = logs.reporter("otf reader","ttf")
45
46local trace_deltas      = false
47
48local readers           = fonts.handlers.otf.readers
49local streamreader      = readers.streamreader
50
51local setposition       = streamreader.setposition
52local getposition       = streamreader.getposition
53local skipbytes         = streamreader.skip
54local readbyte          = streamreader.readcardinal1  --  8-bit unsigned integer
55local readushort        = streamreader.readcardinal2  -- 16-bit unsigned integer
56local readulong         = streamreader.readcardinal4  -- 24-bit unsigned integer
57local readchar          = streamreader.readinteger1   --  8-bit   signed integer
58local readshort         = streamreader.readinteger2   -- 16-bit   signed integer
59local read2dot14        = streamreader.read2dot14     -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14)
60local readinteger       = streamreader.readinteger1
61local readcardinaltable = streamreader.readcardinaltable
62local readintegertable  = streamreader.readintegertable
63
64directives.register("fonts.streamreader",function()
65
66    streamreader      = utilities.streams
67
68    setposition       = streamreader.setposition
69    getposition       = streamreader.getposition
70    skipbytes         = streamreader.skip
71    readbyte          = streamreader.readcardinal1
72    readushort        = streamreader.readcardinal2
73    readulong         = streamreader.readcardinal4
74    readchar          = streamreader.readinteger1
75    readshort         = streamreader.readinteger2
76    read2dot14        = streamreader.read2dot14
77    readinteger       = streamreader.readinteger1
78    readcardinaltable = streamreader.readcardinaltable
79    readintegertable  = streamreader.readintegertable
80
81end)
82
83local short  = 2
84local ushort = 2
85local ulong  = 4
86
87local helpers       = readers.helpers
88local gotodatatable = helpers.gotodatatable
89
90local function mergecomposites(glyphs,shapes)
91
92    -- todo : deltas
93
94    local function merge(index,shape,components)
95        local contours    = { }
96        local points      = { }
97        local nofcontours = 0
98        local nofpoints   = 0
99        local offset      = 0
100        local deltas      = shape.deltas
101        for i=1,#components do
102            local component   = components[i]
103            local subindex    = component.index
104            local subshape    = shapes[subindex]
105            local subcontours = subshape.contours
106            local subpoints   = subshape.points
107            if not subcontours then
108                local subcomponents = subshape.components
109                if subcomponents then
110                    subcontours, subpoints = merge(subindex,subshape,subcomponents)
111                end
112            end
113            if subpoints then
114                local matrix  = component.matrix
115                local xscale  = matrix[1]
116                local xrotate = matrix[2]
117                local yrotate = matrix[3]
118                local yscale  = matrix[4]
119                local xoffset = matrix[5]
120                local yoffset = matrix[6]
121                local count   = #subpoints
122                if xscale == 1 and yscale == 1 and xrotate == 0 and yrotate == 0 then
123                    for i=1,count do
124                        local p = subpoints[i]
125                        nofpoints = nofpoints + 1
126                        points[nofpoints] = {
127                            p[1] + xoffset,
128                            p[2] + yoffset,
129                            p[3]
130                        }
131                    end
132                else
133                    for i=1,count do
134                        local p = subpoints[i]
135                        local x = p[1]
136                        local y = p[2]
137                        nofpoints = nofpoints + 1
138                        points[nofpoints] = {
139                            -- unifractur : u n
140                            -- seguiemj   : 0x270E 0x2710
141                            xscale * x + xrotate * y + xoffset,
142                            yscale * y + yrotate * x + yoffset,
143--                             xscale * x + yrotate * y + xoffset,
144--                             xrotate * x + yscale * y + yoffset,
145                            p[3]
146                        }
147                    end
148                end
149                local subcount = #subcontours
150                if subcount == 1 then
151                    nofcontours = nofcontours + 1
152                    contours[nofcontours] = offset + subcontours[1]
153                else
154                    for i=1,#subcontours do
155                        nofcontours = nofcontours + 1
156                        contours[nofcontours] = offset + subcontours[i]
157                    end
158                end
159                offset = offset + count
160            else
161                report("missing contours composite %s, component %s of %s, glyph %s",index,i,#components,subindex)
162            end
163        end
164        shape.points     = points -- todo : phantom points
165        shape.contours   = contours
166        shape.components = nil
167        return contours, points
168    end
169
170    for index=0,#glyphs do
171        local shape = shapes[index]
172        if shape then
173            local components = shape.components
174            if components then
175                merge(index,shape,components)
176            end
177        end
178    end
179
180end
181
182local function readnothing(f)
183    return {
184        type = "nothing",
185    }
186end
187
188-- begin of converter
189
190local function curveto(m_x,m_y,l_x,l_y,r_x,r_y) -- todo: inline this
191    return
192        l_x + 2/3 *(m_x-l_x), l_y + 2/3 *(m_y-l_y),
193        r_x + 2/3 *(m_x-r_x), r_y + 2/3 *(m_y-r_y),
194        r_x, r_y, "c"
195end
196
197-- We could omit the operator which saves some 10%:
198--
199--   #2=lineto  #4=quadratic  #6=cubic #3=moveto (with "m")
200--
201-- This is tricky ... something to do with phantom points .. however, the hvar
202-- and vvar tables should take care of the width .. the test font doesn't have
203-- those so here we go then (we need a flag for hvar).
204--
205--    h-advance left-side-bearing v-advance top-side-bearing
206--
207-- We had two loops (going backward) but can do it in one loop .. but maybe we
208-- should only accept fonts with proper hvar tables.
209
210-- dowidth is kind of hack ... fonts are not always ok wrt these extra points
211
212local xv = { } -- we share this cache
213local yv = { } -- we share this cache
214
215local function applyaxis(glyph,shape,deltas,dowidth)
216    local points = shape.points
217    if points then
218        local nofpoints = #points
219        local dw = 0
220        local dl = 0
221        for i=1,#deltas do
222            local deltaset = deltas[i]
223            local xvalues  = deltaset.xvalues
224            local yvalues  = deltaset.yvalues
225            if xvalues and yvalues then
226                local dpoints = deltaset.points
227                local factor  = deltaset.factor
228                if dpoints then
229                    local cnt = #dpoints
230                    if dowidth then
231                        cnt = cnt - 4
232                    end
233                    if cnt > 0 then
234                        -- Not the most efficient solution but we seldom do this. We
235                        -- actually need to avoid the extra points here but I'll deal
236                        -- with that when needed.
237                        local contours    = shape.contours
238                        local nofcontours = #contours
239                        local first       = 1
240                        local firstindex  = 1
241                        for contour=1,nofcontours do
242                            local last = contours[contour]
243                            if last >= first then
244                                local lastindex = cnt
245                                if firstindex < cnt then
246                                    for currentindex=firstindex,cnt do
247                                        local found = dpoints[currentindex]
248                                        if found <= first then
249                                            firstindex = currentindex
250                                        end
251                                        if found == last then
252                                            lastindex = currentindex
253                                            break
254                                        elseif found > last then
255                                            -- \definefontfeature[book][default][axis={weight=800}]
256                                            -- \definefont[testfont][file:Commissioner-vf-test.ttf*book]
257                                            -- \testfont EΘÄΞ
258                                            while lastindex > 1 and dpoints[lastindex] > last do
259                                                lastindex = lastindex - 1
260                                            end
261                                            --
262                                            break
263                                        end
264                                    end
265                                end
266                             -- print("unicode: ",glyph.unicode or "?")
267                             -- print("contour: ",first,contour,last)
268                             -- print("index  : ",firstindex,lastindex,cnt)
269                             -- print("points : ",dpoints[firstindex],dpoints[lastindex])
270                                local function find(i)
271                                    local prv = lastindex
272                                    for j=firstindex,lastindex do
273                                        local nxt = dpoints[j] -- we could save this lookup when we return it
274                                        if nxt == i then
275                                            return false, j, false
276                                        elseif nxt > i then
277                                            return prv, false, j
278                                        end
279                                        prv = j
280                                    end
281                                    return prv, false, firstindex
282                                end
283                                -- We need the first and last points untouched so we first
284                                -- collect data.
285                                for point=first,last do
286                                    local d1, d2, d3 = find(point)
287                                    local p2 = points[point]
288                                    if d2 then
289                                        xv[point] = xvalues[d2]
290                                        yv[point] = yvalues[d2]
291                                    else
292                                        local n1 = dpoints[d1]
293                                        local n3 = dpoints[d3]
294                                        -- Some day I need to figure out these extra points but
295                                        -- I'll wait till the standard is more clear and fonts
296                                        -- become better (ntg-context: fraunces.ttf > abcdef).
297                                        if n1 > nofpoints then
298                                            n1 = nofpoints
299                                        end
300                                        if n3 > nofpoints then
301                                            n3 = nofpoints
302                                        end
303                                        --
304                                        local p1 = points[n1]
305                                        local p3 = points[n3]
306                                        local p1x = p1[1]
307                                        local p2x = p2[1]
308                                        local p3x = p3[1]
309                                        local p1y = p1[2]
310                                        local p2y = p2[2]
311                                        local p3y = p3[2]
312                                        local x1 = xvalues[d1]
313                                        local y1 = yvalues[d1]
314                                        local x3 = xvalues[d3]
315                                        local y3 = yvalues[d3]
316                                        --
317                                        local fx
318                                        local fy
319                                        --
320                                        if p1x == p3x then
321                                            if x1 == x3 then
322                                                fx = x1
323                                            else
324                                                fx = 0
325                                            end
326                                        elseif p2x <= min(p1x,p3x) then
327                                            if p1x < p3x then
328                                                fx = x1
329                                            else
330                                                fx = x3
331                                            end
332                                        elseif p2x >= max(p1x,p3x) then
333                                            if p1x > p3x then
334                                                fx = x1
335                                            else
336                                                fx = x3
337                                            end
338                                        else
339                                            fx = (p2x - p1x)/(p3x - p1x)
340-- fx = round(fx)
341                                            fx = (1 - fx) * x1 + fx * x3
342                                        end
343                                        --
344                                        if p1y == p3y then
345                                            if y1 == y3 then
346                                                fy = y1
347                                            else
348                                                fy = 0
349                                            end
350                                        elseif p2y <= min(p1y,p3y) then
351                                            if p1y < p3y then
352                                                fy = y1
353                                            else
354                                                fy = y3
355                                            end
356                                        elseif p2y >= max(p1y,p3y) then
357                                            if p1y > p3y then
358                                                fy = y1
359                                            else
360                                                fy = y3
361                                            end
362                                        else
363                                            fy = (p2y - p1y)/(p3y - p1y)
364-- fy = round(fy)
365                                            fy = (1 - fy) * y1 + fy * y3
366                                        end
367                                     -- -- maybe:
368                                     -- if p1y ~= p3y then
369                                     --     fy = (p2y - p1y)/(p3y - p1y)
370                                     --     fy = (1 - fy) * y1 + fy * y3
371                                     -- elseif abs(p1y-p2y) < abs(p3y-p2y) then
372                                     --     fy = y1
373                                     -- else
374                                     --     fy = y3
375                                     -- end
376                                        --
377                                        xv[point] = fx
378                                        yv[point] = fy
379                                    end
380                                end
381                                if lastindex < cnt then
382                                    firstindex = lastindex + 1
383                                end
384                            end
385                            first = last + 1
386                        end
387
388                        for i=1,nofpoints do
389                            local pi = points[i]
390                            local fx = xv[i]
391                            local fy = yv[i]
392                            if fx ~= 0 then
393                                pi[1] = pi[1] + factor * fx
394                            end
395                            if fy ~= 0 then
396                                pi[2] = pi[2] + factor * fy
397                            end
398                        end
399                    else
400                        report("bad deltapoint data, maybe a missing hvar table")
401                    end
402                else
403                    for i=1,nofpoints do
404                        local p = points[i]
405                        local x = xvalues[i]
406                        if x then
407                            local y = yvalues[i]
408                            if x ~= 0 then
409                                p[1] = p[1] + factor * x
410                            end
411                            if y ~= 0 then
412                                p[2] = p[2] + factor * y
413                            end
414                        else
415                            break
416                        end
417                    end
418                end
419                if dowidth then
420                    local h = nofpoints + 2 -- weird, the example font seems to have left first
421                    local l = nofpoints + 1
422                    ----- v = nofpoints + 3
423                    ----- t = nofpoints + 4
424                    local x = xvalues[h]
425                    if x then
426                        dw = dw + factor * x
427                    end
428                    local x = xvalues[l]
429                    if x then
430                        dl = dl + factor * x
431                    end
432                end
433            end
434        end
435     -- for i=1,nofpoints do
436     --     local p = points[i]
437     --     p[1] = round(p[1])
438     --     p[2] = round(p[2])
439     -- end
440        if dowidth then
441            local width = glyph.width or 0
442         -- local lsb   = glyph.lsb or 0
443            glyph.width = width + dw - dl
444        end
445    else
446        report("no points for glyph %a",glyph.name)
447    end
448end
449
450-- round or not ?
451
452-- local quadratic = true -- both methods work, todo: install a directive
453local quadratic = false
454
455local function contours2outlines_normal(glyphs,shapes) -- maybe accept the bbox overhead
456--     for index=1,#glyphs do
457    for index=0,#glyphs-1 do
458        local shape = shapes[index]
459        if shape then
460            local glyph    = glyphs[index]
461            local contours = shape.contours
462            local points   = shape.points
463            if contours then
464                local nofcontours = #contours
465                local segments    = { }
466                local nofsegments = 0
467                glyph.segments    = segments
468                if nofcontours > 0 then
469                    local px    = 0
470                    local py    = 0
471                    local first = 1
472                    for i=1,nofcontours do
473                        local last = contours[i]
474                        if last >= first then
475                            local first_pt = points[first]
476                            local first_on = first_pt[3]
477                            -- todo no new tables but reuse lineto and quadratic
478                            if first == last then
479                                first_pt[3] = "m" -- "moveto"
480                                nofsegments = nofsegments + 1
481                                segments[nofsegments] = first_pt
482                            else -- maybe also treat n == 2 special
483                                local first_on   = first_pt[3]
484                                local last_pt    = points[last]
485                                local last_on    = last_pt[3]
486                                local start      = 1
487                                local control_pt = false
488                                if first_on then
489                                    start = 2
490                                else
491                                    if last_on then
492                                        first_pt = last_pt
493                                    else
494                                        first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false }
495                                    end
496                                    control_pt = first_pt
497                                end
498                                local x = first_pt[1]
499                                local y = first_pt[2]
500                                if not done then
501                                    xmin = x
502                                    ymin = y
503                                    xmax = x
504                                    ymax = y
505                                    done = true
506                                end
507                                nofsegments = nofsegments + 1
508                                segments[nofsegments] = { x, y, "m" } -- "moveto"
509                                if not quadratic then
510                                    px = x
511                                    py = y
512                                end
513                                local previous_pt = first_pt
514                                for i=first,last do
515                                    local current_pt  = points[i]
516                                    local current_on  = current_pt[3]
517                                    local previous_on = previous_pt[3]
518                                    if previous_on then
519                                        if current_on then
520                                            -- both normal points
521                                            local x, y = current_pt[1], current_pt[2]
522                                            nofsegments = nofsegments + 1
523                                            segments[nofsegments] = { x, y, "l" } -- "lineto"
524                                            if not quadratic then
525                                                px, py = x, y
526                                            end
527                                        else
528                                            control_pt = current_pt
529                                        end
530                                    elseif current_on then
531                                        local x1 = control_pt[1]
532                                        local y1 = control_pt[2]
533                                        local x2 = current_pt[1]
534                                        local y2 = current_pt[2]
535                                        nofsegments = nofsegments + 1
536                                        if quadratic then
537                                            segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
538                                        else
539                                            x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
540                                            segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
541                                        end
542                                        control_pt = false
543                                    else
544                                        local x2 = (previous_pt[1]+current_pt[1])/2
545                                        local y2 = (previous_pt[2]+current_pt[2])/2
546                                        local x1 = control_pt[1]
547                                        local y1 = control_pt[2]
548                                        nofsegments = nofsegments + 1
549                                        if quadratic then
550                                            segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
551                                        else
552                                            x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
553                                            segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
554                                        end
555                                        control_pt = current_pt
556                                    end
557                                    previous_pt = current_pt
558                                end
559                                if first_pt == last_pt then
560                                    -- we're already done, probably a simple curve
561                                else
562                                    nofsegments = nofsegments + 1
563                                    local x2 = first_pt[1]
564                                    local y2 = first_pt[2]
565                                    if not control_pt then
566                                        segments[nofsegments] = { x2, y2, "l" } -- "lineto"
567                                    elseif quadratic then
568                                        local x1 = control_pt[1]
569                                        local y1 = control_pt[2]
570                                        segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
571                                    else
572                                        local x1 = control_pt[1]
573                                        local y1 = control_pt[2]
574                                        x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
575                                        segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
576                                     -- px, py = x2, y2
577                                    end
578                                end
579                            end
580                        end
581                        first = last + 1
582                    end
583                end
584            end
585        end
586    end
587end
588
589local function contours2outlines_shaped(glyphs,shapes,keepcurve)
590--     for index=1,#glyphs do
591    for index=0,#glyphs-1 do
592        local shape = shapes[index]
593        if shape then
594            local glyph    = glyphs[index]
595            local contours = shape.contours
596            local points   = shape.points
597            if contours then
598                local nofcontours = #contours
599                local segments    = keepcurve and { } or nil
600                local nofsegments = 0
601                if keepcurve then
602                    glyph.segments = segments
603                end
604                if nofcontours > 0 then
605                    local xmin, ymin, xmax, ymax, done = 0, 0, 0, 0, false
606                    local px, py = 0, 0 -- we could use these in calculations which saves a copy
607                    local first = 1
608                    for i=1,nofcontours do
609                        local last = contours[i]
610                        if last >= first then
611                            local first_pt = points[first]
612                            local first_on = first_pt[3]
613                            -- todo no new tables but reuse lineto and quadratic
614                            if first == last then
615                                -- this can influence the boundingbox
616                                if keepcurve then
617                                    first_pt[3] = "m" -- "moveto"
618                                    nofsegments = nofsegments + 1
619                                    segments[nofsegments] = first_pt
620                                end
621                            else -- maybe also treat n == 2 special
622                                local first_on   = first_pt[3]
623                                local last_pt    = points[last]
624                                local last_on    = last_pt[3]
625                                local start      = 1
626                                local control_pt = false
627                                if first_on then
628                                    start = 2
629                                else
630                                    if last_on then
631                                        first_pt = last_pt
632                                    else
633                                        first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false }
634                                    end
635                                    control_pt = first_pt
636                                end
637                                local x = first_pt[1]
638                                local y = first_pt[2]
639                                if not done then
640                                    xmin, ymin, xmax, ymax = x, y, x, y
641                                    done = true
642                                else
643                                    if x < xmin then xmin = x elseif x > xmax then xmax = x end
644                                    if y < ymin then ymin = y elseif y > ymax then ymax = y end
645                                end
646                                if keepcurve then
647                                    nofsegments = nofsegments + 1
648                                    segments[nofsegments] = { x, y, "m" } -- "moveto"
649                                end
650                                if not quadratic then
651                                    px = x
652                                    py = y
653                                end
654                                local previous_pt = first_pt
655                                for i=first,last do
656                                    local current_pt  = points[i]
657                                    local current_on  = current_pt[3]
658                                    local previous_on = previous_pt[3]
659                                    if previous_on then
660                                        if current_on then
661                                            -- both normal points
662                                            local x = current_pt[1]
663                                            local y = current_pt[2]
664                                            if x < xmin then xmin = x elseif x > xmax then xmax = x end
665                                            if y < ymin then ymin = y elseif y > ymax then ymax = y end
666                                            if keepcurve then
667                                                nofsegments = nofsegments + 1
668                                                segments[nofsegments] = { x, y, "l" } -- "lineto"
669                                            end
670                                            if not quadratic then
671                                                px = x
672                                                py = y
673                                            end
674                                        else
675                                            control_pt = current_pt
676                                        end
677                                    elseif current_on then
678                                        local x1 = control_pt[1]
679                                        local y1 = control_pt[2]
680                                        local x2 = current_pt[1]
681                                        local y2 = current_pt[2]
682                                        if quadratic then
683                                            if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
684                                            if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
685                                            if keepcurve then
686                                                nofsegments = nofsegments + 1
687                                                segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
688                                            end
689                                        else
690                                            x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
691                                            if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
692                                            if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
693                                            if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
694                                            if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
695                                            if px < xmin then xmin = px elseif px > xmax then xmax = px end
696                                            if py < ymin then ymin = py elseif py > ymax then ymax = py end
697                                            if keepcurve then
698                                                nofsegments = nofsegments + 1
699                                                segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
700                                            end
701                                        end
702                                        control_pt = false
703                                    else
704                                        local x2 = (previous_pt[1]+current_pt[1])/2
705                                        local y2 = (previous_pt[2]+current_pt[2])/2
706                                        local x1 = control_pt[1]
707                                        local y1 = control_pt[2]
708                                        if quadratic then
709                                            if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
710                                            if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
711                                            if keepcurve then
712                                                nofsegments = nofsegments + 1
713                                                segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
714                                            end
715                                        else
716                                            x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
717                                            if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
718                                            if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
719                                            if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
720                                            if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
721                                            if px < xmin then xmin = px elseif px > xmax then xmax = px end
722                                            if py < ymin then ymin = py elseif py > ymax then ymax = py end
723                                            if keepcurve then
724                                                nofsegments = nofsegments + 1
725                                                segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
726                                            end
727                                        end
728                                        control_pt = current_pt
729                                    end
730                                    previous_pt = current_pt
731                                end
732                                if first_pt == last_pt then
733                                    -- we're already done, probably a simple curve
734                                elseif not control_pt then
735                                    if keepcurve then
736                                        nofsegments = nofsegments + 1
737                                        segments[nofsegments] = { first_pt[1], first_pt[2], "l" } -- "lineto"
738                                    end
739                                else
740                                    local x1 = control_pt[1]
741                                    local y1 = control_pt[2]
742                                    local x2 = first_pt[1]
743                                    local y2 = first_pt[2]
744                                    if x1 < xmin then xmin = x1 elseif x1 > xmax then xmax = x1 end
745                                    if y1 < ymin then ymin = y1 elseif y1 > ymax then ymax = y1 end
746                                    if quadratic then
747                                        if keepcurve then
748                                            nofsegments = nofsegments + 1
749                                            segments[nofsegments] = { x1, y1, x2, y2, "q" } -- "quadraticto"
750                                        end
751                                    else
752                                        x1, y1, x2, y2, px, py = curveto(x1, y1, px, py, x2, y2)
753                                        if x2 < xmin then xmin = x2 elseif x2 > xmax then xmax = x2 end
754                                        if y2 < ymin then ymin = y2 elseif y2 > ymax then ymax = y2 end
755                                        if px < xmin then xmin = px elseif px > xmax then xmax = px end
756                                        if py < ymin then ymin = py elseif py > ymax then ymax = py end
757                                        if keepcurve then
758                                            nofsegments = nofsegments + 1
759                                            segments[nofsegments] = { x1, y1, x2, y2, px, py, "c" } -- "curveto"
760                                        end
761                                     -- px, py = x2, y2
762                                    end
763                                end
764                            end
765                        end
766                        first = last + 1
767                    end
768                    -- See readers.hvar where we set the delta lsb as well as the adapted
769                    -- width. At this point we do know the boundingbox's llx. The xmax is
770                    -- not that relevant. It needs more testing!
771                    --
772                    xmin = glyph.boundingbox[1]
773                    --
774                    local dlsb = glyph.dlsb
775                    if dlsb then
776                        xmin = xmin + dlsb
777                        glyph.dlsb = nil -- save space
778                    end
779                    --
780                    glyph.boundingbox = { round(xmin), round(ymin), round(xmax), round(ymax) }
781                end
782            end
783        end
784    end
785end
786
787-- optimize for zero
788
789local c_zero = char(0)
790local s_zero = char(0,0)
791
792-- local shorthash = setmetatableindex(function(t,k)
793--     t[k] = char(band(rshift(k,8),0xFF),band(k,0xFF)) return t[k]
794-- end)
795
796local function toushort(n)
797    return char(band(rshift(n,8),0xFF),band(n,0xFF))
798 -- return shorthash[n]
799end
800
801local function toshort(n)
802    if n < 0 then
803        n = n + 0x10000
804    end
805    return char(band(rshift(n,8),0xFF),band(n,0xFF))
806 -- return shorthash[n]
807end
808
809-- todo: we can reuse result, xpoints and ypoints
810
811local chars = setmetatableindex(function(t,k)
812    for i=0,255 do local v = char(i) t[i] = v end return t[k]
813end)
814
815local function repackpoints(glyphs,shapes)
816    local noboundingbox = { 0, 0, 0, 0 }
817    local result        = { } -- reused
818    local xpoints       = { } -- reused
819    local ypoints       = { } -- reused
820    for index=0,#glyphs do
821        local shape = shapes[index]
822        if shape then
823            local r     = 0
824            local glyph = glyphs[index]
825            local contours    = shape.contours
826            local nofcontours = contours and #contours or 0
827            local boundingbox = glyph.boundingbox or noboundingbox
828            r = r + 1 result[r] = toshort(nofcontours)
829            r = r + 1 result[r] = toshort(boundingbox[1]) -- xmin
830            r = r + 1 result[r] = toshort(boundingbox[2]) -- ymin
831            r = r + 1 result[r] = toshort(boundingbox[3]) -- xmax
832            r = r + 1 result[r] = toshort(boundingbox[4]) -- ymax
833            if nofcontours > 0 then
834                for i=1,nofcontours do
835                    r = r + 1 result[r] = toshort(contours[i]-1)
836                end
837                r = r + 1 result[r] = s_zero -- no instructions
838                local points   = shape.points
839                local currentx = 0
840                local currenty = 0
841             -- local xpoints  = { }
842             -- local ypoints  = { }
843                local x        = 0
844                local y        = 0
845                local lastflag = nil
846                local nofflags = 0
847                for i=1,#points do
848                    local pt = points[i]
849                    local px = pt[1]
850                    local py = pt[2]
851                    local fl = pt[3] and 0x01 or 0x00
852                    if px == currentx then
853                        fl = fl + 0x10
854                    else
855                        local dx = round(px - currentx)
856                        x = x + 1
857                        if dx < -255 or dx > 255 then
858                            xpoints[x] = toshort(dx)
859                        elseif dx < 0 then
860                            fl = fl + 0x02
861                         -- xpoints[x] = char(-dx)
862                            xpoints[x] = chars[-dx]
863                        elseif dx > 0 then
864                            fl = fl + 0x12
865                         -- xpoints[x] = char(dx)
866                            xpoints[x] = chars[dx]
867                        else
868                            fl = fl + 0x02
869                            xpoints[x] = c_zero
870                        end
871                    end
872                    if py == currenty then
873                        fl = fl + 0x20
874                    else
875                        local dy = round(py - currenty)
876                        y = y + 1
877                        if dy < -255 or dy > 255 then
878                            ypoints[y] = toshort(dy)
879                        elseif dy < 0 then
880                            fl = fl + 0x04
881                         -- ypoints[y] = char(-dy)
882                            ypoints[y] = chars[-dy]
883                        elseif dy > 0 then
884                            fl = fl + 0x24
885                         -- ypoints[y] = char(dy)
886                            ypoints[y] = chars[dy]
887                        else
888                            fl = fl + 0x04
889                            ypoints[y] = c_zero
890                        end
891                    end
892                    currentx = px
893                    currenty = py
894                    if lastflag == fl then
895                        if nofflags == 255 then
896                            -- This happens in koeieletters!
897                            lastflag = lastflag + 0x08
898                            r = r + 1 result[r] = char(lastflag,nofflags-1)
899                            nofflags = 1
900                            lastflag = fl
901                        else
902                            nofflags = nofflags + 1
903                        end
904                    else -- if > 255
905                        if nofflags == 1 then
906                         -- r = r + 1 result[r] = char(lastflag)
907                            r = r + 1 result[r] = chars[lastflag]
908                        elseif nofflags == 2 then
909                            r = r + 1 result[r] = char(lastflag,lastflag)
910                        elseif nofflags > 2 then
911                            lastflag = lastflag + 0x08
912                            r = r + 1 result[r] = char(lastflag,nofflags-1)
913                        end
914                        nofflags = 1
915                        lastflag = fl
916                    end
917                end
918                if nofflags == 1 then
919                 -- r = r + 1 result[r] = char(lastflag)
920                    r = r + 1 result[r] = chars[lastflag]
921                elseif nofflags == 2 then
922                    r = r + 1 result[r] = char(lastflag,lastflag)
923                elseif nofflags > 2 then
924                    lastflag = lastflag + 0x08
925                    r = r + 1 result[r] = char(lastflag,nofflags-1)
926                end
927             -- r = r + 1 result[r] = concat(xpoints)
928             -- r = r + 1 result[r] = concat(ypoints)
929                r = r + 1 result[r] = concat(xpoints,"",1,x)
930                r = r + 1 result[r] = concat(ypoints,"",1,y)
931            end
932            -- can be helper or delegated to user
933            local stream  = concat(result,"",1,r)
934            local length  = #stream
935            local padding = idiv(length+3,4) * 4 - length
936            if padding > 0 then
937             -- stream = stream .. rep("\0",padding) -- can be a repeater
938                if padding == 1 then
939                    padding = "\0"
940                elseif padding == 2 then
941                    padding = "\0\0"
942                else
943                    padding = "\0\0\0"
944                end
945                padding = stream .. padding
946            end
947            glyph.stream = stream
948        end
949    end
950end
951
952-- end of converter
953
954local flags = { }
955
956local function readglyph(f,nofcontours) -- read deltas here, saves space
957    local points          = { }
958 -- local instructions    = { }
959    local contours        = { } -- readintegertable(f,nofcontours,short)
960    for i=1,nofcontours do
961        contours[i] = readshort(f) + 1
962    end
963    local nofpoints       = contours[nofcontours]
964    local nofinstructions = readushort(f)
965    skipbytes(f,nofinstructions)
966    -- because flags can repeat we don't know the amount ... in fact this is
967    -- not that efficient (small files but more mem)
968    local i = 1
969    while i <= nofpoints do
970        local flag = readbyte(f)
971        flags[i] = flag
972        if band(flag,0x08) ~= 0 then
973            local n = readbyte(f)
974            if n == 1 then
975                i = i + 1
976                flags[i] = flag
977            else
978                for j=1,n do
979                    i = i + 1
980                    flags[i] = flag
981                end
982            end
983        end
984        i = i + 1
985    end
986    -- first come the x coordinates, and next the y coordinates and they
987    -- can be repeated
988    local x = 0
989    for i=1,nofpoints do
990        local flag = flags[i]
991     -- local short = band(flag,0x02) ~= 0
992     -- local same  = band(flag,0x10) ~= 0
993        if band(flag,0x02) ~= 0 then
994            if band(flag,0x10) ~= 0 then
995                x = x + readbyte(f)
996            else
997                x = x - readbyte(f)
998            end
999        elseif band(flag,0x10) ~= 0 then
1000            -- copy
1001        else
1002            x = x + readshort(f)
1003        end
1004        points[i] = { x, 0, band(flag,0x01) ~= 0 }
1005    end
1006    local y = 0
1007    for i=1,nofpoints do
1008        local flag = flags[i]
1009     -- local short = band(flag,0x04) ~= 0
1010     -- local same  = band(flag,0x20) ~= 0
1011        if band(flag,0x04) ~= 0 then
1012            if band(flag,0x20) ~= 0 then
1013                y = y + readbyte(f)
1014            else
1015                y = y - readbyte(f)
1016            end
1017        elseif band(flag,0x20) ~= 0 then
1018         -- copy
1019        else
1020            y = y + readshort(f)
1021        end
1022        points[i][2] = y
1023    end
1024    return {
1025        type      = "glyph",
1026        points    = points,
1027        contours  = contours,
1028        nofpoints = nofpoints,
1029    }
1030end
1031
1032local function readcomposite(f)
1033    local components    = { }
1034    local nofcomponents = 0
1035    local instructions  = false
1036    while true do
1037        local flags      = readushort(f)
1038        local index      = readushort(f)
1039        ----- f_words    = band(flags,0x0001) ~= 0
1040        local f_xyarg    = band(flags,0x0002) ~= 0
1041        ----- f_round    = band(flags,0x0006) ~= 0 -- 2 + 4
1042        ----- f_scale    = band(flags,0x0008) ~= 0
1043        ----- f_reserved = band(flags,0x0010) ~= 0
1044        ----- f_more     = band(flags,0x0020) ~= 0
1045        ----- f_xyscale  = band(flags,0x0040) ~= 0
1046        ----- f_matrix   = band(flags,0x0080) ~= 0
1047        ----- f_instruct = band(flags,0x0100) ~= 0
1048        ----- f_usemine  = band(flags,0x0200) ~= 0
1049        ----- f_overlap  = band(flags,0x0400) ~= 0
1050        local f_offset   = band(flags,0x0800) ~= 0
1051        ----- f_uoffset  = band(flags,0x1000) ~= 0
1052        local xscale     = 1
1053        local xrotate    = 0
1054        local yrotate    = 0
1055        local yscale     = 1
1056        local xoffset    = 0
1057        local yoffset    = 0
1058        local base       = false
1059        local reference  = false
1060        if f_xyarg then
1061            if band(flags,0x0001) ~= 0 then -- f_words
1062                xoffset = readshort(f)
1063                yoffset = readshort(f)
1064            else
1065                xoffset = readchar(f) -- signed byte, stupid name
1066                yoffset = readchar(f) -- signed byte, stupid name
1067            end
1068        else
1069            if band(flags,0x0001) ~= 0 then -- f_words
1070                base      = readshort(f)
1071                reference = readshort(f)
1072            else
1073                base      = readchar(f) -- signed byte, stupid name
1074                reference = readchar(f) -- signed byte, stupid name
1075            end
1076        end
1077        if band(flags,0x0008) ~= 0 then -- f_scale
1078            xscale = read2dot14(f)
1079            yscale = xscale
1080            if f_xyarg and f_offset then
1081                xoffset = xoffset * xscale
1082                yoffset = yoffset * yscale
1083            end
1084        elseif band(flags,0x0040) ~= 0 then -- f_xyscale
1085            xscale = read2dot14(f)
1086            yscale = read2dot14(f)
1087            if f_xyarg and f_offset then
1088                xoffset = xoffset * xscale
1089                yoffset = yoffset * yscale
1090            end
1091        elseif band(flags,0x0080) ~= 0 then -- f_matrix
1092            xscale  = read2dot14(f) -- xxpart
1093            xrotate = read2dot14(f) -- yxpart
1094            yrotate = read2dot14(f) -- xypart
1095            yscale  = read2dot14(f) -- yypart
1096            if f_xyarg and f_offset then
1097                xoffset = xoffset * sqrt(xscale ^2 + yrotate^2) -- was xrotate
1098                yoffset = yoffset * sqrt(xrotate^2 + yscale ^2) -- was yrotate
1099            end
1100        end
1101        nofcomponents = nofcomponents + 1
1102        components[nofcomponents] = {
1103            index      = index,
1104            usemine    = band(flags,0x0200) ~= 0, -- f_usemine
1105            round      = band(flags,0x0006) ~= 0, -- f_round,
1106            base       = base,
1107            reference  = reference,
1108            matrix     = { xscale, xrotate, yrotate, yscale, xoffset, yoffset },
1109        }
1110        if band(flags,0x0100) ~= 0 then
1111            instructions = true
1112        end
1113        if band(flags,0x0020) == 0 then -- f_more
1114            break
1115        end
1116    end
1117    return {
1118        type       = "composite",
1119        components = components,
1120    }
1121end
1122
1123-- function readers.cff(f,offset,glyphs,doshapes) -- false == no shapes (nil or true otherwise)
1124
1125-- The glyf table depends on the loca table. We have one entry to much
1126-- in the locations table (the last one is a dummy) because we need to
1127-- calculate the size of a glyph blob from the delta, although we not
1128-- need it in our usage (yet). We can remove the locations table when
1129-- we're done (todo: cleanup finalizer).
1130
1131function readers.loca(f,fontdata,specification)
1132    if specification.glyphs then
1133        local datatable = fontdata.tables.loca
1134        if datatable then
1135            -- locations are relative to the glypdata table (glyf)
1136            local offset    = fontdata.tables.glyf.offset
1137            local format    = fontdata.fontheader.indextolocformat
1138            local profile   = fontdata.maximumprofile
1139            local nofglyphs = profile and profile.nofglyphs
1140            local locations = { }
1141            setposition(f,datatable.offset)
1142            if format == 1 then
1143                if not nofglyphs then
1144                    nofglyphs = idiv(datatable.length,4) - 1
1145                end
1146                for i=0,nofglyphs do
1147                    locations[i] = offset + readulong(f)
1148                end
1149                fontdata.nofglyphs = nofglyphs
1150            else
1151                if not nofglyphs then
1152                    nofglyphs = idiv(datatable.length,2) - 1
1153                end
1154                for i=0,nofglyphs do
1155                    locations[i] = offset + readushort(f) * 2
1156                end
1157            end
1158            fontdata.nofglyphs = nofglyphs
1159            fontdata.locations = locations
1160        end
1161    end
1162end
1163
1164function readers.glyf(f,fontdata,specification) -- part goes to cff module
1165    local tableoffset = gotodatatable(f,fontdata,"glyf",specification.glyphs)
1166    if tableoffset then
1167        local locations = fontdata.locations
1168        if locations then
1169            local glyphs     = fontdata.glyphs
1170            local nofglyphs  = fontdata.nofglyphs
1171            local filesize   = fontdata.filesize
1172            local nothing    = { 0, 0, 0, 0 }
1173            local shapes     = { }
1174            local loadshapes = specification.shapes or specification.instance or specification.streams
1175            for index=0,nofglyphs-1 do
1176                local location = locations[index]
1177                local length   = locations[index+1] - location
1178                if location >= filesize then
1179                    report("discarding %s glyphs due to glyph location bug",nofglyphs-index+1)
1180                    fontdata.nofglyphs = index - 1
1181                    fontdata.badfont   = true
1182                    break
1183                elseif length > 0 then
1184                    setposition(f,location)
1185                    local nofcontours = readshort(f)
1186                    glyphs[index].boundingbox = {
1187                        readshort(f), -- xmin
1188                        readshort(f), -- ymin
1189                        readshort(f), -- xmax
1190                        readshort(f), -- ymax
1191                    }
1192                    if not loadshapes then
1193                        -- save space
1194                    elseif nofcontours == 0 then
1195                        shapes[index] = readnothing(f)
1196                    elseif nofcontours > 0 then
1197                        shapes[index] = readglyph(f,nofcontours)
1198                    else
1199                        shapes[index] = readcomposite(f,nofcontours)
1200                    end
1201                else
1202                    if loadshapes then
1203                        shapes[index] = readnothing(f)
1204                    end
1205                    glyphs[index].boundingbox = nothing
1206                end
1207            end
1208            if loadshapes then
1209                if readers.gvar then
1210                    readers.gvar(f,fontdata,specification,glyphs,shapes)
1211                end
1212                mergecomposites(glyphs,shapes)
1213                if specification.instance then
1214                    if specification.streams then
1215                        repackpoints(glyphs,shapes)
1216                    else
1217                        contours2outlines_shaped(glyphs,shapes,specification.shapes)
1218                    end
1219                elseif specification.shapes then
1220                    if specification.streams then
1221                        repackpoints(glyphs,shapes)
1222                    else
1223                        contours2outlines_normal(glyphs,shapes)
1224                    end
1225                elseif specification.streams then
1226                    repackpoints(glyphs,shapes)
1227                end
1228            end
1229        end
1230    end
1231end
1232
1233-- gvar is a bit crazy format and one can really wonder if the bit-jugling obscurity
1234-- is still needed in these days .. cff is much nicer with these blends while the ttf
1235-- coding variant looks quite horrible
1236
1237local function readtuplerecord(f,nofaxis)
1238    local record = { }
1239    for i=1,nofaxis do
1240        record[i] = read2dot14(f)
1241    end
1242    return record
1243end
1244
1245-- (1) the first is a real point the rest deltas
1246-- (2) points can be present more than once (multiple deltas then)
1247
1248local function readpoints(f)
1249    local count = readbyte(f)
1250    if count == 0 then
1251        -- second byte not used, deltas for all point numbers
1252        return nil, 0 -- todo
1253    else
1254        if count < 128 then
1255            -- no second byte, use count
1256        elseif band(count,0x80) ~= 0 then
1257            count = band(count,0x7F) * 256 + readbyte(f)
1258        else
1259            -- bad news
1260        end
1261        local points = { }
1262        local p = 0
1263        local n = 1 -- indices
1264        while p < count do
1265            local control   = readbyte(f)
1266            local runreader = band(control,0x80) ~= 0 and readushort or readbyte
1267            local runlength = band(control,0x7F)
1268            for i=1,runlength+1 do
1269                n = n + runreader(f)
1270                p = p + 1
1271                points[p] = n
1272            end
1273        end
1274        return points, p
1275    end
1276end
1277
1278local function readdeltas(f,nofpoints)
1279    local deltas = { }
1280    local p = 0
1281    while nofpoints > 0 do
1282        local control = readbyte(f)
1283        if control then
1284            local allzero   = band(control,0x80) ~= 0
1285            local runlength = band(control,0x3F) + 1
1286            if allzero then
1287                for i=1,runlength do
1288                    p = p + 1
1289                    deltas[p] = 0
1290                end
1291            else
1292                local runreader = band(control,0x40) ~= 0 and readshort or readinteger
1293                for i=1,runlength do
1294                    p = p + 1
1295                    deltas[p] = runreader(f)
1296                end
1297            end
1298            nofpoints = nofpoints - runlength
1299        else
1300            -- it happens
1301            break
1302        end
1303    end
1304    -- saves space
1305    if p > 0 then
1306        return deltas
1307    else
1308        -- forget about all zeros
1309    end
1310end
1311
1312function readers.gvar(f,fontdata,specification,glyphdata,shapedata)
1313    -- this is one of the messiest tables
1314    local instance = specification.instance
1315    if not instance then
1316        return
1317    end
1318    local factors = specification.factors
1319    if not factors then
1320        return
1321    end
1322    local tableoffset = gotodatatable(f,fontdata,"gvar",specification.variable or specification.shapes)
1323    if tableoffset then
1324        local version     = readulong(f) -- 1.0
1325        local nofaxis     = readushort(f)
1326        local noftuples   = readushort(f)
1327        local tupleoffset = tableoffset + readulong(f)
1328        local nofglyphs   = readushort(f)
1329        local flags       = readushort(f)
1330        local dataoffset  = tableoffset + readulong(f)
1331        local data        = { }
1332        local tuples      = { }
1333        local glyphdata   = fontdata.glyphs
1334        local dowidth     = not fontdata.variabledata.hvarwidths
1335        -- there is one more offset (so that one can calculate the size i suppose)
1336        -- so we could test for overflows but we simply assume sane font files
1337        if band(flags,0x0001) ~= 0  then
1338            for i=1,nofglyphs+1 do
1339                data[i] = dataoffset + readulong(f)
1340            end
1341        else
1342            for i=1,nofglyphs+1 do
1343                data[i] = dataoffset + 2*readushort(f)
1344            end
1345        end
1346        --
1347        if noftuples > 0 then
1348            setposition(f,tupleoffset)
1349            for i=1,noftuples do
1350                tuples[i] = readtuplerecord(f,nofaxis)
1351            end
1352        end
1353        local nextoffset  = false
1354        local startoffset = data[1]
1355        for i=1,nofglyphs do -- hm one more cf spec
1356            nextoffset = data[i+1]
1357            local glyph = glyphdata[i-1]
1358            local name  = trace_deltas and glyph.name
1359            if startoffset == nextoffset then
1360                if name then
1361                    report("no deltas for glyph %a",name)
1362                end
1363            else
1364                local shape = shapedata[i-1] -- todo 0
1365                if not shape then
1366                    if name then
1367                        report("no shape for glyph %a",name)
1368                    end
1369                else
1370                    lastoffset = startoffset
1371                    setposition(f,startoffset)
1372                    local flags     = readushort(f)
1373                    local count     = band(flags,0x0FFF)
1374                    local offset    = startoffset + readushort(f) -- to serialized
1375                    local deltas    = { }
1376                    local allpoints = (shape.nofpoints or 0) -- + 1
1377                    local shared    = false
1378                    local nofshared = 0
1379                    if band(flags,0x8000) ~= 0  then -- has shared points
1380                        -- go to the packed stream (get them once)
1381                        local current = getposition(f)
1382                        setposition(f,offset)
1383                        shared, nofshared = readpoints(f)
1384                        offset = getposition(f)
1385                        setposition(f,current)
1386                        -- and back to the table
1387                    end
1388                    for j=1,count do
1389                        local size         = readushort(f) -- check
1390                        local flags        = readushort(f)
1391                        local index        = band(flags,0x0FFF)
1392                        local haspeak      = band(flags,0x8000) ~= 0
1393                        local intermediate = band(flags,0x4000) ~= 0
1394                        local private      = band(flags,0x2000) ~= 0
1395                        local peak         = nil
1396                        local start        = nil
1397                        local stop         = nil
1398                        local xvalues      = nil
1399                        local yvalues      = nil
1400                        local points       = shared    -- we default to shared
1401                        local nofpoints    = nofshared -- we default to shared
1402                     -- local advance      = 4
1403                        if haspeak then
1404                            peak    = readtuplerecord(f,nofaxis)
1405                         -- advance = advance + 2*nofaxis
1406                        else
1407                            if index+1 > #tuples then
1408                                report("error, bad tuple index",index)
1409                            end
1410                            peak = tuples[index+1] -- hm, needs checking, only peak?
1411                        end
1412                        if intermediate then
1413                            start   = readtuplerecord(f,nofaxis)
1414                            stop    = readtuplerecord(f,nofaxis)
1415                         -- advance = advance + 4*nofaxis
1416                        end
1417                        -- get the deltas
1418                        if size > 0 then
1419                            local current = getposition(f)
1420                            -- goto the packed stream
1421                            setposition(f,offset)
1422                            if private then
1423                                points, nofpoints = readpoints(f)
1424                            end -- else
1425                            if nofpoints == 0 then
1426                                nofpoints = allpoints + 4
1427                            end
1428                            if nofpoints > 0 then
1429                                -- a nice test is to do only one
1430                                xvalues = readdeltas(f,nofpoints)
1431                                yvalues = readdeltas(f,nofpoints)
1432                            end
1433                            -- resync offset
1434                            offset = offset + size
1435                            -- back to the table
1436                            setposition(f,current)
1437                        end
1438                        if not xvalues and not yvalues then
1439                            points = nil
1440                        end
1441                        local s = 1
1442                        for i=1,nofaxis do
1443                            local f = factors[i]
1444                            local peak  = peak  and peak [i] or 0
1445                         -- local start = start and start[i] or 0
1446                         -- local stop  = stop  and stop [i] or 0
1447                            local start = start and start[i] or (peak < 0 and peak or 0)
1448                            local stop  = stop  and stop [i] or (peak > 0 and peak or 0) -- or 1 ?
1449--                             local stop  = stop  and stop [i] or (peak > 0 and peak or 1) -- or 1 ?
1450                            -- do we really need these tests ... can't we assume sane values
1451                            if start > peak or peak > stop then
1452                                -- * 1
1453                            elseif start < 0 and stop > 0 and peak ~= 0 then
1454                                -- * 1
1455                            elseif peak == 0 then
1456                                -- * 1
1457                            elseif f < start or f > stop then
1458                                -- * 0
1459                                s = 0
1460                                break
1461                            elseif f < peak then
1462                                s = s * (f - start) / (peak - start)
1463                            elseif f > peak then
1464                                s = s * (stop - f) / (stop - peak)
1465                            else
1466                                -- * 1
1467                            end
1468                        end
1469                        if s == 0 then
1470                            if name then
1471                                report("no deltas applied for glyph %a",name)
1472                            end
1473                        else
1474                            deltas[#deltas+1] = {
1475                                factor  = s,
1476                                points  = points,
1477                                xvalues = xvalues,
1478                                yvalues = yvalues,
1479                            }
1480                        end
1481                    end
1482                    if shape.type == "glyph" then
1483                        applyaxis(glyph,shape,deltas,dowidth)
1484                    else
1485                        -- todo: args_are_xy_values mess .. i have to be really bored
1486                        -- and motivated to deal with it
1487                        shape.deltas = deltas
1488                    end
1489                end
1490            end
1491            startoffset = nextoffset
1492        end
1493    end
1494end
1495