/* Copyright (C) 1989, 1990, 1991 Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.uucp)
This file is part of groff.
groff is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any later
groff is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
You should have received a copy of the GNU General Public License along
with groff; see the file LICENSE. If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
symbol
default_family("T");
enum { ADJUST_LEFT
= 0, ADJUST_BOTH
= 1, ADJUST_CENTER
= 3, ADJUST_RIGHT
= 5 };
enum { HYPHEN_LAST_LINE
= 2, HYPHEN_LAST_CHARS
= 4, HYPHEN_FIRST_CHARS
= 8 };
env_list(environment
*e
, env_list
*p
) : env(e
), next(p
) {}
const int NENVIRONMENTS
= 10;
environment
*env_table
[NENVIRONMENTS
];
dictionary
env_dictionary(10);
static int next_line_number
= 0;
charinfo
*field_delimiter_char
;
charinfo
*padding_indicator_char
;
class pending_output_line
{
int last_line
; // Is it the last line of the paragraph?
#endif /* WIDOW_CONTROL */
pending_output_line
*next
;
pending_output_line(node
*, int, vunits
, int, hunits
,
pending_output_line
* = 0);
friend void environment::mark_last_line();
friend void environment::output(node
*, int, vunits
, int, hunits
);
#endif /* WIDOW_CONTROL */
pending_output_line::pending_output_line(node
*n
, int nf
, vunits v
, int l
,
hunits w
, pending_output_line
*p
)
: nd(n
), no_fill(nf
), vs(v
), ls(l
), width(w
),
#endif /* WIDOW_CONTROL */
pending_output_line::~pending_output_line()
int pending_output_line::output()
if (next
&& next
->last_line
&& !no_fill
) {
curdiv
->need(vs
*ls
+ vunits(vresolution
));
next
->last_line
= 0; // Try to avoid infinite loops.
curdiv
->output(nd
, no_fill
, vs
, ls
, width
);
void environment::output(node
*nd
, int no_fill
, vunits vs
, int ls
,
if (widow_control
&& !pending_lines
->no_fill
&& !pending_lines
->next
)
if (!pending_lines
->output())
pending_output_line
*tem
= pending_lines
;
pending_lines
= pending_lines
->next
;
#else /* WIDOW_CONTROL */
#endif /* WIDOW_CONTROL */
if (!trap_sprung_flag
&& !pending_lines
&& (!widow_control
|| no_fill
)
#endif /* WIDOW_CONTROL */
curdiv
->output(nd
, no_fill
, vs
, ls
, width
);
for (pending_output_line
**p
= &pending_lines
; *p
; p
= &(*p
)->next
)
*p
= new pending_output_line(nd
, no_fill
, vs
, ls
, width
);
// a line from .tl goes at the head of the queue
void environment::output_title(node
*nd
, int no_fill
, vunits vs
, int ls
,
curdiv
->output(nd
, no_fill
, vs
, ls
, width
);
pending_lines
= new pending_output_line(nd
, no_fill
, vs
, ls
, width
,
void environment::output_pending_lines()
while (pending_lines
&& pending_lines
->output()) {
pending_output_line
*tem
= pending_lines
;
pending_lines
= pending_lines
->next
;
void environment::mark_last_line()
if (!widow_control
|| !pending_lines
)
for (pending_output_line
*p
= pending_lines
; p
->next
; p
= p
->next
)
void widow_control_request()
curenv
->widow_control
= n
!= 0;
curenv
->widow_control
= 1;
#endif /* WIDOW_CONTROL */
/* font_size functions */
size_range
*font_size::size_table
= 0;
int font_size::nranges
= 0;
static int compare_ranges(void *p1
, void *p2
)
return ((size_range
*)p1
)->min
- ((size_range
*)p2
)->min
;
void font_size::init_size_table(int *sizes
)
while (sizes
[nranges
*2] != 0)
size_table
= new size_range
[nranges
];
for (int i
= 0; i
< nranges
; i
++) {
size_table
[i
].min
= sizes
[i
*2];
size_table
[i
].max
= sizes
[i
*2 + 1];
qsort(size_table
, nranges
, sizeof(size_range
), compare_ranges
);
font_size::font_size(int sp
)
for (int i
= 0; i
< nranges
; i
++) {
if (sp
< size_table
[i
].min
) {
if (i
> 0 && size_table
[i
].min
- sp
>= sp
- size_table
[i
- 1].max
)
p
= size_table
[i
- 1].max
;
if (sp
<= size_table
[i
].max
) {
p
= size_table
[nranges
- 1].max
;
int font_size::to_units()
return scale(p
, units_per_inch
, sizescale
*72);
// we can't do this in a static constructor because various dictionaries
// have to get initialized first
curenv
= env_table
[0] = new environment("0");
curenv
->tab_char
= get_optional_char();
curenv
->leader_char
= get_optional_char();
void environment::add_char(charinfo
*ci
)
// don't allow fields in dummy environments
else if (ci
== field_delimiter_char
&& !dummy
) {
else if (current_field
&& ci
== padding_indicator_char
) {
tab_contents
= new space_node(H0
, tab_contents
);
line
= new space_node(H0
, line
);
tab_contents
= new line_start_node
;
if (ci
!= hyphen_indicator_char
)
tab_contents
= tab_contents
->add_char(ci
, this, &tab_width
);
tab_contents
= tab_contents
->add_discretionary_hyphen();
if (ci
!= hyphen_indicator_char
)
line
= line
->add_char(ci
, this, &width_total
);
line
= line
->add_discretionary_hyphen();
node
*environment::make_char_node(charinfo
*ci
)
return make_node(ci
, this);
void environment::add_node(node
*n
)
if (current_tab
|| current_field
)
if (discarding
&& n
->discardable()) {
width_total
+= n
->width();
space_total
+= n
->nspaces();
void environment::add_hyphen_indicator()
if (current_tab
|| interrupted
|| current_field
|| hyphen_indicator_char
!= 0)
line
= line
->add_discretionary_hyphen();
int environment::get_hyphenation_flags()
return hyphenation_flags
;
int environment::get_hyphen_line_max()
int environment::get_hyphen_line_count()
return hyphen_line_count
;
int environment::get_center_lines()
int environment::get_right_justify_lines()
return right_justify_lines
;
void environment::add_italic_correction()
tab_contents
= tab_contents
->add_italic_correction(&tab_width
);
line
= line
->add_italic_correction(&width_total
);
int environment::get_space_size()
int environment::get_sentence_space_size()
return sentence_space_size
;
hunits
environment::get_space_width()
return scale(env_space_width(this), space_size
, 12);
hunits
environment::get_narrow_space_width()
return env_narrow_space_width(this);
hunits
environment::get_half_narrow_space_width()
return env_half_narrow_space_width(this);
void environment::space_newline()
assert(!current_tab
&& !current_field
);
if (node_list_ends_sentence(line
) == 1)
ss
+= sentence_space_size
;
hunits x
= scale(env_space_width(this), ss
, 12);
if (line
!= 0 && line
->merge_space(x
)) {
add_node(new word_space_node(x
));
possibly_break_line(spread_flag
);
void environment::space()
if (current_field
&& padding_indicator_char
== 0) {
node
**pp
= current_tab
? &tab_contents
: &line
;
int *hp
= current_tab
? &tab_field_spaces
: &field_spaces
;
*pp
= new space_node(H0
, *pp
);
hunits x
= scale(env_space_width(this), space_size
, 12);
node
*p
= current_tab
? tab_contents
: line
;
hunits
*tp
= current_tab
? &tab_width
: &width_total
;
if (p
&& p
->nspaces() == 1 && p
->width() == x
&& node_list_ends_sentence(p
->next
) == 1) {
hunits xx
= scale(env_space_width(this), sentence_space_size
, 12);
if (p
->merge_space(xx
)) {
if (p
&& p
->merge_space(x
)) {
add_node(new word_space_node(x
));
possibly_break_line(spread_flag
);
void environment::set_font(symbol nm
)
if (family
->make_definite(prev_fontno
) < 0)
int n
= symbol_fontno(nm
);
n
= next_available_font_position();
if (family
->make_definite(n
) < 0)
void environment::set_font(int n
)
error("bad font number");
void environment::set_family(symbol fam
)
if (prev_family
->make_definite(fontno
) < 0)
font_family
*tem
= family
;
font_family
*f
= lookup_family(fam
);
if (f
->make_definite(fontno
) < 0)
void environment::set_size(int n
)
font_size temp
= prev_size
;
int temp2
= prev_requested_size
;
prev_requested_size
= requested_size
;
prev_requested_size
= requested_size
;
void environment::set_char_height(int n
)
if (n
== requested_size
|| n
<= 0)
void environment::set_char_slant(int n
)
environment::environment(symbol nm
)
prev_line_length((units_per_inch
*13)/2),
line_length((units_per_inch
*13)/2),
prev_title_length((units_per_inch
*13)/2),
title_length((units_per_inch
*13)/2),
requested_size(sizescale
*10),
prev_requested_size(sizescale
*10),
adjust_mode(ADJUST_BOTH
),
prev_line_interrupted(0),
prev_vertical_spacing(points_to_units(12)),
vertical_spacing(points_to_units(12)),
have_temporary_indent(0),
no_break_control_char('\''),
hyphen_indicator_char(0),
tabs(units_per_inch
/2, TAB_LEFT
),
margin_character_node(0),
margin_character_distance(points_to_units(10)),
number_text_separation(1),
leader_char(charset_table
['.']),
#endif /* WIDOW_CONTROL */
prev_family
= family
= lookup_family(default_family
);
prev_fontno
= fontno
= 1;
fatal("font number 1 not a valid font");
if (family
->make_definite(1) < 0)
fatal("invalid default family `%1'", default_family
.contents());
environment::environment(const environment
*e
)
: name(e
->name
), // so that eg `.if "\n[.ev]"0"' works
prev_line_length(e
->prev_line_length
),
line_length(e
->line_length
),
prev_title_length(e
->prev_title_length
),
title_length(e
->title_length
),
prev_requested_size(e
->prev_requested_size
),
requested_size(e
->requested_size
),
char_height(e
->char_height
),
char_slant(e
->char_slant
),
space_size(e
->space_size
),
sentence_space_size(e
->sentence_space_size
),
adjust_mode(e
->adjust_mode
),
prev_line_interrupted(0),
prev_vertical_spacing(e
->prev_vertical_spacing
),
vertical_spacing(e
->vertical_spacing
),
prev_line_spacing(e
->prev_line_spacing
),
line_spacing(e
->line_spacing
),
prev_indent(e
->prev_indent
),
have_temporary_indent(0),
prev_text_length(e
->prev_text_length
),
control_char(e
->control_char
),
no_break_control_char(e
->no_break_control_char
),
hyphen_indicator_char(e
->hyphen_indicator_char
),
margin_character_node(e
->margin_character_node
),
margin_character_distance(e
->margin_character_distance
),
number_text_separation(e
->number_text_separation
),
line_number_multiple(e
->line_number_multiple
),
line_number_indent(e
->line_number_indent
),
no_number_count(e
->no_number_count
),
leader_char(e
->leader_char
),
hyphenation_flags(e
->hyphenation_flags
),
prev_fontno(e
->prev_fontno
),
prev_family(e
->prev_family
),
widow_control(e
->widow_control
),
#endif /* WIDOW_CONTROL */
hyphen_line_max(e
->hyphen_line_max
),
hyphenation_space(e
->hyphenation_space
),
hyphenation_margin(e
->hyphenation_margin
)
environment::~environment()
delete_node_list(numbering_nodes
);
hunits
environment::get_input_line_position()
n
= width_total
- input_line_start
;
void environment::set_input_line_position(hunits n
)
input_line_start
= line
== 0 ? -n
: width_total
- n
;
input_line_start
+= tab_width
;
hunits
environment::get_line_length()
hunits
environment::get_saved_line_length()
return target_text_length
+ saved_indent
;
vunits
environment::get_vertical_spacing()
int environment::get_line_spacing()
int environment::get_bold()
return get_bold_fontno(fontno
);
hunits
environment::get_digit_width()
return env_digit_width(this);
int environment::get_adjust_mode()
int environment::get_fill()
hunits
environment::get_indent()
hunits
environment::get_saved_indent()
else if (have_temporary_indent
)
hunits
environment::get_temporary_indent()
hunits
environment::get_title_length()
node
*environment::get_prev_char()
for (node
*n
= current_tab
? tab_contents
: line
; n
; n
= n
->next
) {
node
*last
= n
->last_char_node();
hunits
environment::get_prev_char_width()
node
*last
= get_prev_char();
hunits
environment::get_prev_char_skew()
node
*last
= get_prev_char();
vunits
environment::get_prev_char_height()
node
*last
= get_prev_char();
last
->vertical_extent(&min
, &max
);
vunits
environment::get_prev_char_depth()
node
*last
= get_prev_char();
last
->vertical_extent(&min
, &max
);
hunits
environment::get_text_length()
hunits n
= line
== 0 ? H0
: width_total
;
hunits
environment::get_prev_text_length()
static int sb_reg_contents
= 0;
static int st_reg_contents
= 0;
static int ct_reg_contents
= 0;
static int rsb_reg_contents
= 0;
static int rst_reg_contents
= 0;
static int skw_reg_contents
= 0;
static int ssc_reg_contents
= 0;
void environment::width_registers()
// this is used to implement \w; it sets the st, sb, ct registers
vunits min
= 0, max
= 0, cur
= 0;
ssc_reg_contents
= line
? line
->subscript_correction().to_units() : 0;
skw_reg_contents
= line
? line
->skew().to_units() : 0;
line
= reverse_node_list(line
);
for (node
*tem
= line
; tem
; tem
= tem
->next
) {
tem
->vertical_extent(&v1
, &v2
);
if ((cur
+= tem
->vertical_width()) < min
)
character_type
|= tem
->character_type();
line
= reverse_node_list(line
);
st_reg_contents
= -min
.to_units();
sb_reg_contents
= -max
.to_units();
rst_reg_contents
= -real_min
.to_units();
rsb_reg_contents
= -real_max
.to_units();
ct_reg_contents
= character_type
;
node
*environment::extract_output_line()
/* environment related requests */
void environment_switch()
int pop
= 0; // 1 means pop, 2 means pop but no error message on underflow
error("can't switch environments when current environment is dummy");
// It looks like a number.
if (n
>= 0 && n
< NENVIRONMENTS
) {
env_stack
= new env_list(curenv
, env_stack
);
env_table
[n
] = new environment(itoa(n
));
environment
*e
= (environment
*)env_dictionary
.lookup(nm
);
(void)env_dictionary
.lookup(nm
, e
);
env_stack
= new env_list(curenv
, env_stack
);
error("environment stack underflow");
env_list
*tem
= env_stack
;
env_stack
= env_stack
->next
;
static symbol
P_symbol("P");
if (s
.is_null() || s
== P_symbol
) {
for (const char *p
= s
.contents(); p
!= 0 && *p
!= 0; p
++)
curenv
->set_font(atoi(s
.contents()));
if (get_number(&n
, 0, curenv
->get_requested_point_size()/sizescale
)) {
if (get_number(&n
, 'z', curenv
->get_requested_point_size())) {
curenv
->sentence_space_size
= n
;
curenv
->sentence_space_size
= curenv
->space_size
;
while (!tok
.newline() && !tok
.eof())
while (!tok
.newline() && !tok
.eof())
if (!has_arg() || !get_integer(&n
))
while (!tok
.newline() && !tok
.eof())
curenv
->right_justify_lines
= 0;
curenv
->center_lines
= n
;
if (!has_arg() || !get_integer(&n
))
while (!tok
.newline() && !tok
.eof())
curenv
->center_lines
= 0;
curenv
->right_justify_lines
= n
;
hunits temp
= curenv
->line_length
;
curenv
->line_length
= curenv
->prev_line_length
;
curenv
->prev_line_length
= temp
;
else if (get_hunits(&temp
, 'm', curenv
->line_length
)) {
warning(WARN_RANGE
, "bad line length %1u", temp
.to_units());
curenv
->prev_line_length
= curenv
->line_length
;
curenv
->line_length
= temp
;
hunits temp
= curenv
->title_length
;
curenv
->title_length
= curenv
->prev_title_length
;
curenv
->prev_title_length
= temp
;
else if (get_hunits(&temp
, 'm', curenv
->title_length
)) {
warning(WARN_RANGE
, "bad title length %1u", temp
.to_units());
curenv
->prev_title_length
= curenv
->title_length
;
curenv
->title_length
= temp
;
vunits temp
= curenv
->vertical_spacing
;
curenv
->vertical_spacing
= curenv
->prev_vertical_spacing
;
curenv
->prev_vertical_spacing
= temp
;
if (get_vunits(&temp
, 'p', curenv
->vertical_spacing
)) {
warning(WARN_RANGE
, "vertical spacing must be greater than 0");
curenv
->prev_vertical_spacing
= curenv
->vertical_spacing
;
curenv
->vertical_spacing
= temp
;
temp
= curenv
->line_spacing
;
curenv
->line_spacing
= curenv
->prev_line_spacing
;
curenv
->prev_line_spacing
= temp
;
else if (get_integer(&temp
)) {
warning(WARN_RANGE
, "value %1 out of range: interpreted as 1", temp
);
curenv
->prev_line_spacing
= curenv
->line_spacing
;
curenv
->line_spacing
= temp
;
temp
= curenv
->prev_indent
;
else if (!get_hunits(&temp
, 'm', curenv
->indent
))
while (!tok
.newline() && !tok
.eof())
warning(WARN_RANGE
, "indent cannot be negative");
curenv
->have_temporary_indent
= 0;
curenv
->prev_indent
= curenv
->indent
;
if (!get_hunits(&temp
, 'm', curenv
->get_indent()))
while (!tok
.newline() && !tok
.eof())
warning(WARN_RANGE
, "total indent cannot be negative");
curenv
->temporary_indent
= temp
;
curenv
->have_temporary_indent
= 1;
else if (!get_integer(&n
))
if (curenv
->underline_lines
> 0) {
curenv
->prev_fontno
= curenv
->fontno
;
curenv
->fontno
= curenv
->pre_underline_fontno
;
curenv
->underline_lines
= 0;
curenv
->underline_lines
= n
;
curenv
->pre_underline_fontno
= curenv
->fontno
;
curenv
->fontno
= get_underline_fontno();
curenv
->control_char
= '.';
error("bad control character");
curenv
->control_char
= tok
.ch();
void no_break_control_char()
curenv
->no_break_control_char
= '\'';
error("bad control character");
curenv
->no_break_control_char
= tok
.ch();
if (curenv
->margin_character_node
) {
delete curenv
->margin_character_node
;
curenv
->margin_character_node
= 0;
charinfo
*ci
= get_optional_char();
curenv
->margin_character_node
= curenv
->make_char_node(ci
);
if (curenv
->margin_character_node
&& has_arg() && get_hunits(&d
, 'm'))
curenv
->margin_character_distance
= d
;
delete_node_list(curenv
->numbering_nodes
);
curenv
->numbering_nodes
= 0;
if (has_arg() && get_integer(&n
, next_line_number
)) {
if (next_line_number
< 0) {
warning(WARN_RANGE
, "negative line number");
for (int i
= '9'; i
>= '0'; i
--) {
node
*tem
= make_node(charset_table
[i
], curenv
);
curenv
->numbering_nodes
= nd
;
curenv
->line_number_digit_width
= env_digit_width(curenv
);
warning(WARN_RANGE
, "negative or zero line number multiple");
curenv
->line_number_multiple
= n
;
while (!tok
.space() && !tok
.newline() && !tok
.eof())
curenv
->number_text_separation
= n
;
while (!tok
.space() && !tok
.newline() && !tok
.eof())
if (has_arg() && !tok
.delimiter() && get_integer(&n
))
curenv
->line_number_indent
= n
;
curenv
->no_number_count
= n
> 0 ? n
: 0;
curenv
->no_number_count
= 1;
curenv
->hyphenation_flags
= 0;
curenv
->hyphenation_flags
= n
;
curenv
->hyphenation_flags
= 1;
curenv
->hyphen_indicator_char
= get_optional_char();
void hyphen_line_max_request()
curenv
->hyphen_line_max
= n
;
curenv
->hyphen_line_max
= -1;
void environment::interrupt()
add_node(new transparent_dummy_node
);
void environment::newline()
if (underline_lines
> 0) {
if (--underline_lines
== 0) {
fontno
= pre_underline_fontno
;
while (line
!= 0 && line
->discardable()) {
width_total
-= line
->width();
space_total
-= line
->nspaces();
hunits to_be_output_width
;
prev_line_interrupted
= interrupted
;
else if (center_lines
> 0) {
hunits x
= target_text_length
- width_total
;
to_be_output_width
= width_total
;
else if (right_justify_lines
> 0) {
hunits x
= target_text_length
- width_total
;
to_be_output_width
= width_total
;
to_be_output_width
= width_total
;
input_line_start
= line
== 0 ? H0
: width_total
;
output_line(to_be_output
, to_be_output_width
);
if (input_trap_count
> 0) {
if (--input_trap_count
== 0)
void environment::output_line(node
*n
, hunits width
)
prev_text_length
= width
;
if (margin_character_node
) {
hunits d
= line_length
+ margin_character_distance
- saved_indent
- width
;
n
= new hmotion_node(d
, n
);
node
*tem
= margin_character_node
->copy();
if (!saved_indent
.is_zero())
nn
= new hmotion_node(saved_indent
, nn
);
else if (numbering_nodes
) {
hunits w
= (line_number_digit_width
*(3+line_number_indent
+number_text_separation
));
if (next_line_number
% line_number_multiple
!= 0)
nn
= new hmotion_node(w
, nn
);
nn
= new hmotion_node(number_text_separation
*line_number_digit_width
,
x
-= number_text_separation
*line_number_digit_width
;
sprintf(buf
, "%3d", next_line_number
);
for (char *p
= strchr(buf
, '\0') - 1; p
>= buf
&& *p
!= ' '; --p
) {
node
*gn
= numbering_nodes
;
for (int count
= *p
- '0'; count
> 0; count
--)
nn
= new hmotion_node(x
, nn
);
output(nn
, !fill
, vertical_spacing
, line_spacing
, width
);
void environment::start_line()
line
= new line_start_node
;
if (have_temporary_indent
) {
saved_indent
= temporary_indent
;
have_temporary_indent
= 0;
target_text_length
= line_length
- saved_indent
;
hunits
environment::get_hyphenation_space()
return hyphenation_space
;
void hyphenation_space_request()
if (get_hunits(&n
, 'm')) {
warning(WARN_RANGE
, "hyphenation space cannot be negative");
curenv
->hyphenation_space
= n
;
hunits
environment::get_hyphenation_margin()
return hyphenation_margin
;
void hyphenation_margin_request()
if (get_hunits(&n
, 'm')) {
warning(WARN_RANGE
, "hyphenation margin cannot be negative");
curenv
->hyphenation_margin
= n
;
breakpoint
*environment::choose_breakpoint()
breakpoint
*best_bp
= 0; // the best breakpoint so far
breakpoint
*bp
= n
->get_breakpoints(x
, s
);
if (bp
->width
<= target_text_length
) {
breakpoint
*tem
= bp
->next
;
// Decide whether to use the hyphenated breakpoint.
// Only choose the hyphenated breakpoint if it would not
// exceed the maximum number of consecutive hyphenated
|| hyphen_line_count
+ 1 <= hyphen_line_max
)
&& !(adjust_mode
== ADJUST_BOTH
// Don't choose the hyphenated breakpoint if the line
// can be justified by adding no more than
// hyphenation_space to any word space.
&& (((target_text_length
- bp
->width
+ (bp
->nspaces
- 1)*hresolution
)/bp
->nspaces
)
// Don't choose the hyphenated breakpoint if the line
// is no more than hyphenation_margin short.
: target_text_length
- bp
->width
<= hyphenation_margin
)) {
if ((adjust_mode
== ADJUST_BOTH
? hyphenation_space
== H0
: hyphenation_margin
== H0
)
|| hyphen_line_count
+ 1 <= hyphen_line_max
)) {
// No need to consider a non-hyphenated breakpoint.
// It fits but it's hyphenated.
warning(WARN_BREAK
, "can't break line");
void environment::hyphenate_line()
hyphenation_type prev_type
= line
->get_hyphenation_type();
for (node
*tem
= line
->next
; tem
!= 0; tem
= tem
->next
) {
hyphenation_type this_type
= tem
->get_hyphenation_type();
if (prev_type
== HYPHEN_BOUNDARY
&& this_type
== HYPHEN_MIDDLE
)
} while (tem
!= 0 && tem
->get_hyphenation_type() == HYPHEN_MIDDLE
);
int inhibit
= (tem
!= 0 && tem
->get_hyphenation_type() == HYPHEN_INHIBIT
);
sl
= tem
->get_hyphen_list(sl
);
// this is for characters like hyphen and emdash
for (hyphen_list
*h
= sl
; h
; h
= h
->next
) {
h
->breakable
= (prev_code
!= 0
&& h
->next
->hyphenation_code
!= 0);
prev_code
= h
->hyphenation_code
;
if (hyphenation_flags
!= 0
// this may not be right if we have extra space on this line
&& !((hyphenation_flags
& HYPHEN_LAST_LINE
)
&& curdiv
->distance_to_next_trap() <= line_spacing
*vertical_spacing
)
hyphenate(sl
, hyphenation_flags
);
tem
= tem1
->add_self(tem
, &sl
);
for (tem
= line
; tem
->next
!= start
; tem
= tem
->next
)
static node
*node_list_reverse(node
*n
)
static void distribute_space(node
*n
, int nspaces
, hunits desired_space
,
if (!force_forward
&& reverse
)
n
= node_list_reverse(n
);
for (node
*tem
= n
; tem
; tem
= tem
->next
)
tem
->spread_space(&nspaces
, &desired_space
);
(void)node_list_reverse(n
);
assert(desired_space
.is_zero() && nspaces
== 0);
void environment::possibly_break_line(int forced
)
if (!fill
|| current_tab
|| current_field
|| dummy
)
while (line
!= 0 && (forced
|| width_total
> target_text_length
)) {
breakpoint
*bp
= choose_breakpoint();
// we'll find one eventually
bp
->nd
->split(bp
->index
, &pre
, &post
);
hunits extra_space_width
= H0
;
extra_space_width
= target_text_length
- bp
->width
;
saved_indent
+= (target_text_length
- bp
->width
)/2;
saved_indent
+= target_text_length
- bp
->width
;
distribute_space(pre
, bp
->nspaces
, extra_space_width
);
output_line(pre
, bp
->width
+ extra_space_width
);
input_line_start
-= bp
->width
+ extra_space_width
;
for (tem
= line
; tem
->next
!= bp
->nd
; tem
= tem
->next
)
node
*first_non_discardable
= 0;
for (tem
= line
; tem
!= 0; tem
= tem
->next
)
first_non_discardable
= tem
;
if (first_non_discardable
) {
to_be_discarded
= first_non_discardable
->next
;
first_non_discardable
->next
= 0;
for (tem
= line
; tem
!= 0; tem
= tem
->next
) {
width_total
+= tem
->width();
space_total
+= tem
->nspaces();
while (to_be_discarded
!= 0) {
to_be_discarded
= to_be_discarded
->next
;
input_line_start
-= tem
->width();
if (have_temporary_indent
) {
saved_indent
= temporary_indent
;
have_temporary_indent
= 0;
target_text_length
= line_length
- saved_indent
;
void environment::do_break()
if (curdiv
== topdiv
&& !topdiv
->first_page_begun
) {
line
= new space_node(H0
, line
); // this is so that hyphenation works
while (line
!= 0 && line
->discardable()) {
width_total
-= line
->width();
space_total
-= line
->nspaces();
saved_indent
+= (target_text_length
- width_total
)/2;
saved_indent
+= target_text_length
- width_total
;
output_line(tem
, width_total
);
#endif /* WIDOW_CONTROL */
int environment::is_empty()
return !current_tab
&& line
== 0 && pending_lines
== 0;
while (!tok
.newline() && !tok
.eof())
if (curdiv
== topdiv
&& !topdiv
->first_page_begun
) {
part
[0] = part
[1] = part
[2] = 0;
environment
*oldenv
= curenv
;
read_title_parts(part
, part_width
);
curenv
->prev_size
= env
.prev_size
;
curenv
->requested_size
= env
.requested_size
;
curenv
->prev_requested_size
= env
.prev_requested_size
;
curenv
->char_height
= env
.char_height
;
curenv
->char_slant
= env
.char_slant
;
curenv
->fontno
= env
.fontno
;
curenv
->prev_fontno
= env
.prev_fontno
;
hunits
title_length(curenv
->title_length
);
hunits f
= title_length
- part_width
[1];
n
= new hmotion_node(f2
- part_width
[2], n
);
n
= new hmotion_node(f
- f2
- part_width
[0], n
);
curenv
->output_title(n
, !curenv
->fill
, curenv
->vertical_spacing
,
curenv
->line_spacing
, title_length
);
curenv
->hyphen_line_count
= 0;
curenv
->adjust_mode
|= 1;
curenv
->adjust_mode
= ADJUST_LEFT
;
curenv
->adjust_mode
= ADJUST_RIGHT
;
curenv
->adjust_mode
= ADJUST_CENTER
;
curenv
->adjust_mode
= ADJUST_BOTH
;
warning(WARN_RANGE
, "adjustment mode `%1' out of range", n
);
curenv
->adjust_mode
&= ~1;
curenv
->input_trap_count
= 0;
else if (get_integer(&n
)) {
error("number of lines for input trap must be greater than zero");
curenv
->input_trap_count
= n
;
// must not be R or C or L or a legitimate part of a number expression
const char TAB_REPEAT_CHAR
= 'T';
void *operator new(size_t);
void operator delete(void *);
void *tab::operator new(size_t n
)
assert(n
== sizeof(tab
));
free_list
= (tab
*)new char[sizeof(tab
)*BLOCK
];
for (int i
= 0; i
< BLOCK
- 1; i
++)
free_list
[i
].next
= free_list
+ i
+ 1;
free_list
[BLOCK
-1].next
= 0;
free_list
= (tab
*)(free_list
->next
);
/* cfront can't cope with this. */
void tab::operator delete(void *p
)
((tab
*)p
)->next
= free_list
;
tab::tab(hunits x
, tab_type t
) : next(0), pos(x
), type(t
)
tab_stops::tab_stops(hunits distance
, tab_type type
)
repeated_list
= new tab(distance
, type
);
tab_type
tab_stops::distance_to_next_tab(hunits curpos
, hunits
*distance
)
for (tab
*tem
= initial_list
; tem
&& tem
->pos
<= curpos
; tem
= tem
->next
)
*distance
= tem
->pos
- curpos
;
for (tem
= repeated_list
; tem
&& tem
->pos
+ base
<= curpos
; tem
= tem
->next
)
*distance
= tem
->pos
+ base
- curpos
;
const char *tab_stops::to_string()
// figure out a maximum on the amount of space we can need
for (tab
*p
= initial_list
; p
; p
= p
->next
)
for (p
= repeated_list
; p
; p
= p
->next
)
// (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
if (buf
== 0 || need
> buf_size
) {
buf
= new char[buf_size
];
for (p
= initial_list
; p
; p
= p
->next
) {
strcpy(ptr
, itoa(p
->pos
.to_units()));
*ptr
++ = TAB_REPEAT_CHAR
;
for (p
= repeated_list
; p
; p
= p
->next
) {
strcpy(ptr
, itoa(p
->pos
.to_units()));
tab_stops::tab_stops() : initial_list(0), repeated_list(0)
tab_stops::tab_stops(const tab_stops
&ts
)
: initial_list(0), repeated_list(0)
tab
*t
= ts
.initial_list
;
*p
= new tab(t
->pos
, t
->type
);
*p
= new tab(t
->pos
, t
->type
);
initial_list
= initial_list
->next
;
tab
*tem
= repeated_list
;
repeated_list
= repeated_list
->next
;
void tab_stops::add_tab(hunits pos
, tab_type type
, int repeated
)
for (tab
**p
= repeated
? &repeated_list
: &initial_list
; *p
; p
= &(*p
)->next
)
void tab_stops::operator=(const tab_stops
&ts
)
tab
*t
= ts
.initial_list
;
*p
= new tab(t
->pos
, t
->type
);
*p
= new tab(t
->pos
, t
->type
);
if (tok
.ch() == TAB_REPEAT_CHAR
) {
if (!get_hunits(&pos
, 'm', prev_pos
))
warning(WARN_RANGE
, "positions of tab stops must be strictly increasing");
tab_type type
= TAB_LEFT
;
else if (tok
.ch() == 'R') {
else if (tok
.ch() == 'L') {
tabs
.add_tab(pos
, type
, repeated
);
const char *environment::get_tabs()
saved_tabs
= curenv
->tabs
;
curenv
->tabs
= saved_tabs
;
tab_type
environment::distance_to_next_tab(hunits
*distance
)
return curenv
->tabs
.distance_to_next_tab(get_input_line_position(), distance
);
field_delimiter_char
= get_optional_char();
if (field_delimiter_char
)
padding_indicator_char
= get_optional_char();
padding_indicator_char
= 0;
void environment::wrap_up_tab()
tab_amount
= tab_distance
- tab_width
;
line
= make_tab_node(tab_amount
, line
);
tab_amount
= tab_distance
- tab_width
/2;
line
= make_tab_node(tab_amount
, line
);
width_total
+= tab_amount
;
width_total
+= tab_width
;
if (tab_precedes_field
) {
pre_field_width
+= tab_amount
;
field_distance
-= tab_amount
;
field_spaces
+= tab_field_spaces
;
for (node
*tem
= tab_contents
; tem
->next
!= 0; tem
= tem
->next
)
node
*environment::make_tab_node(hunits d
, node
*next
)
if (leader_node
!= 0 && d
< 0) {
error("motion generated by leader cannot be negative");
return new hmotion_node(d
, next
);
node
*n
= new hline_node(d
, leader_node
, next
);
void environment::handle_tab(int is_leader
)
charinfo
*ci
= is_leader
? leader_char
: tab_char
;
leader_node
= ci
? make_char_node(ci
) : 0;
tab_type t
= distance_to_next_tab(&d
);
add_node(make_tab_node(d
));
void environment::start_field()
if (distance_to_next_tab(&d
) != TAB_NONE
) {
pre_field_width
= get_text_length();
for (node
*p
= line
; p
; p
= p
->next
)
tab_precedes_field
= current_tab
!= TAB_NONE
;
error("zero field width");
void environment::wrap_up_field()
hunits padding
= field_distance
- (get_text_length() - pre_field_width
);
if (current_tab
&& tab_field_spaces
!= 0) {
hunits tab_padding
= scale(padding
,
field_spaces
+ tab_field_spaces
);
distribute_space(tab_contents
, tab_field_spaces
, tab_padding
, 1);
tab_width
+= tab_padding
;
distribute_space(line
, field_spaces
, padding
, 1);
// the start of the tab has been moved to the right by padding, so
if (tab_distance
<= H0
) {
// use the next tab stop instead
current_tab
= tabs
.distance_to_next_tab(get_input_line_position()
if (current_tab
== TAB_NONE
|| current_tab
== TAB_LEFT
) {
width_total
+= tab_width
;
if (current_tab
== TAB_LEFT
) {
line
= make_tab_node(tab_distance
, line
);
width_total
+= tab_distance
;
for (node
*tem
= tab_contents
; tem
->next
!= 0; tem
= tem
->next
)
typedef int (environment::*INT_FUNCP
)();
typedef vunits (environment::*VUNITS_FUNCP
)();
typedef hunits (environment::*HUNITS_FUNCP
)();
typedef const char *(environment::*STRING_FUNCP
)();
class int_env_reg
: public reg
{
const char *get_string();
int get_value(units
*val
);
class vunits_env_reg
: public reg
{
vunits_env_reg(VUNITS_FUNCP f
);
const char *get_string();
int get_value(units
*val
);
class hunits_env_reg
: public reg
{
hunits_env_reg(HUNITS_FUNCP f
);
const char *get_string();
int get_value(units
*val
);
class string_env_reg
: public reg
{
string_env_reg(STRING_FUNCP
);
const char *get_string();
int_env_reg::int_env_reg(INT_FUNCP f
) : func(f
)
int int_env_reg::get_value(units
*val
)
*val
= (curenv
->*func
)();
const char *int_env_reg::get_string()
return itoa((curenv
->*func
)());
vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f
) : func(f
)
int vunits_env_reg::get_value(units
*val
)
*val
= (curenv
->*func
)().to_units();
const char *vunits_env_reg::get_string()
return itoa((curenv
->*func
)().to_units());
hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f
) : func(f
)
int hunits_env_reg::get_value(units
*val
)
*val
= (curenv
->*func
)().to_units();
const char *hunits_env_reg::get_string()
return itoa((curenv
->*func
)().to_units());
string_env_reg::string_env_reg(STRING_FUNCP f
) : func(f
)
const char *string_env_reg::get_string()
return (curenv
->*func
)();
class horizontal_place_reg
: public general_reg
{
horizontal_place_reg::horizontal_place_reg()
int horizontal_place_reg::get_value(units
*res
)
*res
= curenv
->get_input_line_position().to_units();
void horizontal_place_reg::set_value(units n
)
curenv
->set_input_line_position(hunits(n
));
const char *environment::get_font_family_string()
return family
->nm
.contents();
const char *environment::get_name_string()
// Convert a quantity in scaled points to ascii decimal fraction.
const char *sptoa(int sp
)
return itoa(sp
/sizescale
);
// See if 1/sizescale is exactly representable as a decimal fraction,
// ie its only prime factors are 2 and 5.
int decimal_point
= power5
> power2
? power5
: power2
;
if (decimal_point
<= 10) {
for (t
= decimal_point
- power2
; --t
>= 0;)
for (t
= decimal_point
- power5
; --t
>= 0;)
if (factor
== 1 || sp
<= INT_MAX
/factor
)
return iftoa(sp
*factor
, decimal_point
);
double s
= double(sp
)/double(sizescale
);
double v
= ceil(s
*factor
);
} while (++decimal_point
< 10);
return iftoa(int(val
), decimal_point
);
const char *environment::get_point_size_string()
return sptoa(curenv
->get_point_size());
const char *environment::get_requested_point_size_string()
return sptoa(curenv
->get_requested_point_size());
#define init_int_env_reg(name, func) \
number_reg_dictionary.define(name, new int_env_reg(&environment::func))
#define init_vunits_env_reg(name, func) \
number_reg_dictionary.define(name, new vunits_env_reg(&environment::func))
#define init_hunits_env_reg(name, func) \
number_reg_dictionary.define(name, new hunits_env_reg(&environment::func))
#define init_string_env_reg(name, func) \
number_reg_dictionary.define(name, new string_env_reg(&environment::func))
init_request("it", input_trap
);
init_request("ad", adjust
);
init_request("na", no_adjust
);
init_request("ev", environment_switch
);
init_request("lt", title_length
);
init_request("ps", point_size
);
init_request("ft", font_change
);
init_request("fam", family_change
);
init_request("ss", space_size
);
init_request("fi", fill
);
init_request("nf", no_fill
);
init_request("ce", center
);
init_request("rj", right_justify
);
init_request("vs", vertical_spacing
);
init_request("ls", line_spacing
);
init_request("ll", line_length
);
init_request("in", indent
);
init_request("ti", temporary_indent
);
init_request("ul", underline
);
init_request("cu", underline
);
init_request("cc", control_char
);
init_request("c2", no_break_control_char
);
init_request("br", break_request
);
init_request("tl", title
);
init_request("ta", set_tabs
);
init_request("fc", field_characters
);
init_request("mc", margin_character
);
init_request("nn", no_number
);
init_request("nm", number_lines
);
init_request("tc", tab_character
);
init_request("lc", leader_character
);
init_request("hy", hyphenate_request
);
init_request("hc", hyphen_char
);
init_request("nh", no_hyphenate
);
init_request("hlm", hyphen_line_max_request
);
init_request("wdc", widow_control_request
);
#endif /* WIDOW_CONTROL */
init_request("tas", tabs_save
);
init_request("tar", tabs_restore
);
init_request("hys", hyphenation_space_request
);
init_request("hym", hyphenation_margin_request
);
init_int_env_reg(".f", get_font
);
init_int_env_reg(".b", get_bold
);
init_hunits_env_reg(".i", get_indent
);
init_hunits_env_reg(".in", get_saved_indent
);
init_int_env_reg(".j", get_adjust_mode
);
init_hunits_env_reg(".k", get_text_length
);
init_hunits_env_reg(".l", get_line_length
);
init_hunits_env_reg(".ll", get_saved_line_length
);
init_int_env_reg(".L", get_line_spacing
);
init_hunits_env_reg(".n", get_prev_text_length
);
init_string_env_reg(".s", get_point_size_string
);
init_string_env_reg(".sr", get_requested_point_size_string
);
init_int_env_reg(".ps", get_point_size
);
init_int_env_reg(".psr", get_requested_point_size
);
init_int_env_reg(".u", get_fill
);
init_vunits_env_reg(".v", get_vertical_spacing
);
init_hunits_env_reg(".w", get_prev_char_width
);
init_int_env_reg(".ss", get_space_size
);
init_int_env_reg(".sss", get_sentence_space_size
);
init_string_env_reg(".fam", get_font_family_string
);
init_string_env_reg(".ev", get_name_string
);
init_int_env_reg(".hy", get_hyphenation_flags
);
init_int_env_reg(".hlm", get_hyphen_line_max
);
init_int_env_reg(".hlc", get_hyphen_line_count
);
init_hunits_env_reg(".lt", get_title_length
);
init_string_env_reg(".tabs", get_tabs
);
init_hunits_env_reg(".csk", get_prev_char_skew
);
init_vunits_env_reg(".cht", get_prev_char_height
);
init_vunits_env_reg(".cdp", get_prev_char_depth
);
init_int_env_reg(".ce", get_center_lines
);
init_int_env_reg(".rj", get_right_justify_lines
);
init_hunits_env_reg(".hys", get_hyphenation_space
);
init_hunits_env_reg(".hym", get_hyphenation_margin
);
number_reg_dictionary
.define("ln", new variable_reg(&next_line_number
));
number_reg_dictionary
.define("ct", new variable_reg(&ct_reg_contents
));
number_reg_dictionary
.define("sb", new variable_reg(&sb_reg_contents
));
number_reg_dictionary
.define("st", new variable_reg(&st_reg_contents
));
number_reg_dictionary
.define("rsb", new variable_reg(&rsb_reg_contents
));
number_reg_dictionary
.define("rst", new variable_reg(&rst_reg_contents
));
number_reg_dictionary
.define("ssc", new variable_reg(&ssc_reg_contents
));
number_reg_dictionary
.define("skw", new variable_reg(&skw_reg_contents
));
number_reg_dictionary
.define("hp", new horizontal_place_reg
);
// Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
const int WORD_MAX
= 1024;
dictionary
exception_dictionary(501);
static void hyphen_word()
unsigned char pos
[WORD_MAX
+ 2];
if (tok
.newline() || tok
.eof())
while (i
< WORD_MAX
&& !tok
.space() && !tok
.newline() && !tok
.eof()) {
charinfo
*ci
= tok
.get_char(1);
if (ci
->get_ascii_code() == '-') {
if (i
> 0 && (npos
== 0 || pos
[npos
- 1] != i
))
int c
= ci
->get_hyphenation_code();
unsigned char *tem
= new unsigned char[npos
+ 1];
memcpy(tem
, pos
, npos
+1);
tem
= (unsigned char *)exception_dictionary
.lookup(symbol(buf
), tem
);
virtual void do_match(int len
, void *val
) = 0;
void insert(unsigned char *, int, void *);
// find calls do_match for each match it finds
void find(unsigned char *pat
, int patlen
);
trie_node(unsigned char, trie_node
*);
trie_node::trie_node(unsigned char ch
, trie_node
*p
)
: c(ch
), right(p
), down(0), val(0)
void trie::insert(unsigned char *pat
, int patlen
, void *val
)
assert(patlen
> 0 && pat
!= 0);
while (*p
!= 0 && (*p
)->c
< pat
[0])
if (*p
== 0 || (*p
)->c
!= pat
[0])
*p
= new trie_node(pat
[0], *p
);
void trie::find(unsigned char *pat
, int patlen
)
for (int i
= 0; p
!= 0 && i
< patlen
; i
++) {
while (p
!= 0 && p
->c
< pat
[i
])
if (p
!= 0 && p
->c
== pat
[i
]) {
class hyphen_trie
: private trie
{
void do_match(int i
, void *v
);
void insert_pattern(unsigned char *pat
, int patlen
, int *num
);
void hyphenate(unsigned char *word
, int len
, int *hyphens
);
void read_patterns_file(const char *name
);
operation(int, int, operation
*);
operation::operation(int i
, int j
, operation
*op
)
: num(i
), distance(j
), next(op
)
void hyphen_trie::insert_pattern(unsigned char *pat
, int patlen
, int *num
)
for (int i
= 0; i
< patlen
+1; i
++)
op
= new operation(num
[i
], patlen
- i
, op
);
void hyphen_trie::hyphenate(unsigned char *word
, int len
, int *hyphens
)
for (int j
= 0; j
< len
+1; j
++)
for (j
= 0; j
< len
- 1; j
++) {
inline int max(int m
, int n
)
void hyphen_trie::do_match(int i
, void *v
)
operation
*op
= (operation
*)v
;
h
[i
- op
->distance
] = max(h
[i
- op
->distance
], op
->num
);
void hyphen_trie::read_patterns_file(const char *name
)
unsigned char buf
[WORD_MAX
];
FILE *fp
= fopen(name
, "r");
fatal("can't open hyphenation patterns file `%1': %2",
while (c
!= EOF
&& csspace(c
))
} while (i
< WORD_MAX
&& c
!= EOF
&& !csspace(c
));
insert_pattern(buf
, i
, num
);
void hyphenate(hyphen_list
*h
, unsigned flags
)
while (h
&& h
->hyphenation_code
== 0)
for (hyphen_list
*tem
= h
; tem
&& len
< WORD_MAX
; tem
= tem
->next
) {
if (tem
->hyphenation_code
!= 0)
buf
[len
++] = tem
->hyphenation_code
;
unsigned char *pos
= (unsigned char *)exception_dictionary
.lookup(buf
);
for (tem
= h
; tem
!= 0; tem
= tem
->next
, i
++)
ht
.hyphenate((unsigned char *)hbuf
, len
+2, num
);
for (i
= 2, tem
= h
; i
< len
&& tem
; tem
= tem
->next
, i
++)
void read_hyphen_file(const char *name
)
ht
.read_patterns_file(name
);
void init_hyphen_requests()
init_request("hw", hyphen_word
);