summaryrefslogtreecommitdiff
path: root/doc/context/sources/general/manuals/hybrid/hybrid-goodies.tex
blob: f6a2317f0b749baa18acc590bde4b5946e37b825 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
% language=uk

\usetypescriptfile[type-husayni]

\startcomponent hybrid-goodies

\environment hybrid-environment

% this will change

\definefontfeature
  [husayni-none]
  [analyze=yes,mode=node,
   language=dflt,script=arab,
   ccmp=yes]

\definefontfeature
  [husayni-default]
  [analyze=yes,mode=node,
   language=dflt,script=arab,
   ccmp=yes,init=yes,medi=yes,fina=yes,
   rlig=yes,calt=yes,salt=yes,anum=yes,
   kern=yes,curs=yes,mark=yes,mkmk=yes,
   ss01=yes,ss03=yes,ss10=yes,ss12=yes,ss15=yes,ss16=yes,
   ss19=yes,ss24=yes,ss25=yes,ss26=yes,ss27=yes,ss31=yes,
   ss34=yes,ss35=yes,ss36=yes,ss37=yes,ss38=yes,ss41=yes,
   ss43=yes]

\definefontfeature
  [husayni-first-order]
  [script=arab,ss01=yes,ss03=yes,ss05=yes,
   ss10=yes,ss12=yes,ss15=yes,ss16=yes,ss19=yes,ss24=yes,
   ss25=yes,ss26=yes,ss27=yes,ss31=yes,ss34=yes,ss35=yes,
   ss36=yes,ss37=yes,ss38=yes,ss41=yes,ss42=yes,ss43=yes]

\definefontfeature
  [husayni-stack-jiim-multi-level]
  [script=arab,ss05=yes]

\definefontfeature
  [husayni-minimal-stretching]
  [script=arab,
   ss05=yes,ss09=yes,ss06=yes,ss13=yes,ss17=yes,ss40=yes,
   js11=yes,js14=yes,js16=yes]

\definefontfeature
  [husayni-maximal-stretching]
  [script=arab,
   ss05=yes,ss09=yes,ss06=yes,ss13=yes,ss17=yes,ss40=yes,
   js13=yes,js14=yes,js16=yes]

\definefontfeature
  [husayni-chop-haa]
  [script=arab,
   ss05=yes,ss09=yes,ss06=yes,ss13=yes,ss17=yes,ss54=yes]

\definefontfeature
  [husayni-normal]
  [goodies=husayni,
   featureset=default]

\definefont[ArabicFontNone][husayni*husayni-none    at 40pt]
\definefont[ArabicFontFull][husayni*husayni-default at 40pt] % husayni-normal

\startchapter[title={Font Goodies}]

\startsection[title={Introduction}]

The Oriental \TEX\ project is one of the first and more ambitious users of
\LUATEX. A major undertaking in this project is the making of a rather full
features and complex font for typesetting Arabic. As the following text will show
some Arabic, you might get the impression that I'm an expert but be warned that
I'm far from that. But as Idris compensates this quite well the team has a lot of
fun in figuring out how to achieve our goals using \OPENTYPE\ technology in
combination with \LUATEX\ and \MKIV. A nice side effect of this is that we end up
with some neat tricks in the \CONTEXT\ core.

Before we come to some of these goodies, an example of Arabic is given that
relates quite well to the project. It was first used at the euro\TEX\ 2009
meeting. Take the following 6 shapes:

\starttabulate[|c|c|c|c|c|c|]
\NC \ArabicFontFull ل \NC \ArabicFontFull و \NC \ArabicFontFull ا \NC \ArabicFontFull ت \NC \ArabicFontFull ي \NC \ArabicFontFull خ \NC \NR
\NC \type{l} \NC \type{w} \NC \type{ā} \NC \type{t} \NC \type{ī} \NC \type{kh} \NC \NR
\stoptabulate

With these we can make the name \LUATEX\ and as we use a nice script we can
forget about the lowered~E. Putting these characters in sequence is not enough as
Arabic typesetting has to mimick the subtle aspects of scribes.

In Latin scripts we have mostly one|-|to|-|one and many|-|to|-|one substitutions.
These can happen in sequence which in in practice boils down to multiple passes
over the stream of characters. In this process sometimes surrounding characters
(or shapes) play a role, for instance ligatures are not always wanted and their
coming into existence might depend on neighbouring characters. In some cases
glyphs have to be (re)positioned relative to each other. While in Latin scripts
the number of substitutions and positioning is not that large but in advanced
Arabic fonts it can be pretty extensive.

