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