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