2 * misc.c: miscellaneous useful items
16 s
= mknew(struct stackTag
);
24 void stk_free(stack s
) {
29 void stk_push(stack s
, void *item
) {
30 if (s
->size
<= s
->sp
) {
32 s
->data
= resize(s
->data
, s
->size
);
34 s
->data
[s
->sp
++] = item
;
37 void *stk_pop(stack s
) {
39 return s
->data
[--s
->sp
];
44 void *stk_top(stack s
) {
46 return s
->data
[s
->sp
-1];
52 * Small routines to amalgamate a string from an input source.
54 const rdstring empty_rdstring
= {0, 0, NULL
};
55 const rdstringc empty_rdstringc
= {0, 0, NULL
};
57 void rdadd(rdstring
*rs
, wchar_t c
) {
58 if (rs
->pos
>= rs
->size
-1) {
59 rs
->size
= rs
->pos
+ 128;
60 rs
->text
= resize(rs
->text
, rs
->size
);
62 rs
->text
[rs
->pos
++] = c
;
63 rs
->text
[rs
->pos
] = 0;
65 void rdadds(rdstring
*rs
, wchar_t const *p
) {
67 if (rs
->pos
>= rs
->size
- len
) {
68 rs
->size
= rs
->pos
+ len
+ 128;
69 rs
->text
= resize(rs
->text
, rs
->size
);
71 ustrcpy(rs
->text
+ rs
->pos
, p
);
74 wchar_t *rdtrim(rdstring
*rs
) {
75 rs
->text
= resize(rs
->text
, rs
->pos
+ 1);
79 void rdaddc(rdstringc
*rs
, char c
) {
80 if (rs
->pos
>= rs
->size
-1) {
81 rs
->size
= rs
->pos
+ 128;
82 rs
->text
= resize(rs
->text
, rs
->size
);
84 rs
->text
[rs
->pos
++] = c
;
85 rs
->text
[rs
->pos
] = 0;
87 void rdaddsc(rdstringc
*rs
, char const *p
) {
89 if (rs
->pos
>= rs
->size
- len
) {
90 rs
->size
= rs
->pos
+ len
+ 128;
91 rs
->text
= resize(rs
->text
, rs
->size
);
93 strcpy(rs
->text
+ rs
->pos
, p
);
96 char *rdtrimc(rdstringc
*rs
) {
97 rs
->text
= resize(rs
->text
, rs
->pos
+ 1);
101 static int compare_wordlists_literally(word
*a
, word
*b
) {
104 if (a
->type
!= b
->type
)
105 return (a
->type
< b
->type ?
-1 : +1); /* FIXME? */
107 if ((t
!= word_Normal
&& t
!= word_Code
&&
108 t
!= word_WeakCode
&& t
!= word_Emph
) ||
111 if (a
->text
&& b
->text
) {
112 c
= ustricmp(a
->text
, b
->text
);
116 c
= compare_wordlists_literally(a
->alt
, b
->alt
);
122 wchar_t *ap
= a
->text
, *bp
= b
->text
;
124 wchar_t ac
= utolower(*ap
), bc
= utolower(*bp
);
126 return (ac
< bc ?
-1 : +1);
127 if (!*++ap
&& a
->next
&& a
->next
->type
== t
&& !a
->next
->alt
)
128 a
= a
->next
, ap
= a
->text
;
129 if (!*++bp
&& b
->next
&& b
->next
->type
== t
&& !b
->next
->alt
)
130 b
= b
->next
, bp
= b
->text
;
133 return (*ap ?
+1 : -1);
140 return (a ?
+1 : -1);
145 int compare_wordlists(word
*a
, word
*b
) {
147 * First we compare only the alphabetic content of the word
148 * lists, with case not a factor. If that comes out equal,
149 * _then_ we compare the word lists literally.
159 pos
[0].i
= pos
[1].i
= 0;
163 * Find the next alphabetic character in each word list.
167 for (k
= 0; k
< 2; k
++) {
169 * Advance until we hit either an alphabetic character
170 * or the end of the word list.
174 /* End of word list. */
177 } else if (!pos
[k
].w
->text
|| !pos
[k
].w
->text
[pos
[k
].i
]) {
178 /* No characters remaining in this word; move on. */
179 pos
[k
].w
= pos
[k
].w
->next
;
181 } else if (!uisalpha(pos
[k
].w
->text
[pos
[k
].i
])) {
182 /* This character isn't alphabetic; move on. */
185 /* We have an alphabetic! Lowercase it and continue. */
186 pos
[k
].c
= utolower(pos
[k
].w
->text
[pos
[k
].i
]);
192 if (pos
[0].c
< pos
[1].c
)
194 else if (pos
[0].c
> pos
[1].c
)
198 break; /* they're equal */
205 * If we reach here, the strings were alphabetically equal, so
206 * compare in more detail.
208 return compare_wordlists_literally(a
, b
);
211 void mark_attr_ends(paragraph
*sourceform
) {
214 for (p
= sourceform
; p
; p
= p
->next
) {
216 for (w
= p
->words
; w
; w
= w
->next
) {
217 if (isattr(w
->type
)) {
218 int before
= (wp
&& isattr(wp
->type
) &&
219 sameattr(wp
->type
, w
->type
));
220 int after
= (w
->next
&& isattr(w
->next
->type
) &&
221 sameattr(w
->next
->type
, w
->type
));
223 (after ? attr_Always
: attr_Last
) :
224 (after ? attr_First
: attr_Only
));
232 * This function implements the optimal paragraph wrapping
233 * algorithm, pretty much as used in TeX. A cost function is
234 * defined for each line of the wrapped paragraph (typically some
235 * convex function of the difference between the line's length and
236 * its desired length), and a dynamic programming approach is used
237 * to optimise globally across all possible layouts of the
238 * paragraph to find the one with the minimum total cost.
240 * The function as implemented here gives a choice of two options
241 * for the cost function:
243 * - If `natural_space' is zero, then the algorithm attempts to
244 * make each line the maximum possible width (either `width' or
245 * `subsequentwidth' depending on whether it's the first line of
246 * the paragraph or not), and the cost function is simply the
247 * square of the unused space at the end of each line. This is a
248 * simple mechanism suitable for use in fixed-pitch environments
249 * such as plain text displayed on a terminal.
251 * - However, if `natural_space' is positive, the algorithm
252 * assumes the medium is fully graphical and that the width of
253 * space characters can be adjusted finely, and it attempts to
254 * make each _space character_ the width given in
255 * `natural_space'. (The provided width function should return
256 * the _minimum_ acceptable width of a space character in this
257 * case.) Therefore, the cost function for a line is dependent
258 * on the number of spaces on that line as well as the amount by
259 * which the line width differs from the optimum.
261 wrappedline
*wrap_para(word
*text
, int width
, int subsequentwidth
,
262 int (*widthfn
)(void *, word
*), void *ctx
,
264 wrappedline
*head
= NULL
, **ptr
= &head
;
265 int nwords
, wordsize
;
276 * Break the line up into wrappable components.
278 nwords
= wordsize
= 0;
281 if (nwords
>= wordsize
) {
282 wordsize
= nwords
+ 64;
283 wrapwords
= srealloc(wrapwords
, wordsize
* sizeof(*wrapwords
));
285 wrapwords
[nwords
].width
= 0;
286 wrapwords
[nwords
].begin
= text
;
288 wrapwords
[nwords
].width
+= widthfn(ctx
, text
);
289 wrapwords
[nwords
].end
= text
->next
;
290 if (text
->next
&& (text
->next
->type
== word_WhiteSpace
||
291 text
->next
->type
== word_EmphSpace
||
296 if (text
&& text
->next
&& (text
->next
->type
== word_WhiteSpace
||
297 text
->next
->type
== word_EmphSpace
)) {
298 wrapwords
[nwords
].spacewidth
= widthfn(ctx
, text
->next
);
301 wrapwords
[nwords
].spacewidth
= 0;
309 * Perform the dynamic wrapping algorithm: work backwards from
310 * nwords-1, determining the optimal wrapping for each terminal
311 * subsequence of the paragraph.
313 for (i
= nwords
; i
-- ;) {
317 int linelen
= 0, spacewidth
= 0, minspacewidth
= 0;
319 int thiswidth
= (i
== 0 ? width
: subsequentwidth
);
323 while (i
+j
< nwords
) {
325 * See what happens if we put j+1 words on this line.
329 minspacewidth
= spacewidth
;
331 linelen
+= spacewidth
+ wrapwords
[i
+j
].width
;
332 spacewidth
= wrapwords
[i
+j
].spacewidth
;
334 if (linelen
> thiswidth
) {
336 * If we're over the width limit, abandon ship,
337 * _unless_ there is no best-effort yet (which will
338 * only happen if the first word is too long all by
346 * Compute the cost of this line. The method of doing
347 * this differs hugely depending on whether
348 * natural_space is nonzero or not.
351 if (!nspaces
&& linelen
> thiswidth
) {
353 * Special case: if there are no spaces at all
354 * on the line because one single word is too
355 * long for its line, cost is zero because
356 * there's nothing we can do about it anyway.
360 int shortfall
= thiswidth
- linelen
;
361 int spaceextra
= shortfall
/ (nspaces ? nspaces
: 1);
362 int spaceshortfall
= natural_space
-
363 (minspacewidth
+ spaceextra
);
365 if (i
+j
== nwords
&& spaceshortfall
< 0) {
367 * Special case: on the very last line of
368 * the paragraph, we don't score penalty
369 * points for having to _stretch_ the line,
370 * since we won't stretch it anyway.
371 * However, we score penalties as normal
372 * for having to squeeze it.
377 * Squaring this number is tricky since
378 * it's liable to be quite big. Let's
379 * divide it through by 256.
381 int x
= spaceshortfall
>> 8;
382 int xf
= spaceshortfall
& 0xFF;
385 * Not counting strange variable-fixed-
386 * point oddities, we are computing
388 * (x+xf)^2 = x^2 + 2*x*xf + xf*xf
390 * except that _our_ xf is 256 times the
395 cost
+= (2 * x
* xf
) >> 8;
401 * Special case: if we're at the very end of the
402 * paragraph, we don't score penalty points for the
403 * white space left on the line.
407 cost
= (thiswidth
-linelen
) * (thiswidth
-linelen
);
412 * Add in the cost of wrapping all lines after this
416 cost
+= wrapwords
[i
+j
].cost
;
419 * We compare bestcost >= cost, not bestcost > cost,
420 * because in cases where the costs are identical we
421 * want to try to look like the greedy algorithm,
422 * because readers are likely to have spent a lot of
423 * time looking at greedy-wrapped paragraphs and
424 * there's no point violating the Principle of Least
425 * Surprise if it doesn't actually gain anything.
427 if (best
< 0 || bestcost
>= cost
) {
433 * Now we know the optimal answer for this terminal
434 * subsequence, so put it in wrapwords.
436 wrapwords
[i
].cost
= bestcost
;
437 wrapwords
[i
].nwords
= best
;
441 * We've wrapped the paragraph. Now build the output
442 * `wrappedline' list.
446 wrappedline
*w
= mknew(wrappedline
);
451 n
= wrapwords
[i
].nwords
;
452 w
->begin
= wrapwords
[i
].begin
;
453 w
->end
= wrapwords
[i
+n
-1].end
;
456 * Count along the words to find nspaces and shortfall.
459 w
->shortfall
= width
;
460 for (j
= 0; j
< n
; j
++) {
461 w
->shortfall
-= wrapwords
[i
+j
].width
;
462 if (j
< n
-1 && wrapwords
[i
+j
].spacewidth
) {
464 w
->shortfall
-= wrapwords
[i
+j
].spacewidth
;
475 void wrap_free(wrappedline
*w
) {
477 wrappedline
*t
= w
->next
;