With \OPENTYPE\ we have some machinery available, so we try to put as much logic
in the font as possible. However, in addition we have some dedicated optimizing
routines. The whole process is split into a couple if stages.

The so called First|-|Order Analysis puts a given character into isolated,
initial, middle, or final state. Next, the Second|-|Order Analysis looks at the
characters and relates this state to what characters precede or succeed it. Based
on that state we do character substitutions. There can be multiple analysis and
replacements in sequence. We can do some simple aesthetic stretching and
additional related replacements. We need to attach identity marks and vowels in
proper but nice looking places. In most cases we're then done. Contrary to other
fonts we don't use many ligatures but compose characters.

The previous steps already give reasonable results and implementing it also
nicely went along with the development of \LUATEX\ and \CONTEXT\ \MKIV. Currently
we're working on extending and perfecting the font to support what we call
Third|-|Order Contextual Analysis. This boils down to an interplay between the
paragraph builder and additional font features. In order to get pleasing spacing
we apply further substitutions, this time with wider or narrower shapes. When
this is done we need to reattach identity marks and vowels. Optionally we can
apply \HZ\ like stretching as a finishing touch but so far we didn't follow that
route yet.

So, let's see how we can typeset the word \LUATEX\ in Arabic using some of these
techniques.

\startlines
no order (kh ī t ā w [u] l)\hfilll {\righttoleft\ArabicFontNone لُواتيخ}
first order \hfilll {\subff{husayni-first-order}\righttoleft\ArabicFontFull لُواتيخ}
second order \hfilll {\righttoleft\ArabicFontFull لُواتيخ}
second order (Jiim-stacking) \hfilll {\addff{husayni-stack-jiim-multi-level}\righttoleft\ArabicFontFull لُواتيخ}
minimal stretching \hfilll {\addff{husayni-minimal-stretching}\righttoleft\ArabicFontFull لُواتيخ}
maximal stretching (level 3) \hfilll {\addff{husayni-maximal-stretching}\righttoleft\ArabicFontFull لُواتيخ}
chopped letter khaa (for e.g.\ underlining) \hfilll {\addff{husayni-chop-haa}\righttoleft\ArabicFontFull لُواتيخ}
\stoplines

As said, this font is quite complex in the sense that it has many features and
associated lookups. In addition to the usual features we have stylistic and
justification variants. As these are not standardized (after all, each font can
have its own look and feel and associated treatments) we store some information
in the goodies files that ship with this font.

\startbuffer[stylistics]
\startluacode
  local goodies = fonts.goodies.load("husayni")
  local stylistics = goodies and goodies.stylistics
  if stylistics then
    local col, row, type = context.NC, context.NR, context.type
    context.starttabulate { "|l|pl|" }
    col() context("feature") col() context("meaning") col() row()
    for feature, meaning in table.sortedpairs(stylistics) do
      col() type(feature) col() type(meaning) col() row()
    end
    context.stoptabulate()
  end
\stopluacode
\stopbuffer

\getbuffer[stylistics]

It is highly unlikely that a user will remember all these features, which is why
there will be a bunch of predefined combinations. These are internalized as
follows:

\startbuffer[featuresets]
\startluacode
  local goodies = fonts.goodies.load("husayni")
  local featuresets = goodies and goodies.featuresets
  if featuresets then
    local col, row, type = context.NC, context.NR, context.type
    context.starttabulate { "|l|pl|" }
    col() context("featureset") col() context("definitions") col() row()
    for featureset, definitions in table.sortedpairs(featuresets) do
      col() type(featureset) col()
      for k, v in table.sortedpairs(definitions) do
        type(string.format("%s=%s",k,tostring(v)))
        context.quad()
      end
      col() row()
    end
    context.stoptabulate()
  end
\stopluacode
\stopbuffer

\getbuffer[featuresets]

\stopsection

\startsection[title={Color}]

One of the objectives of the oriental \TEX\ project is to bring color to typeset
Arabic. When Idris started making samples with much manual intervention it was
about time to figure out if it could be supported by a bit of \LUA\ code.

