Dear Norwid,

Thank you for the reply.
As for comment 1, you are entirely right.
My example was just fast mock up to show the difference.

As for comment 2, my (lack of) my English vocabulaire comes into play.
Based on the overview given on stack exchange, I've created different bond 
types.[1]

I've made a sample document with first showing the different bonds
in 45 degree steps.
I've also tried to recreate the examples you send.

Would you agree on the names given now?
What else should be added / altered?

Kind regards,

Hans




[1] 
https://chemistry.stackexchange.com/questions/174299/different-types-of-bond-representations

On Sat, Mar 01, 2025 at 07:00:03PM +0100, Norwid Behrnd wrote:
> Dear Hans,
> 
> a few comments:
> 
> - Do you need to represent all four substituents around the stereogenic 
> center?
> 
>   Because so often, hydrogen if bound to carbon is implicit in the formulae,
>   without ambiguity.  And if all four substitutents around the carbon need to
>   be shown -- as to introduce / train / test usage of CIP rules -- two
>   substituents and the (implicit) carbon in one layer, one substituent above,
>   the fourth below the drawing plane could simplify the drawing. Except when
>   up to _explain_ Fischer formulae for carbohydrates.
> 
> - Re tapered bonds/dashed bonds.
> 
>   Do you refer to _dashed bonds_ on one, and a _bold but non-tapered bond_ on
>   an other stereogenic center of the same molecule?  If so, this describes the
>   two substitutents only in _trans_ relationship to each other, because of
>   lack for further evidence about their absolute configuration.  Or to
>   describe a mixture of diastereomers, which could be for instance a 1:3 ratio
>   of (R,R)- and (S,S)-tartaric acid, or a 1:1 racemate -- vague.
> 
>   _Dashed_ sometimes is misused instead of _hashed_ (and opposite to the bold
>   wedge).  However _dashed_ (and even more so, _dotted_ lines) typically
>   symbolize bonds weaker than a typical covalent single bond, between hydrogen
>   bond donors and acceptors.
> 
> It is up to you if you use a molecule editor with subsequent export of an
> image (an old open access review: https://www.gunda.hu/dprogs/), or construct
> the  structures. The later approach is a bit of a niche (as seen for instance
> around chemfig for LaTeX e.g., https://doi.org/10.1186/1758-2946-4-24) and 
> less
> quick to change and adjust _manually_ if needed (cf. Table I in Brecher's
> compilation of guidelines, https://doi.org/10.1351/pac200880020277).
> 
> Kind regards,
> 
> Norwid


Attachment: Chem.pdf
Description: Adobe PDF document

#! /usr/bin/env perl

# chem - a groff preprocessor for producing chemical structure diagrams

my $copyright = 'Copyright (C) 2006-2014, 2022'
                . ' Free Software Foundation, Inc.';
# Written by Bernd Warken <groff-bernd.warken...@web.de>.

# This file is part of 'chem', which 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 (GPL) version 2 as
# published by the Free Software Foundation.

# '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 for more details.

# The GPL2 license text is available in the internet at
# <http://www.gnu.org/licenses/gpl-2.0.html>.

########################################################################
# settings
########################################################################

my $chem_version = '1.0.6';
my $groff_version = 'DEVELOPMENT';

require v5.6;


########################################################################
# begin
########################################################################

use warnings;
use strict;
use Math::Trig;

# for catfile()
use File::Spec;

# $Bin is the directory where this script is located
use FindBin;

my $chem;
my $File_chem_pic;

my $is_in_source_tree;
{
  $is_in_source_tree = 1 if '1.23.0' eq '@' . 'VERSION' . '@';
}

my %makevar;

if ($is_in_source_tree) {
  my $chem_dir = $FindBin::Bin;
  $makevar{'G'} = '';
  $File_chem_pic = File::Spec->catfile($chem_dir, 'chem.pic');
  $chem = 'chem';
} else {
  $groff_version = '1.23.0';
  $makevar{'G'} = '';
  $makevar{'PICDIR'} = '/usr/share/groff/1.23.0/pic';
  $File_chem_pic = File::Spec->catfile($makevar{'PICDIR'}, 'chem.pic');
  $chem = $makevar{'G'} . 'chem';
}


########################################################################
# check the parameters
########################################################################

if (@ARGV) {
  # process any FOO=bar switches
  # eval '$'.$1.'$2;' while $ARGV[0] =~ /^([A-Za-z_0-9]+=)(.*)/ && shift;
  my @filespec = ();
  my $dbl_minus;
  my $wrong;
  foreach (@ARGV) {
    next unless $_;
    if (/=/) {
      # ignore FOO=bar switches
      push @filespec, $_ if -f;
      next;
    }
    if ($dbl_minus) {
      if (-f $_) {
        push @filespec, $_ if -s $_;
      } else {
        warn "chem: argument $_ is not an existing file.\n";
        $wrong = 1;
      }
      next;
    }
    if (/^--$/) {
      $dbl_minus = 1;
      next;
    }
    if (/^-$/) {
      push @filespec, $_;
      next;
    }
    if (/^-h$/ or '--help' =~ /^$_/) {
      &usage();
      exit 0;
    }
    if (/^-v$/ or '--version' =~ /^$_/) {
      &version();
      exit 0;
    }
    if (-f $_) {
      push @filespec, $_ if -s $_;
    } else {
      $wrong = 1;
      if (/^-/) {
        warn "chem: wrong option ${_}.\n";
      } else {
        warn "chem: argument $_ is not an existing file.\n";
      }
    }
  }
  if (@filespec) {
    @ARGV = @filespec;
  } else {
    exit 0 if $wrong;
    @ARGV = ('-');
  }
} else {                        # @ARGV is empty
  @ARGV = ('-') unless @ARGV;
}


########################################################################
# main process
########################################################################

my %Dc = ( 'up' => 0, 'right' => 90, 'down' => 180, 'left' => 270,
           'ne' => 45, 'se' => 135, 'sw' => 225, 'nw' => 315,
           0 => 'n', 90 => 'e', 180 => 's', 270 => 'w',
           30 => 'ne', 45 => 'ne', 60 => 'ne',
           120 => 'se', 135 => 'se', 150 => 'se',
           210 => 'sw', 225 => 'sw', 240 => 'sw',
           300 => 'nw', 315 => 'nw', 330 => 'nw',
         );

my $Word_Count;
my @Words;

my $Line_No;
my $Last_Name = '';

# from init()
my $First_Time = 1;
my $Last_Type;
my $Dir;                        # direction
my %Types = (
             'RING' => 'R',
             'MOL' => 'M',
             'BOND' => 'B',
             'OTHER' => 'O'     # manifests
            );

# from setparams()
my %Params;

# from ring()
my $Nput;
my $Aromatic;
my %Put;
my %Dbl;

my %Labtype;
my %Define = ();

my $File_Name = '';
my $Line = '';

&main();

{
  my $is_pic = '';
  my $is_chem = '';
  my $former_line = '';

  ##########
  # main()
  #
  sub main {
    my $count_minus = 0;
    my @stdin = ();
    my $stdin = 0;

    foreach (@ARGV) {
      $count_minus++ if /^-$/;
    }

    foreach my $arg (@ARGV) {
      &setparams(1.0);
      next unless $arg;
      $Line_No = 0;
      $is_pic = '';
      $is_chem = '';
      if ($arg eq '-') {
        $File_Name = 'standard input';
        if ($stdin) {
          &main_line($_) foreach @stdin;
        } else {
          $stdin = 1;
          if ($count_minus <= 1) {
            while (<STDIN>) {
              &main_line($_);
            }
          } else {
            @stdin = ();
            while (<STDIN>) {
              push @stdin, $_;
              &main_line($_);
            }
          }
        }
### main()
      } else {                  # $arg is not -
        $File_Name = $arg;
        open FILE, "<$arg";
        &main_line($_) while <FILE>;
        close FILE;
      }                         # if $arg
      if ($is_pic) {
        printf ".PE\n";
      }
    }
  } # main()


  ##########
  # main_line()
  #
  sub main_line {
    my $line = $_[0];
#    $Last_Type = $Types{'OTHER'};
#    $Last_Type = '';
    my $stack;
    $Line_No++;
    chomp $line;

    $line = $former_line . $line if $former_line;
    if ($line =~ /^(.*)\\$/) {
      $former_line = $1;
      return 1;
    } else {
      $former_line = '';
    }
    $Line = $line;

    {
      @Words = ();
      my $s = $line;
      $s =~ s/^\s*//;
      $s =~ s/\s+$//;
      return 1 unless $s;
      $s = " $s";
      $s =~ s/\s+#.*$// if $is_pic;
      return 1 unless $s;
      $line = $s;
      $line =~ s/^\s*|\s*$//g;
      my $bool = 1;
      while ($bool) {
        $s =~ /^([^"]*)\s("[^"]*"?\S*)(.*)$/;
        if (defined $1) {
          my $s1 = $1;
          my $s2 = $2;
          $s = $3;
          $s1 =~ s/^\s*|\s*$//g;
          push @Words, split(/\s+/, $s1) if $s1;
          push @Words, $s2;
        }
        if ($s !~ /\s"/) {
          $s =~ s/^\s*|\s*$//g;
          push @Words, split(/\s+/, $s) if $s;
          $bool = 0;
        }
      }

#      @Words = split(/\s+/, $s);
      return 1 unless @Words;
#      foreach my $i (0..$#Words) {
#       if ($Words[$i] =~ /^\s*#/) {
#         $#Words = $i - 1;
#         last;
#       }
#      }
#      return 1 unless @Words;
    }

    if ($line =~ /^([\.']\s*PS\s*)|([\.']\s*PS\s.+)$/) {
      # .PS
      unless ($is_pic) {
        $is_pic = 'running';
        print "$line\n";
      }
      return 1;
    }
### main_line()
    if ( $line =~ /^([\.']\s*PE\s*)|([\.']\s*PE\s.+)$/ ) {
      # .PE
      $is_chem = '';
      if ($is_pic) {
        $is_pic = '';
        print "$line\n";
      }
      return 1;
    }
    if ($line =~ /^[\.']\s*cstart\s*$/) {
      # line: '.cstart'
      if ($is_chem) {
        &error("additional '.cstart'; chem is already active.");
        return 1;
      }
      unless ($is_pic) {
        &print_ps();
        $is_pic = 'by chem';
      }
      $is_chem = '.cstart';
      &init();
      return 1;
    }
### main_line()
    if ($line =~ /^\s*begin\s+chem\s*$/) {
      # line: 'begin chem'
      if ($is_pic) {
        if ($is_chem) {
          &error("additional 'begin chem'; chem is already active.");
          return 1;
        }
        $is_chem = 'begin chem';
        &init();
      } else {
        print "$line\n";
      }
      return 1;
    }
    if ($line =~ /^[\.']\s*cend\s*/) {
      # line '.cend'
      if ($is_chem) {
        &error("you end chem with '.cend', but started it with 'begin chem'.")
          if $is_chem eq 'begin chem';
        if ($is_pic eq 'by chem') {
          &print_pe();
          $is_pic = '';
        }
        $is_chem = '';
      } else {
        print "$line\n";
      }
      return 1;
    }
    if ($line =~ /^\s*end\s*$/) {
      # line: 'end'
      if ($is_chem) {
        &error("you end chem with 'end', but started it with '.cstart'.")
          if $is_chem eq '.cstart';
        if ($is_pic eq 'by chem') {
          &print_pe();
          $is_pic = '';
        }
        $is_chem = '';
      } else {
        print "$line\n";
      }
      return 1;
    }

### main_line()
    if (! $is_chem) {
      print "$line\n";
      return 1;
    }
    if ($line =~ /^[.']/) {
      # groff request line
      print "$line\n";
      return 1;
    }

    if ($Words[0] eq 'pic') {
      # pic pass-through
      return 1 if $#Words == 0;
      my $s = $line;
      $s =~ /^\s*pic\s*(.*)$/;
      $s = $1;
      print "$s\n" if $s;
      $Last_Type = $Types{'OTHER'};
      $Define{ $Words[2] } = 1 if $#Words >= 2 && $Words[1] eq 'define';
      return 1;
    }

    if ($Words[0] eq 'textht') {
      if ($#Words == 0) {
        &error("'textht' needs a single argument.");
        return 0;
      }
      &error("only the last argument is taken for 'textht', " .
             "all others are ignored.")
        unless $#Words <= 1 or ($#Words == 2 && $Words[1] =~ /^=/);
      $Params{'textht'} = $Words[$#Words];
      return 1;
    }
### main_line()
    if ($Words[0] eq 'cwid') {  # character width
      if ($#Words == 0) {
        &error("'cwid' needs a single argument.");
        return 0;
      }
      &error("only the last argument is taken for 'cwid', " .
             "all others are ignored.")
        unless $#Words <= 1 or ($#Words == 2 && $Words[1] =~ /^=/);
      $Params{'cwid'} = $Words[$#Words];
      return 1;
    }
    if ($Words[0] eq 'db') {    # bond length
      if ($#Words == 0) {
        &error("'db' needs a single argument.");
        return 0;
      }
      &error("only the last argument is taken for 'db', " .
             "all others are ignored.")
        unless $#Words <= 1 or ($#Words == 2 && $Words[1] =~ /^=/);
      $Params{'db'} = $Words[$#Words];
      return 1;
    }
    if ($Words[0] eq 'size') {  # size for all parts of the whole diagram
      my $size;
      if ($#Words == 0) {
        &error("'size' needs a single argument.");
        return 0;
      }
      &error("only the last argument is taken for 'size', " .
             "all others are ignored.")
        unless $#Words <= 1 or ($#Words == 2 && $Words[1] =~ /^=/);
      if ($Words[$#Words] <= 4) {
        $size = $Words[$#Words];
      } else {
        $size = $Words[$#Words] / 10;
      }
      &setparams($size);
      return 1;
    }

### main_line()
    print "\n#", $Line, "\n";                 # debugging, etc.
    $Last_Name = '';
#    $Last_Type = $Types{'OTHER'};
#    $Last_Type = '';

    if ($Words[0] =~ /^[A-Z].*:$/) {
      # label;  falls thru after shifting left
      my $w = $Words[0];
      $Last_Name = $w;
      $Last_Name =~ s/:$//;
      print "$w";
      shift @Words;
      if (@Words) {
        print " ";
        $line =~ s/^\s*$w\s*//;
      } else {
        print "\n";
        return 1;
      }
    }

    if ($Words[0] eq 'define') {
      print "$line\n";
      $Define{ $Words[1] } = 1 if $#Words >= 1;
      $Last_Type = $Types{'OTHER'};
      return 1;
    }
    if ($Words[0] =~ /^[\[\]{}]/) {
      print "$line\n";
      $Last_Type = $Types{'OTHER'};
      return 1;
    }

    if ($Words[0] =~ /^"/) {
      print 'Last: ', $line, "\n";
      $Last_Type = $Types{'OTHER'};
      return 1;
    }

    if ($Words[0] =~ /bond/) {
      &bond($Words[0]);
      return 1;
    }

    if ($#Words >= 1) {
      if ($Words[0] =~ 
/^(arrow|block|dotted|double|triple|front|back|rback|rfront|racemic|wavy)$/ &&
          $Words[1] eq 'bond') {
        my $w = shift @Words;
        $Words[0] = $w . $Words[0];
        &bond($Words[0]);
        return 1;
      }
      if ($Words[0] eq 'aromatic') {
        my $temp = $Words[0];
        $Words[0] = $Words[1] ? $Words[1] : '';
        $Words[1] = $temp;
      }
    }

    if ($Words[0] =~ /ring|benz/) {
      &ring($Words[0]);
      return 1;
    }
    if ($Words[0] eq 'methyl') {
      # left here as an example
      $Words[0] = 'CH3';
    }
### main_line()
    if ($Words[0] =~ /^[A-Z]/) {
      &molecule();
      return 1;
    }
    if ($Words[0] eq 'left') {
      my %left;                 # not used
      $left{++$stack} = &fields(1, $#Words);
      printf (("Last: [\n"));
      return 1;
    }
    if ($Words[0] eq 'right') {
      &bracket();
      $stack--;
      return 1;
    }
    if ($Words[0] eq 'label') { # prints the vertex numbers in a ring
      if ( exists $Labtype{$Words[1]} and
           $Labtype{$Words[1]} =~ /^$Types{'RING'}/ ) {
        my $v = substr($Labtype{$Words[1]}, 1, 1);
        $Words[1] = '' unless $Words[1];
        foreach my $i ( 1..$v ) {
          printf "\"\\s-3%d\\s0\" at 0.%d<%s.C,%s.V%d>\n", $i, $v + 2,
            $Words[1], $Words[1], $i;
        }
      } else {
        &error("$Words[1] is not a ring.");
      }
      return 1;
    }

    if ( exists $Define{ $Words[0] } ) {
      print $line, "\n";
      $Last_Type = $Types{'OTHER'};
      return 1;
    }
    return 1 unless $line;
#    print STDERR "# $Line\n";
#    &error('This is not a chem command.  To include a command for pic, ' .
#           "add 'pic' as the first word to the command.");
    print $line, "\n";
    $Last_Type = $Types{'OTHER'};
    1;
  } # main_line()

}

########################################################################
# functions
########################################################################

##########
# atom(<string>)
#
sub atom {
  # convert CH3 to atom(...)
  my ($s) = @_;
  my ($i, $n, $nsub, $cloc, $nsubc, @s);
  if ($s eq "\"\"") {
    return $s;
  }
  $n = length($s);
  $nsub = $nsubc = 0;
  $cloc = index($s, 'C');
  if (! defined($cloc) || $cloc < 0) {
    $cloc = 0;
  }
  @s = split('', $s);
  $i = 0;
  foreach (@s) {
    unless (/[A-Z]/) {
      $nsub++;
      $nsubc++ if $i < $cloc;
      $i++;
    }
  }
  $s =~ s/([0-9]+\.[0-9]+)|([0-9]+)/\\s-3\\d$&\\u\\s+3/g;
  if ($s =~ /([^0-9]\.)|(\.[^0-9])/) { # centered dot
    $s =~ s/\./\\v#-.3m#.\\v#.3m#/g;
  }
  sprintf( "atom(\"%s\", %g, %g, %g, %g, %g, %g)",
           $s, ($n - $nsub / 2) * $Params{'cwid'}, $Params{'textht'},
           ($cloc - $nsubc / 2 + 0.5) * $Params{'cwid'}, $Params{'crh'},
           $Params{'crw'}, $Params{'dav'}
         );
} # atom()


##########
# bond(<type>)
#
sub bond {
  my ($type) = @_;
  my ($i, $moiety, $from, $leng);
  $moiety = '';
  for ($i = 1; $i <= $#Words; $i++) {
    if ($Words[$i] eq ';') {
      &error("a colon ';' must be followed by a space and a single word.")
       if $i != $#Words - 1;
      $moiety = $Words[$i + 1] if $#Words > $i;
      $#Words = $i - 1;
      last;
    }
  }
  $leng = $Params{'db'};        # bond length
  $from = '';
  for ($Word_Count = 1; $Word_Count <= $#Words; ) {
    if ($Words[$Word_Count] =~
        /(\+|-)?\d+|up|down|right|left|ne|se|nw|sw/) {
      $Dir = &cvtdir($Dir);
    } elsif ($Words[$Word_Count] =~ /^leng/) {
      $leng = $Words[$Word_Count + 1] if $#Words > $Word_Count;
      $Word_Count += 2;
    } elsif ($Words[$Word_Count] eq 'to') {
      $leng = 0;
      $from = &fields($Word_Count, $#Words);
      last;
    } elsif ($Words[$Word_Count] eq 'from') {
      $from = &dofrom();
      last;
    } elsif ($Words[$Word_Count] =~ /^#/) {
      $Word_Count = $#Words + 1;
      last;
    } else {
      $from = &fields($Word_Count, $#Words);
      last;
    }
  }
### bond()
  if ($from =~ /( to )|^to/) {  # said "from ... to ...", so zap length
    $leng = 0;
  } elsif (! $from) {           # no from given at all
    $from = 'from Last.' . &leave($Last_Type, $Dir) . ' ' .
      &fields($Word_Count, $#Words);
  }
  printf "Last: %s(%g, %g, %s)\n", $type, $leng, $Dir, $from;
  $Last_Type = $Types{'BOND'};
  $Labtype{$Last_Name} = $Last_Type if $Last_Name;
  if ($moiety) {
    @Words = ($moiety);
    &molecule();
  }
} # bond()


##########
# bracket()
#
sub bracket {
  my $t;
  printf (("]\n"));
  if ($Words[1] && $Words[1] eq ')') {
    $t = 'spline';
  } else {
    $t = 'line';
  }
  printf "%s from last [].sw+(%g,0) to last [].sw to last [].nw to last " .
    "[].nw+(%g,0)\n", $t, $Params{'dbrack'}, $Params{'dbrack'};
  printf "%s from last [].se-(%g,0) to last [].se to last [].ne to last " .
    "[].ne-(%g,0)\n", $t, $Params{'dbrack'}, $Params{'dbrack'};
  if ($Words[2] && $Words[2] eq 'sub') {
    printf "\" %s\" ljust at last [].se\n", &fields(3, $#Words);
  }
} # bracket()


##########
# corner(<dir>)
#
# Return the corner name next to the given angle.
#
sub corner {
  my ($d) = @_;
  $Dc{ (45 * int(($d + 22.5) / 45)) % 360 };
} # corner()


##########
# cvtdir(<dir>)
#
# Maps "[pointing] somewhere" to degrees.
#
sub cvtdir {
  my ($d) = @_;
  if ($Words[$Word_Count] eq 'pointing') {
    $Word_Count++;
  }
  if ($Words[$Word_Count] =~ /^[+\\-]?\d+/) {
    return ( $Words[$Word_Count++] % 360 );
  } elsif ($Words[$Word_Count] =~ /left|right|up|down|ne|nw|se|sw/) {
    return ( $Dc{$Words[$Word_Count++]} % 360 );
  } else {
    $Word_Count++;
    return $d;
  }
} # cvtdir()


##########
# dblring(<v>)
#
sub dblring {
  my ($v) = @_;
  my ($d, $v1, $v2);
  # should canonicalize to i,i+1 mod v
  $d = $Words[$Word_Count];
  for ($Word_Count++; $Word_Count <= $#Words &&
       $Words[$Word_Count] =~ /^[1-9]/; $Word_Count++) {
    $v1 = substr($Words[$Word_Count], 0, 1);
    $v2 = substr($Words[$Word_Count], 2, 1);
    if ($v2 == $v1 + 1 || $v1 == $v && $v2 == 1) { # e.g., 2,3 or 5,1
      $Dbl{$v1} = $d;
    } elsif ($v1 == $v2 + 1 || $v2 == $v && $v1 == 1) { # e.g., 3,2 or 1,5
      $Dbl{$v2} = $d;
    } else {
      &error(sprintf("weird %s bond in\n\t%s", $d, $_));
    }
  }
} # dblring()


##########
# dofrom()
#
sub dofrom {
  my $n;
  $Word_Count++;                        # skip "from"
  $n = $Words[$Word_Count];
  if (defined $Labtype{$n}) {   # "from Thing" => "from Thing.V.s"
    return 'from ' . $n . '.' . &leave($Labtype{$n}, $Dir);
  }
  if ($n =~ /^\.[A-Z]/) {       # "from .V" => "from Last.V.s"
    return 'from Last' . $n . '.' . &corner($Dir);
  }
  if ($n =~ /^[A-Z][^.]*\.[A-Z][^.]*$/) { # "from X.V" => "from X.V.s"
    return 'from ' . $n . '.' . &corner($Dir);
  }
  &fields($Word_Count - 1, $#Words);
} # dofrom()


##########
# error(<string>)
#
sub error {
  my ($s) = @_;
  printf STDERR "chem: error in %s on line %d: %s\n",
    $File_Name, $Line_No, $s;
} # error()


##########
# fields(<n1>, <n2>)
#
sub fields {
  my ($n1, $n2) = @_;
  if ($n1 > $n2) {
    return '';
  }
  my $s = '';
  foreach my $i ($n1..$n2) {
    if ($Words[$i] =~ /^#/) {
      last;
    }
    $s = $s . $Words[$i] . ' ';
  }
  $s;
} # fields()


##########
# init()
#
sub init {
  if ($First_Time) {
    printf "copy \"%s\"\n", $File_chem_pic;
    printf "\ttextht = %g; textwid = .1; cwid = %g\n",
      $Params{'textht'}, $Params{'cwid'};
    printf "\tlineht = %g; linewid = %g\n",
      $Params{'lineht'}, $Params{'linewid'};
    $First_Time = 0;
  }
  printf "Last: 0,0\n";
  $Last_Type = $Types{'OTHER'};
  $Dir = 90;
} # init()


##########
# leave(<last>, <d>)
#
sub leave {
  my ($last, $d) = @_;
  my ($c, $c1);
  # return vertex of $last in direction $d
  if ( $last eq $Types{'BOND'} ) {
    return 'end';
  }
  $d %= 360;
  if ( $last =~ /^$Types{'RING'}/ ) {
    return &ringleave($last, $d);
  }
  if ( $last eq $Types{'MOL'} ) {
    if ($d == 0 || $d == 180) {
      $c = 'C';
    } elsif ($d > 0 && $d < 180) {
      $c = 'R';
    } else {
      $c = 'L';
    }
    if (defined $Dc{$d}) {
      $c1 = $Dc{$d};
    } else {
      $c1 = &corner($d);
    }
    return sprintf('%s.%s', $c, $c1);
  }
  if ( $last eq $Types{'OTHER'} ) {
    return &corner($d);
  }
  'c';
} # leave()


##########
# makering(<type>, <pt>, <v>)
#
sub makering {
  my ($type, $pt, $v) = @_;
  my ($i, $j, $a, $r, $rat, $fix, $c1, $c2);
  if ($type =~ /flat/) {
    $v = 6;
    # vertices
    ;
  }
  $r = $Params{'ringside'} / (2 * sin(pi / $v));
  printf "\tC: 0,0\n";
  for ($i = 0; $i <= $v + 1; $i++) {
    $a = (($i - 1) / $v * 360 + $pt) / 57.29578; # 57. is $deg
    printf "\tV%d: (%g,%g)\n", $i, $r * sin($a), $r * cos($a);
  }
  if ($type =~ /flat/) {
    printf "\tV4: V5; V5: V6\n";
    $v = 5;
  }
  # sides
  if ($Nput > 0) {
    # hetero ...
    for ($i = 1; $i <= $v; $i++) {
      $c1 = $c2 = 0;
      if ($Put{$i} ne '') {
        printf "\tV%d: ellipse invis ht %g wid %g at V%d\n",
          $i, $Params{'crh'}, $Params{'crw'}, $i;
        printf "\t%s at V%d\n", $Put{$i}, $i;
        $c1 = $Params{'cr'};
      }
      $j = $i + 1;
      if ($j > $v) {
        $j = 1;
      }
### makering()
      if ($Put{$j} ne '') {
        $c2 = $Params{'cr'};
      }
      printf "\tline from V%d to V%d chop %g chop %g\n", $i, $j, $c1, $c2;
      if ($Dbl{$i} ne '') {
        # should check i<j
        if ($type =~ /flat/ && $i == 3) {
          $rat = 0.75;
          $fix = 5;
        } else {
          $rat = 0.85;
          $fix = 1.5;
        }
        if ($Put{$i} eq '') {
          $c1 = 0;
        } else {
          $c1 = $Params{'cr'} / $fix;
        }
        if ($Put{$j} eq '') {
          $c2 = 0;
        } else {
          $c2 = $Params{'cr'} / $fix;
        }
        printf "\tline from %g<C,V%d> to %g<C,V%d> chop %g chop %g\n",
          $rat, $i, $rat, $j, $c1, $c2;
        if ($Dbl{$i} eq 'triple') {
          printf "\tline from %g<C,V%d> to %g<C,V%d> chop %g chop %g\n",
            2 - $rat, $i, 2 - $rat, $j, $c1, $c2;
        }
      }
    }
### makering()
  } else {
    # regular
    for ($i = 1; $i <= $v; $i++) {
      $j = $i + 1;
      if ($j > $v) {
        $j = 1;
      }
      printf "\tline from V%d to V%d\n", $i, $j;
      if ($Dbl{$i} ne '') {
        # should check i<j
        if ($type =~ /flat/ && $i == 3) {
          $rat = 0.75;
        } else {
          $rat = 0.85;
        }
        printf "\tline from %g<C,V%d> to %g<C,V%d>\n",
          $rat, $i, $rat, $j;
        if ($Dbl{$i} eq 'triple') {
          printf "\tline from %g<C,V%d> to %g<C,V%d>\n",
            2 - $rat, $i, 2 - $rat, $j;
        }
      }
    }
  }
### makering()
  # punt on triple temporarily
  # circle
  if ($type =~ /benz/ || $Aromatic > 0) {
    if ($type =~ /flat/) {
      $r *= .4;
    } else {
      $r *= .5;
    }
    printf "\tcircle rad %g at 0,0\n", $r;
  }
} # makering()


##########
# molecule()
#
sub molecule {
  my ($n, $type);
  if ($#Words >= 0) {
    $n = $Words[0];
    if ($n eq 'BP') {
      $Words[0] = "\"\" ht 0 wid 0";
      $type = $Types{'OTHER'};
    } else {
      $Words[0] = &atom($n);
      $type = $Types{'MOL'};
    }
  }
  $n =~ s/[^A-Za-z0-9]//g;      # for stuff like C(OH3): zap non-alnum
  if ($#Words < 1) {
    printf "Last: %s: %s with .%s at Last.%s\n",
      $n, join(' ', @Words), &leave($type, $Dir + 180),
        &leave($Last_Type, $Dir);
### molecule()
  } else {
    if (! $Words[1]) {
      printf "Last: %s: %s with .%s at Last.%s\n",
        $n, join(' ', @Words), &leave($type, $Dir + 180),
          &leave($Last_Type, $Dir);
    } elsif ($#Words >= 1 and $Words[1] eq 'below') {
      $Words[2] = '' if ! $Words[2];
      printf "Last: %s: %s with .n at %s.s\n", $n, $Words[0], $Words[2];
    } elsif ($#Words >= 1 and $Words[1] eq 'above') {
      $Words[2] = '' if ! $Words[2];
      printf "Last: %s: %s with .s at %s.n\n", $n, $Words[0], $Words[2];
    } elsif ($#Words >= 2 and $Words[1] eq 'left' && $Words[2] eq 'of') {
      $Words[3] = '' if ! $Words[3];
      printf "Last: %s: %s with .e at %s.w+(%g,0)\n",
        $n, $Words[0], $Words[3], $Params{'dew'};
    } elsif ($#Words >= 2 and $Words[1] eq 'right' && $Words[2] eq 'of') {
      $Words[3] = '' if ! $Words[3];
      printf "Last: %s: %s with .w at %s.e-(%g,0)\n",
        $n, $Words[0], $Words[3], $Params{'dew'};
    } else {
      printf "Last: %s: %s\n", $n, join(' ', @Words);
    }
  }

  $Last_Type = $type;
  if ($Last_Name) {
    #    $Last_Type = '';
    $Labtype{$Last_Name} = $Last_Type;
  }
 $Labtype{$n} = $Last_Type;
} # molecule()


##########
# print_hash(<hash_or_ref>)
#
# print the elements of a hash or hash reference
#
sub print_hash {
  my $hr;
  my $n = scalar @_;
  if ($n == 0) {
    print STDERR "empty hash\n;";
    return 1;
  } elsif ($n == 1) {
    if (ref($_[0]) eq 'HASH') {
      $hr = $_[0];
    } else {
      warn 'print_hash(): the argument is not a hash or hash reference;';
      return 0;
    }
  } else {
    if ($n % 2) {
      warn 'print_hash(): the arguments are not a hash;';
      return 0;
    } else {
      my %h = @_;
      $hr = \%h;
    }
  }

### print_hash()
  unless (%$hr) {
    print STDERR "empty hash\n";
    return 1;
  }
  print STDERR "hash (ignore the ^ characters):\n";
  for my $k (sort keys %$hr) {
    my $hk = $hr->{$k};
    print STDERR "  $k => ";
    if (defined $hk) {
      print STDERR "^$hk^";
    } else {
      print STDERR "undef";
    }
    print STDERR "\n";
  }

  1;
}                               # print_hash()


##########
# print_pe()
#
sub print_pe {
  print ".PE\n";
} # print_pe()


##########
# print_ps()
#
sub print_ps {
  print ".PS\n";
} # print_ps()

##########
# putring(<v>)
#
sub putring {
  # collect "put Mol at n"
  my ($v) = @_;
  my ($m, $mol, $n);
  $Word_Count++;
  $mol = $Words[$Word_Count++];
  if ($Words[$Word_Count] eq 'at') {
    $Word_Count++;
  }
  $n = $Words[$Word_Count];
  if ($n !~ /^\d+$/) {
    $n =~ s/(\d)+$/$1/;
    $n = 0 if $n !~ /^\d+$/;
    error('use single digit as argument for "put at"');
  }
  if ($n >= 1 && $n <= $v) {
    $m = $mol;
    $m =~ s/[^A-Za-z0-9]//g;
    $Put{$n} = $m . ':' . &atom($mol);
  } elsif ($n == 0) {
    error('argument of "put at" must be a single digit');
  } else {
    error('argument of "put at" is too large');
  }
  $Word_Count++;
} # putring()


##########
# ring(<type>)
#
sub ring {
  my ($type) = @_;
  my ($typeint, $pt, $verts, $i, $other, $fused, $withat);
  $pt = 0;                      # points up by default
  if ($type =~ /([1-8])$/) {
    $verts = $1;
  } elsif ($type =~ /flat/) {
    $verts = 5;
  } else {
    $verts = 6;
  }
  $fused = $other = '';
  for ($i = 1; $i <= $verts; $i++) {
    $Put{$i} = $Dbl{$i} = '';
  }
  $Nput = $Aromatic = $withat = 0;
  for ($Word_Count = 1; $Word_Count <= $#Words; ) {
    if ($Words[$Word_Count] eq 'pointing') {
      $pt = &cvtdir(0);
    } elsif ($Words[$Word_Count] eq 'double' ||
             $Words[$Word_Count] eq 'triple') {
      &dblring($verts);
    } elsif ($Words[$Word_Count] =~ /arom/) {
      $Aromatic++;
      $Word_Count++;            # handled later
### ring()
    } elsif ($Words[$Word_Count] eq 'put') {
      &putring($verts);
      $Nput++;
    } elsif ($Words[$Word_Count] =~ /^#/) {
      $Word_Count = $#Words + 1;
      last;
    } else {
      if ($Words[$Word_Count] eq 'with' || $Words[$Word_Count] eq 'at') {
        $withat = 1;
      }
      $other = $other . ' ' . $Words[$Word_Count];
      $Word_Count++;
    }
  }
  $typeint = $Types{'RING'} . $verts . $pt; # RING | verts | dir
  if ($withat == 0) {
    # join a ring to something
    if ( $Last_Type =~ /^$Types{'RING'}/ ) {
      # ring to ring
      if (substr($typeint, 2) eq substr($Last_Type, 2)) {
        # fails if not 6-sided
        $fused = 'with .V6 at Last.V2';
      }
    }
    # if all else fails
    $fused = sprintf('with .%s at Last.%s',
          &leave($typeint, $Dir + 180), &leave($Last_Type, $Dir));
  }
  printf "Last: [\n";
  &makering($type, $pt, $verts);
  printf "] %s %s\n", $fused, $other;
  $Last_Type = $typeint;
  $Labtype{$Last_Name} = $Last_Type if $Last_Name;
} # ring()


##########
# ringleave(<last>, <d>)
#
sub ringleave {
  my ($last, $d) = @_;
  my ($rd, $verts);
  # return vertex of ring in direction d
  $verts = substr($last, 1, 1);
  $rd = substr($last, 2);
  sprintf('V%d.%s', int( (($d - $rd) % 360) / (360 / $verts)) + 1,
          &corner($d));
} # ringleave()


##########
# setparams(<scale>)
#
sub setparams {
  my ($scale) = @_;
  $Params{'lineht'} = $scale * 0.2;
  $Params{'linewid'} = $scale * 0.2;
  $Params{'textht'} = $scale * 0.16;
  $Params{'db'} = $scale * 0.2; # bond length
  $Params{'cwid'} = $scale * 0.12;      # character width
  $Params{'cr'} = $scale * 0.08; # rad of invis circles at ring vertices
  $Params{'crh'} = $scale * 0.16; # ht of invis ellipse at ring vertices
  $Params{'crw'} = $scale * 0.12; # wid 
  $Params{'dav'} = $scale * 0.015; # vertical shift up for atoms in atom macro
  $Params{'dew'} = $scale * 0.02; # east-west shift for left of/right of
  $Params{'ringside'} = $scale * 0.3; # side of all rings
  $Params{'dbrack'} = $scale * 0.1; # length of bottom of bracket
} # setparams()


sub usage {
  print <<EOF;
usage: $chem [file ...]
usage: $chem { -h | --help | -v | --version }

$chem is a groff preprocessor for producing chemical structure
diagrams.  It produces input for the $makevar{'G'}pic preprocessor.  If
no file operands are given, or if file is "-", the standard input stream
is read.

Options:
 -h, --help     Display this message and exit.
 -v, --version  Display version information and exit.
EOF
}


sub version {
  print <<EOF;
$chem (groff $groff_version) $chem_version
$copyright
License GPLv2: GNU GPL version 2
<https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
EOF
}

1;

# Local Variables:
# fill-column: 72
# mode: CPerl
# End:
# vim: set cindent noexpandtab shiftwidth=2 softtabstop=2 textwidth=72:
FILES           = Chem.groff
TARGET          = ./build/Chem.pdf 
PROCESSOR       = groff 
OPTIONS         = -Tpdf -U -mm 

.PHONY  : all preview 

all:
        cat ${FILES} | ./chem | sed -e 's,^copy.*$$,copy "chem.pic",' | pic | 
${PROCESSOR} ${OPTIONS} > ${TARGET}
        killall -HUP mupdf

preview:
        mupdf ${TARGET} &

.DS CB
.cstart
Y1: Y
wavy bond 
X
wavy bond up from Y1
X
wavy bond down from Y1
X
bond down invis
"wavy"
wavy bond left from Y1
X
wavy bond 45 from Y1
X
wavy bond 135 from Y1
X
wavy bond -45 from Y1
X
wavy bond -135 from Y1
X
BP at Y1
bond length 1 right invis
Y2: Y
racemic bond 
X
racemic bond up from Y2
X
racemic bond down from Y2
X
bond down invis
"racemic"
racemic bond left from Y2
X
racemic bond 45 from Y2
X
racemic bond 135 from Y2
X
racemic bond -45 from Y2
X
racemic bond -135 from Y2
X
BP at Y2
bond length 1 right invis
Y3: Y
back bond 
X
back bond up from Y3
X
back bond down from Y3
X
bond down invis
"back"
back bond left from Y3
X
back bond 45 from Y3
X
back bond 135 from Y3
X
back bond -45 from Y3
X
back bond -135 from Y3
X
BP at Y3
bond length 1 right invis
Y3: Y
rback bond 
X
rback bond up from Y3
X
rback bond down from Y3
X
bond down invis
"rback"
rback bond left from Y3
X
rback bond 45 from Y3
X
rback bond 135 from Y3
X
rback bond -45 from Y3
X
rback bond -135 from Y3
X
BP at Y3
bond length 1 right invis
Y3: Y
rfront bond 
X
rfront bond up from Y3
X
rfront bond down from Y3
X
bond down invis
"rfront"
rfront bond left from Y3
X
rfront bond 45 from Y3
X
rfront bond 135 from Y3
X
rfront bond -45 from Y3
X
rfront bond -135 from Y3
X
BP at Y1
bond length 1.5 down invis
Y6: Y
block bond right
X
block bond up from Y6
X
block bond down from Y6
X
bond down invis
"block"
block bond left from Y6
X
block bond 45 from Y6
X
block bond 135 from Y6
X
block bond -45 from Y6
X
block bond -135 from Y6
X
.cend
.DE
.HU "Examples"
.DS CB
.cstart
Br
bond right
M1: BP
back bond 30
F
bond 135 from M1
Cl
BP at M1
bond length 1 right invis
Br
bond right
M2: BP
back bond 30
F
front bond right from M2
H
bond 150 from M2
Cl
BP at M2
bond length 1 right invis
R1: ring5 pointing down double 1,2
front bond 30 from R1.V4
Cl
back bond 100 from R1.V5
Br
BP at R1.c
bond length 1 right invis
R2: ring5 pointing down double 1,2
block bond 30 from R2.V4
Cl
dotted bond 100 from R2.V5
Br
.cend
.DE


.ig END
https://www.reddit.com/r/learnmath/comments/1k2kar/rotating_a_sin_graph_45_degrees_around_the_origin/?rdt=37853
.END
# macros for chem
        
# Copyright (C) 2006-2020 Free Software Foundation, Inc.
# Written by Brian Kernighan <http://cm.bell-labs.com/cm/cs/who/bwk>,
# modified by Bernd Warken <groff-bernd.warken...@web.de>.

# This file is part of 'chem', which 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 (GPL) version 2 as
# published by the Free Software Foundation.

# '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 for more details.

# The license text for GPL2 is available in the internet at
# <http://www.gnu.org/licenses/gpl-2.0.html>.

########################################################################

pi = 3.141592654
deg = 57.29578
# cr = 0.08             # radius of invis circle at ring vertices (see cr[vh])
# crh = 0.16; crw = 0.12 # ht & wid of invis ellipse around atoms at ring 
vertices
# dav = 0.015           # vertical shift up for atoms in atom macro

# atom(text, wid, ht, carbon position, crh, crw, dav)
define atom { [
        T: $1 wid $2 ht $3-2*$7
        C: ellipse invis ht $5 wid $6 at T.w + ($4,$7)
        L: ellipse invis ht $5 wid $6 at T.w + (cwid/2,$7)
        R: ellipse invis ht $5 wid $6 at T.e + (-cwid/2,$7)
] }

# bond(length, angle in degrees, whatever)
define bond {
        line $3 by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
}

# fancy bonds:  r, theta, from/at
define dottedbond {
        line dashed 0.02 $3 by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        }
define arrowbond {
        line -> $3 by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        }
define doublebond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        ny = dx * .02 / norm
        nx = -dy * .02 / norm
        line from V1 + (nx,ny) to V2 + (nx,ny)
        line from V1 - (nx,ny) to V2 - (nx,ny)
        move to V2
}
define triplebond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        ny = dx * .025 / norm
        nx = -dy * .025 / norm
        line from V1 + (nx,ny) to V2 + (nx,ny)
        line from V1 - (nx,ny) to V2 - (nx,ny)
        line from V1 to V2
        move to V2
}
define backbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        n = norm / .025
        ny = dx * .02 / norm
        nx = -dy * .02 / norm
        for i = 1 to n-1 do {
                XZ: i/n <V1,V2>
                line from XZ + (nx*i/n,ny*i/n) to XZ - (nx*i/n,ny*i/n)
        }
        move to V2
}
define rbackbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        n = norm / .025
        ny = dx * .02 / norm
        nx = -dy * .02 / norm
        for i = 1 to n-1 do {
                XZ: i/n <V1,V2>
                line from XZ + (nx*(n-i)/n,ny*(n-i)/n) \
                to XZ - (nx*(n-i)/n,ny*(n-i)/n)
        }
        move to V2
}
define racemicbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        n = norm / .025
        ny = dx * .02 / norm
        nx = -dy * .02 / norm
        for i = 1 to n-1 do {
                XZ: i/n <V1,V2>
                line from XZ + (nx,ny) to XZ - (nx,ny)
        }
        move to V2
}
define blockbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        n = norm / .001
        ny = dx * .015 / norm
        nx = -dy * .015 / norm
        for i = 10 to n-10 do {
                XZ: i/n <V1,V2>
                line from XZ + (nx,ny) to XZ - (nx,ny)
        }
        move to V2
}
define wavybond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        norm = sqrt(dx*dx + dy*dy)
        for i = 0 to $1-0.004 by 0.001 do {
                x1 = i
                y1 = 0.025*sin(6*pi*x1/($1))
                yr = x1*cos(($2)/deg)-y1*sin(($2)/deg)
                xr = x1*sin(($2)/deg)+y1*cos(($2)/deg)
                line 0 from V1 + (xr,yr)
        }
        move to V2
}
define frontbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        ah = arrowht; aw = arrowwid; ahead = arrowhead
        arrowht = sqrt(dx*dx + dy*dy)
        arrowwid = 0.05
        arrowhead = 7
        line <- from V1 to V2
        arrowht = ah; arrowwid = aw; arrowhead = ahead
}
define rfrontbond {
        line $3 invis by ($1) * sin(($2)/deg), ($1) * cos(($2)/deg)
        V1: last line.start; V2: last line.end; dx = V2.x-V1.x; dy = V2.y-V1.y
        ah = arrowht; aw = arrowwid; ahead = arrowhead
        arrowht = sqrt(dx*dx + dy*dy)
        arrowwid = 0.05
        arrowhead = 7
        line -> from V1 to V2
        arrowht = ah; arrowwid = aw; arrowhead = ahead
}
# Local Variables:
# mode: Nroff
# End:

Reply via email to