399 lines
6.7 KiB
C
399 lines
6.7 KiB
C
/* $Id: out.c,v 1.16 2010/06/19 20:46:28 kristaps Exp $ */
|
|
/*
|
|
* Copyright (c) 2009 Kristaps Dzonsons <kristaps@bsd.lv>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "out.h"
|
|
|
|
/* See a2roffdeco(). */
|
|
#define C2LIM(c, l) do { \
|
|
(l) = 1; \
|
|
if ('[' == (c) || '\'' == (c)) \
|
|
(l) = 0; \
|
|
else if ('(' == (c)) \
|
|
(l) = 2; } \
|
|
while (/* CONSTCOND */ 0)
|
|
|
|
/* See a2roffdeco(). */
|
|
#define C2TERM(c, t) do { \
|
|
(t) = 0; \
|
|
if ('\'' == (c)) \
|
|
(t) = 1; \
|
|
else if ('[' == (c)) \
|
|
(t) = 2; \
|
|
else if ('(' == (c)) \
|
|
(t) = 3; } \
|
|
while (/* CONSTCOND */ 0)
|
|
|
|
/*
|
|
* Convert a `scaling unit' to a consistent form, or fail. Scaling
|
|
* units are documented in groff.7, mdoc.7, man.7.
|
|
*/
|
|
int
|
|
a2roffsu(const char *src, struct roffsu *dst, enum roffscale def)
|
|
{
|
|
char buf[BUFSIZ], hasd;
|
|
int i;
|
|
enum roffscale unit;
|
|
|
|
if ('\0' == *src)
|
|
return(0);
|
|
|
|
i = hasd = 0;
|
|
|
|
switch (*src) {
|
|
case ('+'):
|
|
src++;
|
|
break;
|
|
case ('-'):
|
|
buf[i++] = *src++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ('\0' == *src)
|
|
return(0);
|
|
|
|
while (i < BUFSIZ) {
|
|
if ( ! isdigit((u_char)*src)) {
|
|
if ('.' != *src)
|
|
break;
|
|
else if (hasd)
|
|
break;
|
|
else
|
|
hasd = 1;
|
|
}
|
|
buf[i++] = *src++;
|
|
}
|
|
|
|
if (BUFSIZ == i || (*src && *(src + 1)))
|
|
return(0);
|
|
|
|
buf[i] = '\0';
|
|
|
|
switch (*src) {
|
|
case ('c'):
|
|
unit = SCALE_CM;
|
|
break;
|
|
case ('i'):
|
|
unit = SCALE_IN;
|
|
break;
|
|
case ('P'):
|
|
unit = SCALE_PC;
|
|
break;
|
|
case ('p'):
|
|
unit = SCALE_PT;
|
|
break;
|
|
case ('f'):
|
|
unit = SCALE_FS;
|
|
break;
|
|
case ('v'):
|
|
unit = SCALE_VS;
|
|
break;
|
|
case ('m'):
|
|
unit = SCALE_EM;
|
|
break;
|
|
case ('\0'):
|
|
if (SCALE_MAX == def)
|
|
return(0);
|
|
unit = SCALE_BU;
|
|
break;
|
|
case ('u'):
|
|
unit = SCALE_BU;
|
|
break;
|
|
case ('M'):
|
|
unit = SCALE_MM;
|
|
break;
|
|
case ('n'):
|
|
unit = SCALE_EN;
|
|
break;
|
|
default:
|
|
return(0);
|
|
}
|
|
|
|
if ((dst->scale = atof(buf)) < 0)
|
|
dst->scale = 0;
|
|
dst->unit = unit;
|
|
dst->pt = hasd;
|
|
|
|
return(1);
|
|
}
|
|
|
|
|
|
/*
|
|
* Correctly writes the time in nroff form, which differs from standard
|
|
* form in that a space isn't printed in lieu of the extra %e field for
|
|
* single-digit dates.
|
|
*/
|
|
void
|
|
time2a(time_t t, char *dst, size_t sz)
|
|
{
|
|
struct tm tm;
|
|
char buf[5];
|
|
char *p;
|
|
size_t nsz;
|
|
|
|
assert(sz > 1);
|
|
localtime_r(&t, &tm);
|
|
|
|
p = dst;
|
|
nsz = 0;
|
|
|
|
dst[0] = '\0';
|
|
|
|
if (0 == (nsz = strftime(p, sz, "%B ", &tm)))
|
|
return;
|
|
|
|
p += (int)nsz;
|
|
sz -= nsz;
|
|
|
|
if (0 == strftime(buf, sizeof(buf), "%e, ", &tm))
|
|
return;
|
|
|
|
nsz = strlcat(p, buf + (' ' == buf[0] ? 1 : 0), sz);
|
|
|
|
if (nsz >= sz)
|
|
return;
|
|
|
|
p += (int)nsz;
|
|
sz -= nsz;
|
|
|
|
(void)strftime(p, sz, "%Y", &tm);
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns length of parsed string (the leading "\" should NOT be
|
|
* included). This can be zero if the current character is the nil
|
|
* terminator. "d" is set to the type of parsed decorator, which may
|
|
* have an adjoining "word" of size "sz" (e.g., "(ab" -> "ab", 2).
|
|
*/
|
|
int
|
|
a2roffdeco(enum roffdeco *d,
|
|
const char **word, size_t *sz)
|
|
{
|
|
int j, term, lim;
|
|
char set;
|
|
const char *wp, *sp;
|
|
|
|
*d = DECO_NONE;
|
|
wp = *word;
|
|
|
|
switch ((set = *wp)) {
|
|
case ('\0'):
|
|
return(0);
|
|
|
|
case ('('):
|
|
if ('\0' == *(++wp))
|
|
return(1);
|
|
if ('\0' == *(wp + 1))
|
|
return(2);
|
|
|
|
*d = DECO_SPECIAL;
|
|
*sz = 2;
|
|
*word = wp;
|
|
return(3);
|
|
|
|
case ('F'):
|
|
/* FALLTHROUGH */
|
|
case ('f'):
|
|
/*
|
|
* FIXME: this needs work and consolidation (it should
|
|
* follow the sequence that special characters do, for
|
|
* one), but isn't a priority at the moment. Note, for
|
|
* one, that in reality \fB != \FB, although here we let
|
|
* these slip by.
|
|
*/
|
|
switch (*(++wp)) {
|
|
case ('\0'):
|
|
return(1);
|
|
case ('3'):
|
|
/* FALLTHROUGH */
|
|
case ('B'):
|
|
*d = DECO_BOLD;
|
|
return(2);
|
|
case ('2'):
|
|
/* FALLTHROUGH */
|
|
case ('I'):
|
|
*d = DECO_ITALIC;
|
|
return(2);
|
|
case ('P'):
|
|
*d = DECO_PREVIOUS;
|
|
return(2);
|
|
case ('1'):
|
|
/* FALLTHROUGH */
|
|
case ('R'):
|
|
*d = DECO_ROMAN;
|
|
return(2);
|
|
case ('('):
|
|
if ('\0' == *(++wp))
|
|
return(2);
|
|
if ('\0' == *(wp + 1))
|
|
return(3);
|
|
|
|
*d = 'F' == set ? DECO_FFONT : DECO_FONT;
|
|
*sz = 2;
|
|
*word = wp;
|
|
return(4);
|
|
case ('['):
|
|
*word = ++wp;
|
|
for (j = 0; *wp && ']' != *wp; wp++, j++)
|
|
/* Loop... */ ;
|
|
|
|
if ('\0' == *wp)
|
|
return(j + 2);
|
|
|
|
*d = 'F' == set ? DECO_FFONT : DECO_FONT;
|
|
*sz = (size_t)j;
|
|
return(j + 3);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
*d = 'F' == set ? DECO_FFONT : DECO_FONT;
|
|
*sz = 1;
|
|
*word = wp;
|
|
return(2);
|
|
|
|
case ('*'):
|
|
switch (*(++wp)) {
|
|
case ('\0'):
|
|
return(1);
|
|
|
|
case ('('):
|
|
if ('\0' == *(++wp))
|
|
return(2);
|
|
if ('\0' == *(wp + 1))
|
|
return(3);
|
|
|
|
*d = DECO_RESERVED;
|
|
*sz = 2;
|
|
*word = wp;
|
|
return(4);
|
|
|
|
case ('['):
|
|
*word = ++wp;
|
|
for (j = 0; *wp && ']' != *wp; wp++, j++)
|
|
/* Loop... */ ;
|
|
|
|
if ('\0' == *wp)
|
|
return(j + 2);
|
|
|
|
*d = DECO_RESERVED;
|
|
*sz = (size_t)j;
|
|
return(j + 3);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
*d = DECO_RESERVED;
|
|
*sz = 1;
|
|
*word = wp;
|
|
return(2);
|
|
|
|
case ('s'):
|
|
sp = wp;
|
|
if ('\0' == *(++wp))
|
|
return(1);
|
|
|
|
C2LIM(*wp, lim);
|
|
C2TERM(*wp, term);
|
|
|
|
if (term)
|
|
wp++;
|
|
|
|
*word = wp;
|
|
|
|
if (*wp == '+' || *wp == '-')
|
|
++wp;
|
|
|
|
switch (*wp) {
|
|
case ('\''):
|
|
/* FALLTHROUGH */
|
|
case ('['):
|
|
/* FALLTHROUGH */
|
|
case ('('):
|
|
if (term)
|
|
return((int)(wp - sp));
|
|
|
|
C2LIM(*wp, lim);
|
|
C2TERM(*wp, term);
|
|
wp++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( ! isdigit((u_char)*wp))
|
|
return((int)(wp - sp));
|
|
|
|
for (j = 0; isdigit((u_char)*wp); j++) {
|
|
if (lim && j >= lim)
|
|
break;
|
|
++wp;
|
|
}
|
|
|
|
if (term && term < 3) {
|
|
if (1 == term && *wp != '\'')
|
|
return((int)(wp - sp));
|
|
if (2 == term && *wp != ']')
|
|
return((int)(wp - sp));
|
|
++wp;
|
|
}
|
|
|
|
*d = DECO_SIZE;
|
|
return((int)(wp - sp));
|
|
|
|
case ('['):
|
|
*word = ++wp;
|
|
|
|
for (j = 0; *wp && ']' != *wp; wp++, j++)
|
|
/* Loop... */ ;
|
|
|
|
if ('\0' == *wp)
|
|
return(j + 1);
|
|
|
|
*d = DECO_SPECIAL;
|
|
*sz = (size_t)j;
|
|
return(j + 2);
|
|
|
|
case ('c'):
|
|
*d = DECO_NOSPACE;
|
|
*sz = 1;
|
|
return(1);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
*d = DECO_SPECIAL;
|
|
*word = wp;
|
|
*sz = 1;
|
|
return(1);
|
|
}
|