As the colorization concerns classes of glyphs (like vowels) this is something
that can best be done after all esthetics have been sorted out. Because things
like coloring are not part of font technology and because we don't want to misuse
the \OPENTYPE\ feature mechanisms for that, the solution lays in an extra file
that describes these goodies.

\startbuffer[goodies-1]
\definefontfeature
  [husayni-colored]
  [goodies=husayni,
   colorscheme=default,
   featureset=default]
\stopbuffer

\startbuffer[goodies-2]
\start
  \definedfont[husayni*husayni-colored at 72pt]
  \righttoleft
  \resetfontcolorscheme   لُواتيخ ألف ليلة وليلة \par
  \setfontcolorscheme  [1]لُواتيخ ألف ليلة وليلة \crlf
  \setfontcolorscheme  [2]لُواتيخ ألف ليلة وليلة \crlf
\stop
\stopbuffer

\getbuffer[goodies-1,goodies-2]

The second and third of these three lines have colored vowels and identity marks.
So how did we get the colors? There are actually two mechanisms involved in this:

\startitemize[packed]
\startitem we need to associate colorschemes with classed of glyphs \stopitem
\startitem we need to be able to turn on and off coloring \stopitem
\stopitemize

The first is done by loading goodies and selecting a colorscheme:

\typebuffer[goodies-1]

Turning on and off coloring is done with two commands (we might provide a proper
environment for this) as shown in:

\typebuffer[goodies-2]

If you look closely at the feature definition you'll notice that we also choose a
default featureset. For most (latin) fonts the regular feature definitions are
convenient, but for fonts that are used for Arabic there are preferred
combinations of features as there can be many.

Currently the font we use here has the following colorschemes:

\startbuffer[colorschemes]
\startluacode
  local goodies = fonts.goodies.load("husayni")
  local colorschemes = goodies and goodies.colorschemes
  if colorschemes then
    local col, row, type = context.NC, context.NR, context.type
    context.starttabulate { "|l|pl|" }
    col() context("colorscheme") col() context("numbers") col() row()
    for colorscheme, numbers in table.sortedpairs(colorschemes) do
      col() type(colorscheme) col()
      for i=1,#numbers do
        type(i)
        context.quad()
      end
      col() row()
    end
    context.stoptabulate()
  end
\stopluacode
\stopbuffer

\getbuffer[colorschemes]

\stopsection

\startsection[title={The goodies file}]

In principle a goodies files can contain anuy data that makes sense but in order
to be useable some entries have a prescribed structure. A goodies file looks as
follows:

\starttyping
return {
  name = "husayni",
  version = "1.00",
  comment = "Goodies that complement the Husayni font by Idris Samawi Hamid.",
  author = "Idris Samawi Hamid and Hans Hagen",
  featuresets = {
    default = {
      key = value, <table>, ...
    },
    ...
  },
  stylistics = {
    key = value, ...
  },
  colorschemes = {
    default = {
      [1] = {
        "glyph_a.one", "glyph_b.one", ...
      },
      ...
    }
  }
}
\stoptyping

We already saw the list of special features and these are defined in the \type
{stylistics} stable. In this document, that list was typeset using the following
(hybrid) code:

\typebuffer[stylistics]

The table with colorscheme that we showed is generated with:

\getbuffer[colorschemes]

In a similar fashion we typeset the featuresets:

\typebuffer[featuresets]

The unprocessed \type {featuresets} table can contain one or more
named sets and each set can be a mixture of tables and key value
pairs. Say that we have:

\starttyping
  default = {
    kern = "yes", { ss01 = "yes" }, { ss02 = "yes" }, "mark"
  }
\stoptyping

Given the previous definition, the order of processing is as follows.

\startitemize[packed,n]
\startitem \type {{ ss01 = "yes" }} \stopitem
\startitem \type {{ ss02 = "yes" }} \stopitem
\startitem \type {mark} (set to \type {"yes"}) \stopitem
\startitem \type {kern = "yes"} \stopitem
\stopitemize

So, first we process the indexed part if the list, and next the hash. Already set
values are not set again. The advantage of using a \LUA\ table is that you can
simplify definitions. Before we return the table we can define local variables,
like:

\starttyping
local one = { ss01 = "yes" }
local two = { ss02 = "yes" }
local pos = { kern = "yes", mark = "yes" }
\stoptyping

