1if not modules then modules = { } end modules ['font-mps'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local type, tonumber, tostring = type, tonumber, tostring
10local concat, insert, remove = table.concat, table.insert, table.remove
11local formatters, match = string.formatters, string.match
12local utfbyte = utf.byte
13
14
15
16
17
18
19
20
21
22fonts = fonts or { }
23local metapost = fonts.metapost or { }
24fonts.metapost = metapost
25
26local f_moveto = formatters["(%N,%N)"]
27local f_lineto = formatters["--(%N,%N)"]
28local f_curveto = formatters["..controls(%N,%N)and(%N,%N)..(%N,%N)"]
29local s_cycle <const> = "--cycle"
30
31local f_nofill = formatters["nofill %s;"]
32local f_dofill = formatters["fill %s;"]
33
34local f_draw_trace = formatters["drawpathonly %s;"]
35local f_draw = formatters["draw %s;"]
36
37local f_rectangle = formatters["((%N,%N)--(%N,%N)--(%N,%N)--(%N,%N)--cycle)"]
38local f_line = formatters["((%N,%N)--(%N,%N))"]
39
40function metapost.boundingbox(d,factor)
41 local bounds = d.boundingbox
42 local factor = factor or 1
43 local llx = factor*bounds[1]
44 local lly = factor*bounds[2]
45 local urx = factor*bounds[3]
46 local ury = factor*bounds[4]
47 return f_rectangle(llx,lly,urx,lly,urx,ury,llx,ury)
48end
49
50function metapost.baseline(d,factor)
51 local bounds = d.boundingbox
52 local factor = factor or 1
53 local llx = factor*bounds[1]
54 local urx = factor*bounds[3]
55 return f_line(llx,0,urx,0)
56end
57
58function metapost.widthline(d,factor)
59 local bounds = d.boundingbox
60 local factor = factor or 1
61 local lly = factor*bounds[2]
62 local ury = factor*bounds[4]
63 local width = factor*d.width
64 return f_line(width,lly,width,ury)
65end
66
67function metapost.zeroline(d,factor)
68 local bounds = d.boundingbox
69 local factor = factor or 1
70 local lly = factor*bounds[2]
71 local ury = factor*bounds[4]
72 return f_line(0,lly,0,ury)
73end
74
75function metapost.paths(d,xfactor,yfactor)
76
77 local sequence = d.sequence
78 local segments = d.segments
79 local list = { }
80 local path = { }
81 local size = 0
82 local xfactor = xfactor or 1
83 local yfactor = yfactor or xfactor
84 if sequence then
85 local i = 1
86 local n = #sequence
87 if sequence[1] ~= "m" then
88 report()
89 end
90 while i < n do
91 local operator = sequence[i]
92 if operator == "m" then
93 if size > 0 then
94 size = size + 1
95 path[size] = s_cycle
96 list[#list+1] = concat(path,"",1,size)
97 size = 1
98 else
99 size = size + 1
100 end
101 path[size] = f_moveto(xfactor*sequence[i+1],yfactor*sequence[i+2])
102 i = i + 3
103 elseif operator == "l" then
104 size = size + 1
105 path[size] = f_lineto(xfactor*sequence[i+1],yfactor*sequence[i+2])
106 i = i + 3
107 elseif operator == "c" then
108 size = size + 1
109 path[size] = f_curveto(xfactor*sequence[i+1],yfactor*sequence[i+2],xfactor*sequence[i+3],yfactor*sequence[i+4],xfactor*sequence[i+5],yfactor*sequence[i+6])
110 i = i + 7
111 elseif operator =="q" then
112 size = size + 1
113
114 local l_x = xfactor*sequence[i-2]
115 local l_y = yfactor*sequence[i-1]
116 local m_x = xfactor*sequence[i+1]
117 local m_y = yfactor*sequence[i+2]
118 local r_x = xfactor*sequence[i+3]
119 local r_y = yfactor*sequence[i+4]
120 path[size] = f_curveto (
121 l_x + 2/3 * (m_x-l_x),
122 l_y + 2/3 * (m_y-l_y),
123 r_x + 2/3 * (m_x-r_x),
124 r_y + 2/3 * (m_y-r_y),
125 r_x, r_y
126 )
127 i = i + 5
128 else
129
130 i = i + 1
131 end
132 end
133 elseif segments then
134 for i=1,#segments do
135 local segment = segments[i]
136 local operator = segment[#segment]
137 if operator == "m" then
138 if size > 0 then
139 size = size + 1
140 path[size] = s_cycle
141 list[#list+1] = concat(path,"",1,size)
142 size = 1
143 else
144 size = size + 1
145 end
146 path[size] = f_moveto(xfactor*segment[1],yfactor*segment[2])
147 elseif operator == "l" then
148 size = size + 1
149 path[size] = f_lineto(xfactor*segment[1],yfactor*segment[2])
150 elseif operator == "c" then
151 size = size + 1
152 path[size] = f_curveto(xfactor*segment[1],yfactor*segment[2],xfactor*segment[3],yfactor*segment[4],xfactor*segment[5],yfactor*segment[6])
153 elseif operator == "q" then
154 size = size + 1
155
156 local prev = segments[i-1]
157 local l_x = xfactor*prev[#prev-2]
158 local l_y = yfactor*prev[#prev-1]
159 local m_x = xfactor*segment[1]
160 local m_y = yfactor*segment[2]
161 local r_x = xfactor*segment[3]
162 local r_y = yfactor*segment[4]
163 path[size] = f_curveto (
164 l_x + 2/3 * (m_x-l_x),
165 l_y + 2/3 * (m_y-l_y),
166 r_x + 2/3 * (m_x-r_x),
167 r_y + 2/3 * (m_y-r_y),
168 r_x, r_y
169 )
170 elseif operator == "close" then
171 size = size + 1
172 path[size] = s_cycle
173 list[#list+1] = concat(path,"",1,size)
174 size = 0
175 else
176
177 end
178 end
179 else
180 return
181 end
182 if size > 0 then
183 size = size + 1
184 path[size] = s_cycle
185 list[#list+1] = concat(path,"",1,size)
186 end
187 return list
188end
189
190function metapost.fill(paths)
191 local r = { }
192 local n = #paths
193 for i=1,n do
194 if i < n then
195 r[i] = f_nofill(paths[i])
196 else
197 r[i] = f_dofill(paths[i])
198 end
199 end
200 return concat(r)
201end
202
203function metapost.draw(paths,trace)
204 local r = { }
205 local n = #paths
206 for i=1,n do
207 if trace then
208 r[i] = f_draw_trace(paths[i])
209 else
210 r[i] = f_draw(paths[i])
211 end
212 end
213 return concat(r)
214end
215
216function metapost.maxbounds(data,index,factor)
217 local maxbounds = data.maxbounds
218 local factor = factor or 1
219 local glyphs = data.glyphs
220 local glyph = glyphs[index]
221 local boundingbox = glyph.boundingbox
222 local xmin, ymin, xmax, ymax
223 if not maxbounds then
224 xmin = 0
225 ymin = 0
226 xmax = 0
227 ymax = 0
228 for i=1,#glyphs do
229 local d = glyphs[i]
230 if d then
231 local b = d.boundingbox
232 if b then
233 if b[1] < xmin then xmin = b[1] end
234 if b[2] < ymin then ymin = b[2] end
235 if b[3] > xmax then xmax = b[3] end
236 if b[4] > ymax then ymax = b[4] end
237 end
238 end
239 end
240 maxbounds = { xmin, ymin, xmax, ymax }
241 data.maxbounds = maxbounds
242 else
243 xmin = maxbounds[1]
244 ymin = maxbounds[2]
245 xmax = maxbounds[3]
246 ymax = maxbounds[4]
247 end
248 local llx = boundingbox[1]
249 local lly = boundingbox[2]
250 local urx = boundingbox[3]
251 local ury = boundingbox[4]
252 local width = glyph.width
253 if llx > 0 then
254 llx = 0
255 end
256 if width > urx then
257 urx = width
258 end
259 return f_rectangle(
260 factor*llx,factor*ymin,
261 factor*urx,factor*ymin,
262 factor*urx,factor*ymax,
263 factor*llx,factor*ymax
264 )
265end
266
267
268
269
270
271local texgetbox = tex.getbox
272
273local nodecodes = nodes.nodecodes
274local rulecodes = nodes.rulecodes
275
276local rule_code <const> = nodecodes.rule
277
278local normalrule_code <const> = rulecodes.normal
279local outlinerule_code <const> = rulecodes.outline
280local userrule_code <const> = rulecodes.user
281local emptyrule_code <const> = rulecodes.empty
282
283local nuts = nodes.nuts
284
285local getexpansion = nuts.getexpansion
286local getscales = nuts.getscales
287local isglyph = nuts.isglyph
288local getglyphdimensions = nuts.getglyphdimensions
289
290local fonthashes = fonts.hashes
291local fontcharacters = fonthashes.characters
292local fontparameters = fonthashes.parameters
293local fontshapes = fonthashes.shapes
294local fontdescriptions = fonthashes.descriptions
295
296local topaths = metapost.paths
297
298local f_text = formatters["mfun_do_outline_text_flush(%q,%i,%N,%N,%q)(%,t);"]
299local f_rule = formatters["mfun_do_outline_rule_flush(%q,%N,%N,%N,%N);"]
300local f_bounds = formatters["checkbounds(%N,%N,%N,%N);"]
301local s_nothing = "(origin scaled 10)"
302
303local sc = 10
304local fc = number.dimenfactors.bp
305
306
307
308local function glyph(kind,font,char,advance,shift,ex,s,sx,sy)
309 local character = fontcharacters[font][char]
310 if character then
311 local index = character.index
312 if index then
313 local shapedata = fontshapes[font]
314 local glyphs = shapedata.glyphs
315 if glyphs then
316 local glyf = glyphs[index]
317 if glyf then
318 local units = 1000
319 local yfactor = (sc/units) * fontparameters[font].factor / 655.36
320 local xfactor = yfactor
321 local shift = shift or 0
322 local advance = advance or 0
323 local exfactor = ex or 0
324 local wfactor = 1
325 local detail = kind == "p" and tostring(char) or ""
326
327 local xoffset = character.xoffset or 0
328 local yoffset = character.yoffset or 0
329
330 if exfactor ~= 0 then
331 wfactor = (1+(ex/units)/1000)
332 xfactor = xfactor * wfactor
333 end
334 if xoffset ~= 0 then
335 advance = advance + s * sx * xoffset * fc / 1000000
336 end
337 if yoffset ~= 0 then
338 shift = shift + s * sy * yoffset * fc / 1000000
339 end
340 if s then
341 xfactor = (s/1000) * ((sx or 1000)/1000) * xfactor
342 yfactor = (s/1000) * ((sy or 1000)/1000) * yfactor
343 end
344 local paths = topaths(glyf,xfactor,yfactor)
345 if paths then
346 return f_text(kind,#paths,advance,shift,detail,paths)
347 end
348 end
349 end
350 end
351 end
352end
353
354metapost.glyph = glyph
355
356local kind = ""
357local buffer = { }
358local b = 0
359
360local function reset()
361 buffer = { }
362 b = 0
363end
364
365local function flushcharacter(current, pos_h, pos_v, pod_r, font, char)
366 if current then
367 local char, font = isglyph(current)
368 local s, sx, sy = getscales(current)
369 local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,getexpansion(current),s,sx,sy)
370 if code then
371 b = b + 1
372 buffer[b] = code
373 end
374 else
375 logs.report("mlib-fnt","check 'flushcharacter', no current, font %i, char %i", font or 0, char or 0)
376 end
377end
378
379
380
381local function flushcharacter(current,pos_h,pos_v,pos_r,font,char,data,csx,csy,factor,ssx,ssy)
382 if current then
383 local char, font = isglyph(current)
384 local s, sx, sy = getscales(current)
385 local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,getexpansion(current),s,sx,sy)
386 if code then
387 b = b + 1
388 buffer[b] = code
389 end
390 elseif font and char then
391 if not data then
392 data = fontcharacters[font][char] or { }
393 end
394 if data then
395 local width = data.width or 0
396 local height = data.height or 0
397 local depth = data.depth or 0
398 local sx = 1
399 local sy = 1
400 if csx then sx = sx * csx end
401 if csy then sy = sy * csy end
402 if ssx then sx = sx * ssx end
403 if ssy then sy = sy * ssy end
404 local code = glyph(kind,font,char,pos_h*fc,pos_v*fc,factor,s,sx,sy)
405 if code then
406 b = b + 1
407 buffer[b] = code
408 end
409 else
410 logs.report("mlib-fnt","no font %i with char %i", font, char)
411 end
412
413 else
414 logs.report("mlib-fnt","no current, font, and/or char")
415 end
416end
417
418local function flushrule(current,pos_h,pos_v,pos_r,size_h,size_v,subtype)
419 if subtype == normalrule_code then
420 b = b + 1
421 buffer[b] = f_rule(kind,pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
422 elseif subtype == outlinerule_code then
423 b = b + 1
424 buffer[b] = f_rule("d",pos_h*fc,pos_v*fc,size_h*fc,size_v*fc)
425 elseif subtype == userrule_code then
426
427
428
429 elseif subtype == emptyrule_code then
430
431 else
432
433
434 end
435end
436
437local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
438 flushrule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code)
439end
440
441local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
442 flushrule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
443end
444
445
446
447drivers.install {
448 name = "mpo",
449 actions = {
450 initialize = function()
451 reset()
452 end,
453 finalize = function(driver,details)
454 local bb = details.boundingbox
455 local llx = bb[1] * fc
456 local lly = bb[2] * fc
457 local urx = bb[3] * fc
458 local ury = bb[4] * fc
459 b = b + 1
460 buffer[b] = f_bounds(llx,lly,urx,ury)
461
462 end,
463 },
464 flushers = {
465 updatefontstate = updatefontstate,
466 character = flushcharacter,
467 rule = flushrule,
468 simplerule = flushsimplerule,
469 specialrule = flushspecialrule,
470 }
471}
472
473function metapost.boxtomp(n,k)
474 kind = k
475 nodes.handlers.finalizebox(n,false)
476 drivers.converters.lmtx(drivers.instances.mpo,texgetbox(n),"box",1)
477 local result = concat(buffer,";")
478 reset()
479 return result
480end
481
482
483
484local loaded = table.setmetatableindex(function(t,k)
485 local v = fonts.definers.internal({ name = k } ,"<lmt:glyphshape:font>")
486 t[k] = v
487 return v
488end)
489
490local mpdata = 0
491local mpstack = { }
492
493function mp.lmt_glyphshape_start(id,character)
494 if type(id) == "string" then
495 id = loaded[id]
496 end
497 local fontid = (id and id ~= 0 and id) or font.current()
498 local shapedata = fontshapes [fontid]
499 local characters = fontcharacters [fontid]
500 local descriptions = fontdescriptions[fontid]
501 local mathgaps = mathematics.gaps
502 local shapeglyphs = shapedata.glyphs or { }
503 if type(character) == "string" and character ~= "" then
504 local hex = match(character,"^0x(.+)")
505 if hex then
506 character = tonumber(hex,16)
507 else
508 character = utfbyte(character)
509 end
510 else
511 character = tonumber(character)
512 end
513 local unicode = mathgaps[character] or character
514 local chardata = characters[unicode]
515 local descdata = descriptions[unicode]
516 if chardata then
517 glyph = shapeglyphs[chardata.index]
518 if glyph then
519 mpdata = glyph.mpdata
520 if not mpdata then
521 if glyph.segments or glyph.sequence then
522 local units = shapedata.units or 1000
523 local factor = 100/units
524 local width = (descdata.width or 0) * factor
525 local height = descdata.boundingbox[4] * factor
526 local depth = descdata.boundingbox[2] * factor
527 local llx = descdata.boundingbox[1] * factor
528 local math = descdata.math
529 local italic = (math and math.italic or 0) * factor
530 local accent = (math and math.accent or 0) * factor
531 mpdata = {
532 paths = metapost.paths(glyph,factor),
533 boundingbox = metapost.boundingbox(glyph,factor),
534 baseline = metapost.baseline(glyph,factor),
535 width = width,
536 height = height,
537 depth = depth,
538 italic = italic,
539 accent = accent,
540 llx = llx,
541 usedbox = f_rectangle(llx,depth,llx+width,depth,llx+width,height,llx,height),
542 usedline = f_line(llx,0,llx+width,0),
543 }
544 glyph.mpdata = mpdata
545 else
546 print("CHECK 1",id,character)
547 end
548 end
549 end
550 else
551 print("CHECK 2",id,character)
552 end
553 insert(mpstack, mpdata)
554end
555
556local mpprint = mp.print
557local injectpair = mp.inject.pair
558local injectnumeric = mp.inject.numeric
559
560function mp.lmt_glyphshape_stop()
561 mpdata = remove(mpstack)
562end
563
564function mp.lmt_glyphshape_n()
565 if mpdata then
566 mpprint(#mpdata.paths)
567 else
568 injectnumeric(0)
569 end
570end
571
572function mp.lmt_glyphshape_path(i)
573 if mpdata then
574 mpprint(mpdata.paths[i])
575 else
576 injectpair(0,0)
577 end
578end
579
580function mp.lmt_glyphshape_boundingbox()
581 if mpdata then
582 mpprint(mpdata.boundingbox)
583 else
584 injectpair(0,0)
585 end
586end
587function mp.lmt_glyphshape_usedbox()
588 if mpdata then
589 mpprint(mpdata.usedbox)
590 else
591 injectpair(0,0)
592 end
593end
594
595function mp.lmt_glyphshape_baseline()
596 if mpdata then
597 mpprint(mpdata.baseline)
598 else
599 injectpair(0,0)
600 end
601end
602function mp.lmt_glyphshape_usedline()
603 if mpdata then
604 mpprint(mpdata.usedline)
605 else
606 injectpair(0,0)
607 end
608end
609
610function mp.lmt_glyphshape_width () injectnumeric(mpdata and mpdata.width or 0) end
611function mp.lmt_glyphshape_depth () injectnumeric(mpdata and mpdata.depth or 0) end
612function mp.lmt_glyphshape_height() injectnumeric(mpdata and mpdata.height or 0) end
613function mp.lmt_glyphshape_italic() injectnumeric(mpdata and mpdata.italic or 0) end
614function mp.lmt_glyphshape_accent() injectnumeric(mpdata and mpdata.accent or 0) end
615function mp.lmt_glyphshape_llx () injectnumeric(mpdata and mpdata.llx or 0) end
616 |