$VERSION
=
'0.76'
;
my
$edge_styles
= [
{
'solid'
=> [
'--'
,
"|"
,
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
'double'
=> [
'=='
,
"H"
,
"#"
,
'#'
,
'#'
,
'#'
,
'#'
], # double line
'double-dash'
=> [
'= '
,
'"'
, "
'dotted'
=> [
'..'
,
":"
,
':'
,
'.'
,
'.'
,
'.'
,
'.'
],
'dashed'
=> [
'- '
,
"'"
, '+
', '
+
','
+
','
+
','
+' ],
'dot-dash'
=> [
'.-'
,
"!"
,
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
'dot-dot-dash'
=> [
'..-'
,
"!"
,
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
'wave'
=> [
'~~'
,
"}"
,
'+'
,
'*'
,
'*'
,
'*'
,
'*'
],
'bold'
=> [
'##'
,
"#"
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
], # bold
'bold-dash'
=> [
'# '
,
"#"
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
], # bold-dash
'wide'
=> [
'##'
,
"#"
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
], # wide
'broad'
=> [
'##'
,
"#"
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
], # broad
},
{
'solid'
=> [
'─'
,
'│'
,
'┼'
,
'┌'
,
'┐'
,
'└'
,
'┘'
],
'double'
=> [
'═'
,
'║'
,
'╬'
,
'╔'
,
'╗'
,
'╚'
,
'╝'
],
'double-dash'
=> [
'═'
.
' '
,
'∥'
,
'╬'
,
'╔'
,
'╗'
,
'╚'
,
'╝'
],
'dotted'
=> [
'·'
,
':'
,
'┼'
,
'┌'
,
'┐'
,
'└'
,
'┘'
],
'dashed'
=> [
'╴'
,
'╵'
,
'┘'
,
'┌'
,
'┐'
,
'╵'
,
'┘'
],
'dot-dash'
=> [
'·'
.
'-'
,
"!"
,
'┼'
,
'┌'
,
'┐'
,
'└'
,
'┘'
],
'dot-dot-dash'
=> [ (
'·'
x 2).
'-'
,
"!"
,
'┼'
,
'┌'
,
'┐'
,
'└'
,
'┘'
],
'wave'
=> [
'∼'
,
'≀'
,
'┼'
,
'┌'
,
'┐'
,
'└'
,
'┘'
],
'bold'
=> [
'━'
,
'┃'
,
'╋'
,
'┏'
,
'┓'
,
'┗'
,
'┛'
],
'bold-dash'
=> [
'━'
.
' '
,
'╻'
,
'╋'
,
'┏'
,
'┓'
,
'┗'
,
'┛'
],
'broad'
=> [
'▬'
,
'▮'
,
'█'
,
'█'
,
'█'
,
'█'
,
'█'
],
'wide'
=> [
'█'
,
'█'
,
'█'
,
'█'
,
'█'
,
'█'
,
'█'
],
},
];
sub
_edge_style
{
my
(
$self
,
$st
) =
@_
;
my
$g
=
$self
->{graph}->{_ascii_style} || 0;
$st
=
$self
->{style}
unless
defined
$st
;
$edge_styles
->[
$g
]->{
$st
};
}
my
$cross_styles
= [
[
{
'boldsolid'
=>
'┿'
,
'solidbold'
=>
'╂'
,
'doublesolid'
=>
'╪'
,
'soliddouble'
=>
'╫'
,
'dashedsolid'
=>
'┤'
,
'soliddashed'
=>
'┴'
,
'doubledashed'
=>
'╧'
,
'dasheddouble'
=>
'╢'
,
},
{
'boldsolid'
=>
'+'
,
'dashedsolid'
=>
'+'
,
'dottedsolid'
=>
'!'
,
'dottedwave'
=>
'+'
,
'doublesolid'
=>
'+'
,
'dot-dashsolid'
=>
'+'
,
'dot-dot-dashsolid'
=>
'+'
,
'soliddotted'
=>
'+'
,
'solidwave'
=>
'+'
,
'soliddashed'
=>
'+'
,
'soliddouble'
=>
'H'
,
'wavesolid'
=>
'+'
,
},
],
undef
,
undef
,
undef
,
undef
,
undef
,
undef
,
[
{
'solidsolid'
=>
'┬'
,
'boldbold'
=>
'┳'
,
'doubledouble'
=>
'╦'
,
'dasheddashed'
=>
'╴'
,
'dotteddotted'
=>
'·'
,
},
],
[
{
'solidsolid'
=>
'┴'
,
'boldbold'
=>
'┻'
,
'doubledouble'
=>
'╩'
,
'dotteddotted'
=>
'·'
,
},
],
[
{
'solidsolid'
=>
'├'
,
'boldbold'
=>
'┣'
,
'doubledouble'
=>
'╠'
,
'dotteddotted'
=>
':'
,
},
],
[
{
'solidsolid'
=>
'┤'
,
'boldbold'
=>
'┫'
,
'doubledouble'
=>
'╣'
,
'dotteddotted'
=>
':'
,
},
] ];
sub
_arrow_style
{
my
$self
=
shift
;
my
$edge
=
$self
->{edge};
my
$as
=
$edge
->attribute(
'arrowstyle'
);
$as
=
'none'
if
$edge
->{undirected};
$as
;
}
sub
_arrow_shape
{
my
$self
=
shift
;
my
$edge
=
$self
->{edge};
my
$as
=
$edge
->attribute(
'arrowshape'
);
$as
;
}
sub
_cross_style
{
my
(
$self
,
$st
,
$corner_type
) =
@_
;
my
$g
=
$self
->{graph}->{_ascii_style} || 0;
$g
= 1 -
$g
;
$corner_type
= 0
unless
defined
$corner_type
;
$corner_type
= 0
if
$g
== 1;
$cross_styles
->[
$corner_type
]->[
$g
]->{
$st
};
}
sub
_insert_label
{
my
(
$self
,
$fb
,
$xs
,
$ys
,
$ws
,
$hs
,
$align_ver
) =
@_
;
my
$align
=
$self
->{edge}->attribute(
'align'
);
my
(
$lines
,
$aligns
) =
$self
->_aligned_label(
$align
);
$ys
=
$self
->{h} -
scalar
@$lines
+
$ys
if
$ys
< 0;
$ws
||= 0;
$hs
||= 0;
my
$w
=
$self
->{w} -
$ws
-
$xs
;
my
$h
=
$self
->{h} -
$hs
-
$ys
;
$self
->_printfb_aligned (
$fb
,
$xs
,
$ys
,
$w
,
$h
,
$lines
,
$aligns
,
$align_ver
);
}
sub
_draw_hor
{
my
(
$self
,
$fb
) =
@_
;
my
$style
=
$self
->_edge_style();
my
$w
=
$self
->{w};
my
$len
=
length
(
$style
->[0]);
my
$line
=
$style
->[0] x (2 +
$w
/
$len
);
my
$ofs
=
$self
->{rx} %
$len
;
my
$type
= (
$self
->{type} & (~EDGE_MISC_MASK));
substr
(
$line
,0,
$ofs
) =
''
if
$ofs
!= 0
&& (
$type
!= EDGE_SHORT_E &&
$type
!= EDGE_SHORT_W);
$line
=
substr
(
$line
, 0,
$w
)
if
length
(
$line
) >
$w
;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$as
=
$self
->_arrow_style();
my
$ashape
;
$ashape
=
$self
->_arrow_shape()
if
$as
ne
'none'
;
my
$x
= 0;
my
$xs
= 1;
my
$xr
= 0;
if
((
$flags
& EDGE_START_W) != 0)
{
$x
++;
chop
(
$line
);
$xs
++;
}
if
((
$flags
& EDGE_START_E) != 0)
{
chop
(
$line
);
}
if
((
$flags
& EDGE_END_E) != 0)
{
chop
(
$line
);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_RIGHT,
$ashape
)
if
$as
ne
'none'
;
$xr
++;
}
if
((
$flags
& EDGE_END_W) != 0)
{
substr
(
$line
,0,1) =
' '
if
$as
eq
'none'
;
substr
(
$line
,0,2) =
' '
.
$self
->_arrow(
$as
, ARROW_LEFT,
$ashape
)
if
$as
ne
'none'
;
$xs
++;
}
$self
->_printfb_line (
$fb
,
$x
,
$self
->{h} - 2,
$line
);
$self
->_insert_label(
$fb
,
$xs
, 0,
$xs
+
$xr
, 2,
'bottom'
)
if
(
$self
->{type} & EDGE_LABEL_CELL);
}
sub
_draw_ver
{
my
(
$self
,
$fb
) =
@_
;
my
$style
=
$self
->_edge_style();
my
$h
=
$self
->{h};
my
$line
=
$style
->[1] x (1 +
$h
/
length
(
$style
->[1]));
$line
=
substr
(
$line
, 0,
$h
)
if
length
(
$line
) >
$h
;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$as
=
$self
->_arrow_style();
if
(
$as
ne
'none'
)
{
my
$ashape
=
$self
->_arrow_shape();
substr
(
$line
,0,1) =
$self
->_arrow(
$as
,ARROW_UP,
$ashape
)
if
((
$flags
& EDGE_END_N) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
,ARROW_DOWN,
$ashape
)
if
((
$flags
& EDGE_END_S) != 0);
}
$self
->_printfb_ver (
$fb
, 2, 0,
$line
);
$self
->_insert_label(
$fb
, 4, 1, 4, 2,
'middle'
)
if
(
$self
->{type} & EDGE_LABEL_CELL);
}
sub
_draw_cross
{
my
(
$self
,
$fb
) =
@_
;
my
$style
=
$self
->_edge_style(
$self
->{style_ver} );
my
$invisible
= 0;
my
$line
;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$type
=
$self
->{type} & EDGE_TYPE_MASK;
my
$as
=
$self
->_arrow_style();
my
$y
=
$self
->{h} - 2;
print
STDERR
"# drawing cross at $self->{x},$self->{y} with flags $flags\n"
if
$self
->{debug};
if
(
$self
->{style_ver} ne
'invisible'
)
{
my
$h
=
$self
->{h};
$line
=
$style
->[1] x (2 +
$h
/
length
(
$style
->[1]));
$line
=
substr
(
$line
, 0,
$h
)
if
length
(
$line
) >
$h
;
if
(
$as
ne
'none'
)
{
my
$ashape
=
$self
->_arrow_shape();
substr
(
$line
,0,1) =
$self
->_arrow(
$as
,ARROW_UP,
$ashape
)
if
((
$flags
& EDGE_END_N) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
,ARROW_DOWN,
$ashape
)
if
((
$flags
& EDGE_END_S) != 0);
}
substr
(
$line
,0,
$y
) =
' '
x
$y
if
$type
== EDGE_S_E_W;
substr
(
$line
,
$y
,2) =
' '
if
$type
== EDGE_N_E_W;
$self
->_printfb_ver (
$fb
, 2, 0,
$line
);
}
else
{
$invisible
++; }
$style
=
$self
->_edge_style();
my
$ashape
;
$ashape
=
$self
->_arrow_style()
if
$as
ne
'none'
;
if
(
$self
->{style} ne
'invisible'
)
{
my
$w
=
$self
->{w};
my
$len
=
length
(
$style
->[0]);
$line
=
$style
->[0] x (2 +
$w
/
$len
);
my
$ofs
=
$self
->{rx} %
$len
;
substr
(
$line
,0,
$ofs
) =
''
if
$ofs
!= 0;
$line
=
substr
(
$line
, 0,
$w
)
if
length
(
$line
) >
$w
;
my
$x
= 0;
if
((
$flags
& EDGE_START_W) != 0)
{
$x
++;
chop
(
$line
);
}
if
((
$flags
& EDGE_START_E) != 0)
{
chop
(
$line
);
}
if
((
$flags
& EDGE_END_E) != 0)
{
chop
(
$line
);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_RIGHT,
$ashape
)
if
$as
ne
'none'
;
}
if
((
$flags
& EDGE_END_W) != 0)
{
substr
(
$line
,0,1) =
' '
if
$as
eq
'none'
;
substr
(
$line
,0,2) =
' '
.
$self
->_arrow(
$as
, ARROW_LEFT,
$ashape
)
if
$as
ne
'none'
;
}
substr
(
$line
,0,2) =
' '
if
$type
== EDGE_E_N_S;
substr
(
$line
,2,
$self
->{w}-2) =
' '
x (
$self
->{w}-2)
if
$type
== EDGE_W_N_S;
$self
->_printfb_line (
$fb
,
$x
,
$y
,
$line
);
}
else
{
$invisible
++; }
if
(!
$invisible
)
{
my
$cross
=
$style
->[2];
my
$s
=
$self
->{style} .
$self
->{style_ver};
$cross
= (
$self
->_cross_style(
$s
,
$type
) ||
$cross
);
$self
->_printfb (
$fb
, 2,
$y
,
$cross
);
}
}
sub
_draw_corner
{
my
(
$self
,
$fb
) =
@_
;
my
$type
=
$self
->{type} & EDGE_TYPE_MASK;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$style
=
$self
->_edge_style();
my
$h
= 1;
my
$y
=
$self
->{h} -1;
if
(
$type
== EDGE_N_E ||
$type
== EDGE_N_W)
{
$h
=
$self
->{h} - 2;
$y
= 0;
}
my
$line
=
$style
->[1] x (1 +
$h
/
length
(
$style
->[1]));
$line
=
substr
(
$line
, 0,
$h
)
if
length
(
$line
) >
$h
;
my
$as
=
$self
->_arrow_style();
my
$ashape
;
if
(
$as
ne
'none'
)
{
$ashape
=
$self
->_arrow_shape();
substr
(
$line
,0,1) =
$self
->_arrow(
$as
, ARROW_UP,
$ashape
)
if
((
$flags
& EDGE_END_N) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_DOWN,
$ashape
)
if
((
$flags
& EDGE_END_S) != 0);
}
$self
->_printfb_ver (
$fb
, 2,
$y
,
$line
);
my
$w
=
$self
->{w} - 3;
$y
=
$self
->{h} - 2;
my
$x
= 3;
if
(
$type
== EDGE_N_W ||
$type
== EDGE_S_W)
{
$w
= 2;
$x
= 0;
}
my
$len
=
length
(
$style
->[0]);
$line
=
$style
->[0] x (2 +
$w
/
$len
);
my
$ofs
= (
$x
+
$self
->{rx}) %
$len
;
substr
(
$line
,0,
$ofs
) =
''
if
$ofs
!= 0;
$line
=
substr
(
$line
, 0,
$w
)
if
length
(
$line
) >
$w
;
substr
(
$line
,-1,1) =
' '
if
(
$flags
& EDGE_START_E) != 0;
substr
(
$line
,0,1) =
' '
if
(
$flags
& EDGE_START_W) != 0;
if
((
$flags
& EDGE_END_E) != 0)
{
substr
(
$line
,-1,1) =
' '
if
$as
eq
'none'
;
substr
(
$line
,-2,2) =
$self
->_arrow(
$as
, ARROW_RIGHT,
$ashape
) .
' '
if
$as
ne
'none'
;
}
if
((
$flags
& EDGE_END_W) != 0)
{
substr
(
$line
,0,1) =
' '
if
$as
eq
'none'
;
substr
(
$line
,0,2) =
' '
.
$self
->_arrow(
$as
, ARROW_LEFT,
$ashape
)
if
$as
ne
'none'
;
}
$self
->_printfb_line (
$fb
,
$x
,
$y
,
$line
);
my
$idx
= 3;
$idx
= 4
if
$type
== EDGE_S_W;
$idx
= 5
if
$type
== EDGE_N_E;
$idx
= 6
if
$type
== EDGE_N_W;
$self
->_printfb (
$fb
, 2,
$y
,
$style
->[
$idx
]);
}
sub
_draw_loop_hor
{
my
(
$self
,
$fb
) =
@_
;
my
$type
=
$self
->{type} & EDGE_TYPE_MASK;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$style
=
$self
->_edge_style();
my
$h
= 1;
my
$y
=
$self
->{h} - 1;
if
(
$type
== EDGE_S_W_N)
{
$h
=
$self
->{h} - 2;
$y
= 0;
}
my
$line
=
$style
->[1] x (1 +
$h
/
length
(
$style
->[1]));
$line
=
substr
(
$line
, 0,
$h
)
if
length
(
$line
) >
$h
;
my
$as
=
$self
->_arrow_style();
my
$ashape
;
$ashape
=
$self
->_arrow_shape()
if
$as
ne
'none'
;
if
(
$self
->{edge}->{bidirectional} &&
$as
ne
'none'
)
{
substr
(
$line
,0,1) =
$self
->_arrow(
$as
, ARROW_UP,
$ashape
)
if
((
$flags
& EDGE_END_N) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_DOWN,
$ashape
)
if
((
$flags
& EDGE_END_S) != 0);
}
$self
->_printfb_ver (
$fb
,
$self
->{w}-3,
$y
,
$line
);
if
(
$as
ne
'none'
)
{
substr
(
$line
,0,1) =
$self
->_arrow(
$as
, ARROW_UP,
$ashape
)
if
((
$flags
& EDGE_END_N) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_DOWN,
$ashape
)
if
((
$flags
& EDGE_END_S) != 0);
}
$self
->_printfb_ver (
$fb
, 2,
$y
,
$line
);
my
$w
=
$self
->{w} - 6;
$y
=
$self
->{h} - 2;
my
$x
= 3;
my
$len
=
length
(
$style
->[0]);
$line
=
$style
->[0] x (2 +
$w
/
$len
);
my
$ofs
= (
$x
+
$self
->{rx}) %
$len
;
substr
(
$line
,0,
$ofs
) =
''
if
$ofs
!= 0;
$line
=
substr
(
$line
, 0,
$w
)
if
length
(
$line
) >
$w
;
$self
->_printfb_line (
$fb
,
$x
,
$y
,
$line
);
my
$corner_idx
= 3;
$corner_idx
= 5
if
$type
== EDGE_S_W_N;
$self
->_printfb (
$fb
, 2,
$y
,
$style
->[
$corner_idx
]);
$self
->_printfb (
$fb
,
$self
->{w}-3,
$y
,
$style
->[
$corner_idx
+1]);
my
$align
=
'bottom'
;
$align
=
'top'
if
$type
== EDGE_S_W_N;
$self
->_insert_label(
$fb
, 4, 0, 4, 2,
$align
)
if
(
$self
->{type} & EDGE_LABEL_CELL);
}
sub
_draw_loop_ver
{
my
(
$self
,
$fb
) =
@_
;
my
$type
=
$self
->{type} & EDGE_TYPE_MASK;
my
$flags
=
$self
->{type} & EDGE_FLAG_MASK;
my
$style
=
$self
->_edge_style();
my
$h
= 1;
my
$y
=
$self
->{h} - 3;
my
$line
=
$style
->[1] x (1 +
$h
/
length
(
$style
->[1]));
$line
=
substr
(
$line
, 0,
$h
)
if
length
(
$line
) >
$h
;
my
$x
= 2;
$x
=
$self
->{w}-3
if
(
$type
== EDGE_E_S_W);
$self
->_printfb_ver (
$fb
,
$x
,
$y
,
$line
);
my
$w
=
$self
->{w} - 3;
$y
=
$self
->{h} - 4;
$x
= 2;
$x
= 1
if
(
$type
== EDGE_E_S_W);
my
$len
=
length
(
$style
->[0]);
$line
=
$style
->[0] x (2 +
$w
/
$len
);
my
$ofs
= (
$x
+
$self
->{rx}) %
$len
;
substr
(
$line
,0,
$ofs
) =
''
if
$ofs
!= 0;
$line
=
substr
(
$line
, 0,
$w
)
if
length
(
$line
) >
$w
;
my
$as
=
$self
->_arrow_style();
my
$ashape
;
$ashape
=
$self
->_arrow_shape()
if
$as
ne
'none'
;
if
(
$self
->{edge}->{bidirectional} &&
$as
ne
'none'
)
{
substr
(
$line
,0,1) =
$self
->_arrow(
$as
, ARROW_LEFT,
$ashape
)
if
((
$flags
& EDGE_END_W) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_RIGHT,
$ashape
)
if
((
$flags
& EDGE_END_E) != 0);
}
$self
->_printfb_line (
$fb
,
$x
,
$y
,
$line
);
if
(
$as
ne
'none'
)
{
substr
(
$line
,0,1) =
$self
->_arrow(
$as
, ARROW_LEFT,
$ashape
)
if
((
$flags
& EDGE_END_W) != 0);
substr
(
$line
,-1,1) =
$self
->_arrow(
$as
, ARROW_RIGHT,
$ashape
)
if
((
$flags
& EDGE_END_E) != 0);
}
$self
->_printfb_line (
$fb
,
$x
,
$self
->{h} - 2,
$line
);
$x
= 2;
$x
=
$self
->{w}-3
if
(
$type
== EDGE_E_S_W);
my
$corner_idx
= 3;
$corner_idx
= 4
if
$type
== EDGE_E_S_W;
$self
->_printfb (
$fb
,
$x
,
$y
,
$style
->[
$corner_idx
]);
$self
->_printfb (
$fb
,
$x
,
$self
->{h}-2,
$style
->[
$corner_idx
+2]);
$x
= 4;
$x
= 3
if
(
$type
== EDGE_E_S_W);
$self
->_insert_label(
$fb
,
$x
, 0,
$x
, 4,
'bottom'
)
if
(
$self
->{type} & EDGE_LABEL_CELL);
}
my
$draw_dispatch
=
{
EDGE_HOR() =>
'_draw_hor'
,
EDGE_VER() =>
'_draw_ver'
,
EDGE_S_E() =>
'_draw_corner'
,
EDGE_S_W() =>
'_draw_corner'
,
EDGE_N_E() =>
'_draw_corner'
,
EDGE_N_W() =>
'_draw_corner'
,
EDGE_CROSS() =>
'_draw_cross'
,
EDGE_W_N_S() =>
'_draw_cross'
,
EDGE_E_N_S() =>
'_draw_cross'
,
EDGE_N_E_W() =>
'_draw_cross'
,
EDGE_S_E_W() =>
'_draw_cross'
,
EDGE_N_W_S() =>
'_draw_loop_hor'
,
EDGE_S_W_N() =>
'_draw_loop_hor'
,
EDGE_E_S_W() =>
'_draw_loop_ver'
,
EDGE_W_S_E() =>
'_draw_loop_ver'
,
};
sub
_draw_label
{
my
(
$self
,
$fb
,
$x
,
$y
) =
@_
;
my
$type
=
$self
->{type} & EDGE_TYPE_MASK;
return
if
$self
->attribute(
'style'
) eq
'invisible'
&&
$type
ne EDGE_CROSS;
my
$m
=
$draw_dispatch
->{
$type
};
$self
->_croak(
"Unknown edge type $type"
)
unless
defined
$m
;
$self
->{rx} =
$x
|| 0;
$self
->{ry} =
$y
|| 0;
$self
->
$m
(
$fb
);
delete
$self
->{rx};
delete
$self
->{ry};
}
sub
_framebuffer
{
my
(
$self
,
$w
,
$h
) =
@_
;
print
STDERR
"# trying to generate framebuffer of undefined width for $self->{name}\n"
,
join
(
": "
,
caller
(),
"\n"
)
if
!
defined
$w
;
my
@fb
;
my
$line
=
' '
x
$w
;
for
my
$y
(1..
$h
)
{
push
@fb
,
$line
;
}
\
@fb
;
}
sub
_printfb_aligned
{
my
(
$self
,
$fb
,
$x1
,
$y1
,
$w
,
$h
,
$lines
,
$aligns
,
$align_ver
) =
@_
;
$align_ver
=
'middle'
unless
$align_ver
;
my
$y
=
$y1
+ (
$h
/ 2) - (
scalar
@$lines
/ 2);
if
(
$align_ver
eq
'top'
)
{
$y
=
$y1
;
$y1
= 0;
}
if
(
$align_ver
eq
'bottom'
)
{
$y
=
$h
-
scalar
@$lines
;
$y1
= 0;
}
my
$xc
= (
$w
/ 2);
my
$i
= 0;
while
(
$i
<
@$lines
)
{
my
(
$l
,
$al
) = (
$lines
->[
$i
],
$aligns
->[
$i
]);
my
$x
= 0;
$x
=
$xc
-
length
(
$l
) / 2
if
$al
eq
'c'
;
$x
=
$w
-
length
(
$l
)
if
$al
eq
'r'
;
substr
(
$fb
->[
int
(
$y
+
$i
+
$y1
)],
int
(
$x
+
$x1
),
length
(
$l
)) =
$l
;
$i
++;
}
}
sub
_printfb_line
{
my
(
$self
,
$fb
,
$x
,
$y
,
$l
) =
@_
;
substr
(
$fb
->[
$y
],
$x
,
length
(
$l
)) =
$l
;
}
sub
_printfb
{
my
(
$self
,
$fb
,
$x
,
$y
,
@lines
) =
@_
;
for
my
$l
(
@lines
)
{
substr
(
$fb
->[
$y
],
$x
,
length
(
$l
)) =
$l
;
$y
++;
}
}
sub
_printfb_ver
{
my
(
$self
,
$fb
,
$x
,
$y
,
$line
) =
@_
;
my
$y1
=
$y
+
length
(
$line
);
substr
(
$fb
->[
$y1
],
$x
, 1) =
chop
(
$line
)
while
(
$y1
-- >
$y
);
}
my
$border_styles
=
[
{
solid
=> [
'+'
,
'+'
,
'+'
,
'+'
,
'-'
,
'-'
, [
'|'
], [
'|'
],
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
dotted
=> [
'.'
,
'.'
,
':'
,
':'
,
'.'
,
'.'
, [
':'
], [
':'
],
'.'
,
'.'
,
'.'
,
'.'
,
'.'
],
dashed
=> [
'+'
,
'+'
,
'+'
,
'+'
,
'- '
,
'- '
, [
"'"
], [
"'"
],
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
'dot-dash'
=> [
'+'
,
'+'
,
'+'
,
'+'
,
'.-'
,
'.-'
, [
'!'
], [
'!'
],
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
'dot-dot-dash'
=> [
'+'
,
'+'
,
'+'
,
'+'
,
'..-'
,
'..-'
, [
'|'
,
':'
], [
'|'
,
':'
],
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
bold
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
, [
'#'
], [
'#'
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
'bold-dash'
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'# '
,
'# '
, [
'#'
,
' '
], [
'#'
,
' '
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
double
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'='
,
'='
, [
'H'
], [
'H'
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
'double-dash'
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'= '
,
'= '
, [
'"'
], [
'"'
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
wave
=> [
'+'
,
'+'
,
'+'
,
'+'
,
'~'
,
'~'
, [
'{'
,
'}'
], [
'{'
,
'}'
],
'+'
,
'+'
,
'+'
,
'+'
,
'+'
],
broad
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
, [
'#'
], [
'#'
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
wide
=> [
'#'
,
'#'
,
'#'
,
'#'
,
'#'
,
'#'
, [
'#'
], [
'#'
],
'#'
,
'#'
,
'#'
,
'#'
,
'#'
],
none
=> [
' '
,
' '
,
' '
,
' '
,
' '
,
' '
, [
' '
], [
' '
],
' '
,
' '
,
' '
,
' '
,
' '
],
},
{
solid
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
,
'─'
,
'─'
, [
'│'
], [
'│'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
double
=> [
'╔'
,
'╗'
,
'╝'
,
'╚'
,
'═'
,
'═'
, [
'║'
], [
'║'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
dotted
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
,
'⋯'
,
'⋯'
, [
'⋮'
], [
'⋮'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
dashed
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
,
'−'
,
'−'
, [
'╎'
], [
'╎'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
'dot-dash'
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
,
'·'
.
'-'
,
'·'
.
'-'
, [
'!'
], [
'!'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
'dot-dot-dash'
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
, (
'·'
x 2) .
'-'
, (
'·'
x 2) .
'-'
, [
'│'
,
':'
], [
'│'
,
':'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
bold
=> [
'┏'
,
'┓'
,
'┛'
,
'┗'
,
'━'
,
'━'
, [
'┃'
], [
'┃'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
'bold-dash'
=> [
'┏'
,
'┓'
,
'┛'
,
'┗'
,
'━'
.
' '
,
'━'
.
' '
, [
'╻'
], [
'╻'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
'double-dash'
=> [
'╔'
,
'╗'
,
'╝'
,
'╚'
,
'═'
.
' '
,
'═'
.
' '
, [
'∥'
], [
'∥'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
wave
=> [
'┌'
,
'┐'
,
'┘'
,
'└'
,
'∼'
,
'∼'
, [
'≀'
], [
'≀'
],
'┼'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
broad
=> [
'▛'
,
'▜'
,
'▟'
,
'▙'
,
'▀'
,
'▄'
, [
'▌'
], [
'▐'
],
'▄'
,
'├'
,
'┤'
,
'┴'
,
'┬'
],
wide
=> [
'█'
,
'█'
,
'█'
,
'█'
,
'█'
,
'█'
, [
'█'
], [
'█'
],
'█'
,
'█'
,
'█'
,
'█'
,
'█'
],
none
=> [
' '
,
' '
,
' '
,
' '
,
' '
,
' '
, [
' '
], [
' '
],
' '
,
' '
,
' '
,
' '
,
' '
, ],
},
];
my
$rounded_edges
= [
'╭'
,
'╮'
,
'╯'
,
'╰'
, ];
my
$slants
= [
{
solid
=> [
'/'
,
'\\'
],
dotted
=> [
'.'
,
'.'
],
dashed
=> [
'/ '
,
'\\ '
],
'dot-dash'
=> [
'./'
,
'.\\'
],
'dot-dot-dash'
=> [
'../'
,
'..\\'
],
bold
=> [
'#'
,
'#'
],
'bold-dash'
=> [
'# '
,
'# '
],
'double'
=> [
'/'
,
'\\'
],
'double-dash'
=> [
'/ '
,
'\\ '
],
wave
=> [
'/ '
,
'\\ '
],
broad
=> [
'#'
,
'#'
],
wide
=> [
'#'
,
'#'
],
},
{
solid
=> [
'╱'
,
'╲'
],
dotted
=> [
'⋰'
,
'⋱'
],
dashed
=> [
'╱ '
,
'╲ '
],
'dot-dash'
=> [
'.╱'
,
'.╲'
],
'dot-dot-dash'
=> [
'⋰╱'
,
'⋱╲'
],
bold
=> [
'#'
,
'#'
],
'bold-dash'
=> [
'# '
,
'# '
],
'double'
=> [
'╱'
,
'╲'
],
'double-dash'
=> [
'╱ '
,
'╲ '
],
wave
=> [
'╱ '
,
'╲ '
],
broad
=> [
'#'
,
'#'
],
wide
=> [
'#'
,
'#'
],
},
];
my
$point_shapes
=
[ {
filled
=>
{
'star'
=>
'*'
,
'square'
=>
'#'
,
'dot'
=>
'.'
,
'circle'
=>
'o'
,
'cross'
=>
'+'
,
'diamond'
=>
'<>'
,
'x'
=>
'X'
,
},
closed
=>
{
'star'
=>
'*'
,
'square'
=>
'#'
,
'dot'
=>
'.'
,
'circle'
=>
'o'
,
'cross'
=>
'+'
,
'diamond'
=>
'<>'
,
'x'
=>
'X'
,
},
},
{
filled
=>
{
'star'
=>
'★'
,
'square'
=>
'■'
,
'dot'
=>
'·'
,
'circle'
=>
'●'
,
'cross'
=>
'+'
,
'diamond'
=>
'◆'
,
'x'
=>
'╳'
,
},
closed
=>
{
'star'
=>
'☆'
,
'square'
=>
'□'
,
'dot'
=>
'·'
,
'circle'
=>
'○'
,
'cross'
=>
'+'
,
'diamond'
=>
'◇'
,
'x'
=>
'╳'
,
},
}
];
sub
_point_style
{
my
(
$self
,
$shape
,
$style
) =
@_
;
return
''
if
$shape
eq
'invisible'
;
if
(
$style
=~ /^(star|square|dot|circle|cross|diamond)\z/)
{
$shape
=
$style
;
$style
=
'filled'
;
}
$style
=
'filled'
unless
defined
$style
;
my
$g
=
$self
->{graph}->{_ascii_style} || 0;
$point_shapes
->[
$g
]->{
$style
}->{
$shape
};
}
sub
_border_style
{
my
(
$self
,
$style
,
$type
) =
@_
;
my
$g
=
$self
->{graph}->{_ascii_style} || 0;
my
$s
= [ @{
$border_styles
->[
$g
]->{
$style
} } ];
die
(
"Unknown $type border style '$style'"
)
if
@$s
== 0;
my
$shape
=
'rect'
;
$shape
=
$self
->attribute(
'shape'
)
unless
$self
->isa_cell();
return
$s
unless
$shape
eq
'rounded'
;
splice
(
@$s
, 0, 4,
@$rounded_edges
)
if
$style
=~ /^(solid|dotted|dashed|dot-dash|dot-dot-dash)\z/;
splice
(
@$s
, 0, 4, (
' '
,
' '
,
' '
,
' '
))
if
$g
== 0 ||
$style
=~ /^(bold|wide|broad|double|double-dash|bold-dash)\z/;
$s
;
}
my
$arrow_form
=
{
normal
=> 0,
sleek
=> 1,
};
my
$arrow_shapes
=
{
triangle
=> 0,
diamond
=> 1,
box
=> 2,
dot
=> 3,
inv
=> 4,
line
=> 5,
cross
=> 6,
x
=> 7,
};
my
$arrow_styles
=
[
[
{
open
=> [
'>'
,
'<'
,
'^'
,
'v'
],
closed
=> [
'>'
,
'<'
,
'^'
,
'v'
],
filled
=> [
'>'
,
'<'
,
'^'
,
'v'
],
},
{
open
=> [
'>'
,
'<'
,
'∧'
,
'∨'
],
closed
=> [
'▷'
,
'◁'
,
'△'
,
'▽'
],
filled
=> [
'▶'
,
'◀'
,
'▲'
,
'▼'
],
}
], [
{
open
=> [
'>'
,
'<'
,
'^'
,
'v'
],
closed
=> [
'>'
,
'<'
,
'^'
,
'v'
],
filled
=> [
'>'
,
'<'
,
'^'
,
'v'
],
},
{
open
=> [
'>'
,
'<'
,
'∧'
,
'∨'
],
closed
=> [
'◇'
,
'◇'
,
'◇'
,
'◇'
],
filled
=> [
'◆'
,
'◆'
,
'◆'
,
'◆'
],
}
], [
{
open
=> [
']'
,
'['
,
'°'
,
'u'
],
closed
=> [
'D'
,
'D'
,
'D'
,
'D'
],
filled
=> [
'#'
,
'#'
,
'#'
,
'#'
],
},
{
open
=> [
'⊐'
,
'⊐'
,
'⊓'
,
'⊔'
],
closed
=> [
'◻'
,
'◻'
,
'◻'
,
'◻'
],
filled
=> [
'◼'
,
'◼'
,
'◼'
,
'◼'
],
}
], [
{
open
=> [
')'
,
'('
,
'^'
,
'u'
],
closed
=> [
'o'
,
'o'
,
'o'
,
'o'
],
filled
=> [
'*'
,
'*'
,
'*'
,
'*'
],
},
{
open
=> [
')'
,
'('
,
'◠'
,
'◡'
],
closed
=> [
'○'
,
'○'
,
'○'
,
'○'
],
filled
=> [
'●'
,
'●'
,
'●'
,
'●'
],
}
], [
{
open
=> [
'<'
,
'>'
,
'v'
,
'^'
],
closed
=> [
'<'
,
'>'
,
'v'
,
'^'
],
filled
=> [
'<'
,
'>'
,
'v'
,
'^'
],
},
{
open
=> [
'<'
,
'>'
,
'∨'
,
'∧'
],
closed
=> [
'◁'
,
'▷'
,
'▽'
,
'△'
],
filled
=> [
'◀'
,
'▶'
,
'▼'
,
'▲'
],
}
], [
{
open
=> [
'|'
,
'|'
,
'_'
,
'-'
],
closed
=> [
'|'
,
'|'
,
'_'
,
'-'
],
filled
=> [
'|'
,
'|'
,
'_'
,
'-'
],
},
{
open
=> [
'⎥'
,
'⎢'
,
'_'
,
'¯'
],
closed
=> [
'⎥'
,
'⎢'
,
'_'
,
'¯'
],
filled
=> [
'⎥'
,
'⎢'
,
'_'
,
'¯'
],
}
], [
{
open
=> [
'+'
,
'+'
,
'+'
,
'+'
],
closed
=> [
'+'
,
'+'
,
'+'
,
'+'
],
filled
=> [
'+'
,
'+'
,
'+'
,
'+'
],
},
{
open
=> [
'┼'
,
'┼'
,
'┼'
,
'┼'
],
closed
=> [
'┼'
,
'┼'
,
'┼'
,
'┼'
],
filled
=> [
'┼'
,
'┼'
,
'┼'
,
'┼'
],
}
], [
{
open
=> [
'x'
,
'x'
,
'x'
,
'x'
],
closed
=> [
'x'
,
'x'
,
'x'
,
'x'
],
filled
=> [
'x'
,
'x'
,
'x'
,
'x'
],
},
{
open
=> [
'x'
,
'x'
,
'x'
,
'x'
],
closed
=> [
'x'
,
'x'
,
'x'
,
'x'
],
filled
=> [
'⧓'
,
'⧓'
,
'x'
,
'x'
],
}
]
];
sub
_arrow
{
my
(
$self
,
$style
,
$dir
,
$shape
) =
@_
;
$shape
=
''
unless
defined
$shape
;
$shape
=
$arrow_shapes
->{
$shape
} || 0;
my
$g
=
$self
->{graph}->{_ascii_style} || 0;
$arrow_styles
->[
$shape
]->[
$g
]->{
$style
}->[
$dir
];
}
my
$arrow_dir
= {
'>'
=> 0,
'<'
=> 1,
'^'
=> 2,
'v'
=> 3,
};
sub
_unicode_arrow
{
my
(
$self
,
$shape
,
$style
,
$arrow_text
) =
@_
;
$shape
=
''
unless
defined
$shape
;
$shape
=
$arrow_shapes
->{
$shape
} || 0;
my
$dir
=
$arrow_dir
->{
$arrow_text
} || 0;
$arrow_styles
->[
$shape
]->[1]->{
$style
}->[
$dir
];
}
sub
_draw_border
{
my
(
$self
,
$fb
,
$do_right
,
$do_bottom
,
$do_left
,
$do_top
,
$x
,
$y
) =
@_
;
return
if
$do_right
.
$do_left
.
$do_bottom
.
$do_top
eq
'nonenonenonenone'
;
my
$g
=
$self
->{graph};
my
$w
=
$self
->{w};
if
(
$do_top
ne
'none'
)
{
my
$style
=
$self
->_border_style(
$do_top
,
'top'
);
my
$tl
=
$style
->[0];
$tl
=
''
if
$do_left
eq
'none'
;
my
$top
=
$style
->[4] x ((
$self
->{w}) /
length
(
$style
->[4]) + 1);
my
$len
=
length
(
$style
->[4]);
if
(
defined
$x
)
{
my
$ofs
=
$x
%
$len
;
substr
(
$top
,0,
$ofs
) =
''
if
$ofs
!= 0;
}
substr
(
$top
,0,1) =
$tl
if
$tl
ne
''
;
$top
=
substr
(
$top
,0,
$w
)
if
length
(
$top
) >
$w
;
substr
(
$top
,-1,1) =
$style
->[1]
if
$do_right
ne
'none'
;
if
(
$self
->{border_collapse_right})
{
substr
(
$top
,-1,1) =
$style
->[10];
}
$self
->_printfb(
$fb
, 0,0,
$top
);
}
if
(
$do_bottom
ne
'none'
)
{
my
$style
=
$self
->_border_style(
$do_bottom
,
'bottom'
);
my
$bl
=
$style
->[3];
$bl
=
''
if
$do_left
eq
'none'
;
my
$bottom
=
$style
->[5] x ((
$self
->{w}) /
length
(
$style
->[5]) + 1);
my
$len
=
length
(
$style
->[5]);
if
(
defined
$x
)
{
my
$ofs
=
$x
%
$len
;
substr
(
$bottom
,0,
$ofs
) =
''
if
$ofs
!= 0;
}
substr
(
$bottom
,0,1) =
$bl
if
$bl
ne
''
;
$bottom
=
substr
(
$bottom
,0,
$w
)
if
length
(
$bottom
) >
$w
;
substr
(
$bottom
,-1,1) =
$style
->[2]
if
$do_right
ne
'none'
;
if
(
$self
->{border_collapse_right} ||
$self
->{border_collapse_bottom})
{
if
(
$self
->{rightbelow_count} > 0)
{
my
$piece
= 8;
$piece
= 11
if
$self
->{rightbelow_count} < 2 && !
$self
->{have_below};
$piece
= 10
if
$self
->{rightbelow_count} < 2 && !
$self
->{have_right};
substr
(
$bottom
,-1,1) =
$style
->[
$piece
];
}
}
$self
->_printfb(
$fb
, 0,
$self
->{h}-1,
$bottom
);
}
return
if
$do_right
.
$do_left
eq
'nonenone'
;
my
$style
=
$self
->_border_style(
$do_left
,
'left'
);
my
$left
=
$style
->[6];
my
$lc
=
scalar
@{
$style
->[6] } - 1;
$style
=
$self
->_border_style(
$do_right
,
'right'
);
my
$right
=
$style
->[7];
my
$rc
=
scalar
@{
$style
->[7] } - 1;
my
(
@left
,
@right
);
my
$l
= 0;
my
$r
= 0;
my
$s
= 1;
$s
= 0
if
$do_top
eq
'none'
;
my
$h
=
$self
->{h} - 2;
$h
++
if
defined
$x
&&
$do_bottom
eq
'none'
;
for
(
$s
..
$h
)
{
push
@left
,
$left
->[
$l
];
$l
++;
$l
= 0
if
$l
>
$lc
;
push
@right
,
$right
->[
$r
];
$r
++;
$r
= 0
if
$r
>
$rc
;
}
$self
->_printfb(
$fb
, 0,
$s
,
@left
)
unless
$do_left
eq
'none'
;
$self
->_printfb(
$fb
,
$w
-1,
$s
,
@right
)
unless
$do_right
eq
'none'
;
$self
;
}
sub
_draw_label
{
my
(
$self
,
$fb
,
$x
,
$y
,
$shape
) =
@_
;
if
(
$shape
eq
'point'
)
{
my
$style
=
$self
->attribute(
'pointstyle'
);
my
$shape
=
$self
->attribute(
'pointshape'
);
my
$l
=
$self
->_point_style(
$shape
,
$style
);
$self
->_printfb_line (
$fb
, 2,
$self
->{h} - 2,
$l
)
if
$l
;
return
;
}
my
$w
=
$self
->{w} - 4;
my
$xs
= 2;
my
$h
=
$self
->{h} - 2;
my
$ys
= 0.5;
my
$border
=
$self
->attribute(
'borderstyle'
);
if
(
$border
eq
'none'
)
{
$w
+= 2;
$h
+= 2;
$xs
= 1;
$ys
= 0;
}
my
$align
=
$self
->attribute(
'align'
);
$self
->_printfb_aligned (
$fb
,
$xs
,
$ys
,
$w
,
$h
,
$self
->_aligned_label(
$align
));
}
sub
as_ascii
{
my
(
$self
,
$x
,
$y
) =
@_
;
my
$shape
=
'rect'
;
$shape
=
$self
->attribute(
'shape'
)
unless
$self
->isa_cell();
if
(
$shape
eq
'edge'
)
{
my
$edge
= Graph::Easy::Edge->new();
my
$cell
= Graph::Easy::Edge::Cell->new(
edge
=>
$edge
,
x
=>
$x
,
y
=>
$y
);
$cell
->{w} =
$self
->{w};
$cell
->{h} =
$self
->{h};
$cell
->{att}->{label} =
$self
->label();
$cell
->{type} =
Graph::Easy::Edge::Cell->EDGE_HOR +
Graph::Easy::Edge::Cell->EDGE_LABEL_CELL;
return
$cell
->as_ascii();
}
return
''
if
$shape
eq
'invisible'
||
$self
->{w} == 0 ||
$self
->{h} == 0;
my
$fb
=
$self
->_framebuffer(
$self
->{w},
$self
->{h});
if
(
$shape
ne
'point'
)
{
my
$cache
=
$self
->{cache};
my
$b_top
=
$cache
->{top_border} ||
'none'
;
my
$b_left
=
$cache
->{left_border} ||
'none'
;
my
$b_right
=
$cache
->{right_border} ||
'none'
;
my
$b_bottom
=
$cache
->{bottom_border} ||
'none'
;
$self
->_draw_border(
$fb
,
$b_right
,
$b_bottom
,
$b_left
,
$b_top
);
}
$self
->_draw_label(
$fb
,
$x
,
$y
,
$shape
);
join
(
"\n"
,
@$fb
);
}
1;