and use them in:

\starttyping
default = {
  one, two, pos
}
\stoptyping

That way we we can conveniently define all kind of interesting combinations
without the need for many repetitive entries.

The \type {colorsets} table has named subtables that are (currently) indexed by
number. Each number is associated with a color (at the \TEX\ end) and is coupled
to a list of glyphs. As you can see here, we use the name of the glyph. We prefer
this over an index (that can change during development of the font). We cannot
use \UNICODE\ points as many such glyphs are just variants and have no unique
code.

\stopsection

\startsection[title={Optimizing Arabic}]

\usemodule[abr-01,narrowtt]

\enabletrackers[fonts.goodies,nodes.optimizer]

The ultimate goal of the Oriental \TEX\ project is to improve the look and feel
of a paragraph. Because \TEX\ does a pretty good job on breaking the paragraph
into lines, and because complicating the paragraph builder is not a good idea, we
finally settled on improving the lines that result from the par builder. This
approach is rather close to what scribes do and the advanced Husayni font
provides features that support this.

In principle the current optimizer can replace character expansion but that would
slow down considerably. Also, for that we first have to clean up the experimental
\LUA\ based par builder.

After several iterations the following approach was chosen.

\startitemize

\startitem
    We typeset the paragraph with an optimal feature set. In our case this is
    \type {husayni-default}.
\stopitem

\startitem
    Next we define two sets of additional features: one that we can apply to
    shrink words, and one that does the opposite.
\stopitem

\startitem
    When the line has a badness we don't like, we either stepwise shrink words or
    stretch them, depending on how bad things are.
\stopitem

\stopitemize

The set that takes care of shrinking is defined as:

\starttyping
\definefontfeature
  [shrink]
  [husayni-default]
  [flts=yes,js17=yes,ss05=yes,ss11=yes,ss06=yes,ss09=yes]
\stoptyping

Stretch has a few more variants:

\starttyping
\definefontfeature
  [minimal_stretching]
  [husayni-default]
  [js11=yes,js03=yes]
\definefontfeature
  [medium_stretching]
  [husayni-default]
  [js12=yes,js05=yes]
\definefontfeature
  [maximal_stretching]
  [husayni-default]
  [js13=yes,js05=yes,js09=yes]
\definefontfeature
  [wide_all]
  [husayni-default]
  [js11=yes,js12=yes,js13=yes,js05=yes,js09=yes]
\stoptyping

Next we define a font solution:

\starttyping
\definefontsolution
  [FancyHusayni]
  [goodies=husayni,
   less=shrink,
   more={minimal_stretching,medium_stretching,maximal_stretching,wide_all}]
\stoptyping

Because these featuresets relate quite closely to the font design we don't use
this way if defining but put the definitions in the goodies file:

\startntyping
    .....
    featuresets = { -- here we don't have references to featuresets
        default = {
            default,
        },
        minimal_stretching = {
            default, js11 = yes, js03 = yes,
        },
        medium_stretching = {
            default, js12=yes, js05=yes,
        },
        maximal_stretching= {
            default, js13 = yes, js05 = yes, js09 = yes,
        },
        wide_all = {
            default, js11 = yes, js12 = yes, js13 = yes, js05 = yes, js09 = yes,
        },
        shrink = {
            default, flts = yes, js17 = yes, ss05 = yes, ss11 = yes, ss06 = yes, ss09 = yes,
        },
    },
    solutions = { -- here we have references to featuresets, so we use strings!
        experimental = {
            less = { "shrink" },
            more = { "minimal_stretching", "medium_stretching", "maximal_stretching", "wide_all" },
        },
    },
    .....
\stopntyping

Now the definition looks much simpler:

\startbuffer
\definefontsolution
  [FancyHusayni]
  [goodies=husayni,
   solution=experimental]
\stopbuffer

% unhbox to show stretch - shrink

\typebuffer \getbuffer

{\em I want some funny text (complete with translation). Actually I want all
examples translated.}

\startbuffer[sample]
قد صعدنا
ذرى الحقائق بأقدام النبوة و الولاية و نورنا
سبع طبقات أعلام الفتوى بالهداية فنحن ليوث
الوغى و غيوث الندى و طعان العدى و فينا السيف و
القلم في العاجل و لواء الحمد
و الحوض في الآجل و أسباطنا حلفاء
الدين و خلفاء النبيين و مصابيح الأمم و مفاتيح
الكرم فالكليم ألبس حلة الاصطفاء لما عهدنا
منه الوفاء و روح القدس في جنان الصاقورة ذاق من
حدائقنا الباكورة و شيعتنا الفئة الناجية و
الفرقة الزاكية و صاروا لنا ردءا و صونا و على
الظلمة ألبا و عونا و سينفجر لهم ينابيع
الحيوان بعد لظى النيران لتمام آل حم و طه و
الطواسين من السنين و هذا الكتاب درة من درر
الرحمة و قطرة من بحر الحكمة و كتب الحسن بن
علي العسكري في سنة أربع و خمسين و مائتين
\stopbuffer

\startbuffer
\definedfont[husayni*husayni-default at 24pt]
% todo: factor ivm grid, so the next line looks hackery:
\expanded{\setuplocalinterlinespace[line=\the\dimexpr2\lineheight]}
\setfontsolution[FancyHusayni]% command will change
\enabletrackers[builders.paragraphs.solutions.splitters.colors]
\righttoleft \getbuffer[sample] \par
\disabletrackers[builders.paragraphs.solutions.splitters.colors]
\resetfontsolution
\stopbuffer

In the following example the yellow words are stretched and the green ones are
shrunken.\footnote {Make sure that the paragraph is finished (for instance using
\type {\par} before resetting it.)}

\typebuffer

\start \getbuffer \stop

% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution
% \setfontsolution[FancyHusayni]x\par\resetfontsolution

% \startbuffer[sample]
% \dorecurse{50}{الحمد \recurselevel\space}
% \stopbuffer

This mechanism is somewhat experimental as is the (user) interface. It is also
rather slow compared to normal processing. There is room for improvement but I
will do that when other components are more stable so that simple variants (that
we can use here) can be derived.

When criterium~0 used above is changed into for instance~5 processing is faster.
When you enable a preroll processing is more time consuming. Examples of settings
are:

\starttyping
\setupfontsolutions[method={preroll,normal},criterium=2]
\setupfontsolutions[method={preroll,random},criterium=5]
\setupfontsolutions[method=reverse,criterium=8]
\setupfontsolutions[method=random,criterium=2]
\stoptyping

Using a preroll is slower because it first tries all variants and then settles
for the best; otherwise we process the first till the last solution till the
criterium is satisfied.

% {\em Todo: show normal, reverse and random.}
% {\em Todo: bind setting to paragraph.}

\stopsection

\startsection[title={Protrusion and expansion}]

There are two entries in the goodies file that relate to advanced parbuilding:
\type {protrusions} and \type {expansions}.

\starttyping
protrusions = {
  vectors = {
    pure = {
      [0x002C] = { 0, 1 }, -- comma
      [0x002E] = { 0, 1 }, -- period
      .....
    }
  }
}
\stoptyping

These vectors are similar to the ones defined globally but the vectors defined in
a goodie file are taken instead when present.

\stopsection

\startsection[title={Filenames and properties}]

As filenames and properties of fonts are somewhat of an inconsistent mess, we can
use the goodies to provide more information:

\starttyping
files = {
  name = "antykwapoltawskiego", -- shared
  list = {
      ["AntPoltLtCond-Regular.otf"] = {
       -- name   = "antykwapoltawskiego",
          style  = "regular",
          weight = "light",
          width  = "condensed",
      },
      .....
    }
  }
}
\stoptyping

Internally this will become a lookup tree so that we can have a predictable
specifier:

\starttyping
\definefont[MyFontA][antykwapoltawskiego-bold-italic]
\definefont[MyFontB][antykwapoltawskiego-normal-italic-condensed]
\definefont[MyFontC][antykwapoltawskiego-light-regular-semicondensed]
\stoptyping

Of course one needs to load the goodies. One way to force that is:

\starttyping
\loadfontgoodies[antykwapoltawskiego]
\stoptyping

The Antykwa Poltawskiego family is rather large and provides all kind of
combinations.

\startbuffer
\usemodule[fonts-goodies]
\showfontgoodiesfiles[name=antykwapoltawskiego]
\stopbuffer

\startpacked
\getbuffer
\stoppacked

This list is generated with:

\typebuffer

\stopsection

\stopchapter

\stopcomponent