The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Copyright 2012, 2013 Kevin Ryde

# This file is part of Wx-Perl-PodBrowser.
#
# Wx-Perl-PodBrowser 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 3, or (at your option) any later
# version.
#
# Wx-Perl-PodBrowser 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.
#
# You should have received a copy of the GNU General Public License along
# with Wx-Perl-PodBrowser.  If not, see <http://www.gnu.org/licenses/>.





# require Wx::Perl::PodRichText::PodParser;
# my $parser = Wx::Perl::PodRichText::PodParser->new
#   (richtext => $self);
# $parser->parse_from_filehandle ($fh);
# if ($options{'close_fh'}) {
#   close $fh
#     or $self->WriteText ("\n\n\nError closing filehandle: $!");
# }





package Wx::Perl::PodRichText::PodParser;
use strict;
use warnings;
use Carp;
use Pod::Escapes;
use Pod::ParseLink;
use base 'Pod::Parser';
our $VERSION = 14;

# uncomment this to run the ### lines
#use Smart::Comments;

# sub new {
#   my $class = shift;
#   ### PodRichText-Parser new() ...
#   my $self = $class->SUPER::new (@_);
#   return $self;
# }
#
# sub parse_from_string {
#   my ($self, $str) = @_;
#   open my $fh, '<', \$str
#     or die "Oops, cannot open filehandle on string";
#   $self->parse_from_filehandle ($fh);
# }

my %accept_begin = ('' => 1, # when not in any begin
                    text => 1,
                    TEXT => 1);

# begin/end of whole document
sub begin_pod {
  my $self = shift;
  $self->SUPER::begin_pod(@_);

  $self->{'in_begin'} = '';
  $self->{'in_begin_stack'} = [];
  $self->{'indent'} = 0;

  my $richtext = $self->{'richtext'};
  my $attrs = $richtext->GetBasicStyle;
  my $font = $attrs->GetFont;
  my $font_mm = $font->GetPointSize * (1/72 * 25.4);
  # 1.5 characters expressed in tenths of mm
  $self->{'indent_step'} = int($font_mm*10 * 1.5);
  ### $font_mm
  ### indent_step: $self->{'indent_step'}

  $richtext->Clear;
  $richtext->SetDefaultStyle ($richtext->GetBasicStyle);
  $richtext->BeginSuppressUndo;
  # .6 of a line, expressed in tenths of a mm
  $richtext->BeginParagraphSpacing ($font_mm*10 * .2,  # before
                                    $font_mm*10 * .4); # after
  $richtext->{'section_positions'} = {};
  $richtext->{'heading_list'} = [];
  $self->{'freezer'} = Wx::WindowUpdateLocker->new($richtext);
}
sub end_pod {
  my $self = shift;
  $self->SUPER::end_pod(@_);
  ### end_pod() ...

  delete $self->{'freezer'};
  my $richtext = $self->{'richtext'};
  $richtext->EndSuppressUndo;
  $richtext->EndParagraphSpacing;
  $richtext->SetInsertionPoint(0);
  $richtext->Thaw;
}

sub command {
  my ($self, $command, $text, $linenum, $paraobj) = @_;
  ### $command
  ### $text
  ### $paraobj

  if ($command eq 'begin') {
    push @{$self->{'in_begin_stack'}}, $self->{'in_begin'};
    if ($text =~ /(\w+)/) {
      $self->{'in_begin'} = $1;  # first word only
    } else {
      $self->{'in_begin'} = '';
    }
    return '';
  }
  if ($command eq 'end') {
    $self->{'in_begin'} = pop @{$self->{'in_begin_stack'}};
    if (! defined $self->{'in_begin'}) {
      $self->{'in_begin'} = '';  # if too many =end
    }
    ### pop to in_begin: $self->{'in_begin'}
    return '';
  }

  if (! $accept_begin{$self->{'in_begin'}}) {
    ### ignore: $self->{'in_begin'}
    return ''
  }

  my $richtext = $self->{'richtext'};
  $text =~ s/\s+$//;  # trailing whitespace

  if ($command eq 'over') {
    $self->{'indent'} += $self->{'indent_step'};
  } elsif ($command eq 'back') {
    $self->{'indent'} -= $self->{'indent_step'};

  } elsif ($command =~ /^head(\d*)/) {
    my $level = $1;
    $richtext->BeginLeftIndent($self->{'indent'}
                               + ($level > 1 ? $self->{'indent_step'} / 2 : 0));
    $richtext->BeginBold;
    my $start = $richtext->GetInsertionPoint;
    $self->write_text($text,$linenum);
    $self->set_section_position
      ($richtext->GetRange($start,$richtext->GetInsertionPoint),
       $start);
    $richtext->EndBold;
    $richtext->Newline;
    $richtext->EndLeftIndent;

  } elsif ($command =~ /^item/) {
    if ($text eq '*') {
      $self->{'bullet'} = 1;
    } elsif ($text =~ /^\d+$/) {
      $self->{'numbered_bullet'} = 1;
      $self->{'number'} = $text;
    } else {
      $richtext->BeginLeftIndent($self->{'indent'});
      my $start = $richtext->GetInsertionPoint;
      $self->write_text($text,$linenum);
      $self->set_item_position
        ($richtext->GetRange($start,$richtext->GetInsertionPoint),
         $start);
      $richtext->Newline;
      $richtext->EndLeftIndent;
    }

  } elsif ($command eq 'for') {

  } else {
    carp "Unknown command =$command";
    $richtext->WriteText("=for $command $text");
    $richtext->Newline;
  }
  return '';
}

sub textblock {
  my ($self, $text, $linenum, $paraobj) = @_;
  ### textblock() ...
  ### $text
  ### $linenum
  ### $paraobj

  if (! $accept_begin{$self->{'in_begin'}}) {
    ### ignore: $self->{'in_begin'}
    return ''
  }

  my $richtext = $self->{'richtext'};
  if (delete $self->{'bullet'}) {
    my $start = $richtext->GetInsertionPoint;
    $richtext->BeginStandardBullet("standard/circle",
                                   $self->{'indent'},
                                   $self->{'indent_step'});
    $self->write_text($text,$linenum);
    $self->set_item_position
      ($richtext->GetRange($start,$richtext->GetInsertionPoint),
       $start);
    $richtext->Newline;
    $richtext->EndStandardBullet;

  } elsif (delete $self->{'numbered_bullet'}) {
    my $start = $richtext->GetInsertionPoint;
    $richtext->BeginLeftIndent($self->{'indent'},
                               $self->{'indent_step'});
    $richtext->WriteText($self->{'number'}.'. ');
    $self->write_text($text,$linenum);
    $self->set_item_position
      ($richtext->GetRange($start,$richtext->GetInsertionPoint),
       $start);
    $richtext->Newline;
    $richtext->EndLeftIndent;

    # Numbers bigger than the indent step are drawn overlapped by the text.
    # Use a plain hanging indent para for now.
    # $richtext->BeginNumberedBullet($self->{'number'},
    #                                $self->{'indent'},
    #                                $self->{'indent_step'});
    # $self->write_text($text,$linenum);
    # $richtext->Newline;
    # $richtext->EndNumberedBullet;

  } else {
    $richtext->BeginLeftIndent($self->{'indent'} + $self->{'indent_step'});
    $self->write_text($text,$linenum);
    $richtext->Newline;
    $richtext->EndLeftIndent;
  }
  return '';
}

sub write_text {
  my ($self, $text, $linenum) = @_;
  $text =~ s/\s+$//;  # trailing newlines and other whitespace
  $self->write_ptree ($self->parse_text({}, $text, $linenum));
}

sub write_ptree {
  my ($self, $ptree) = @_;
  ### write_ptree(): $ptree

  my $richtext = $self->{'richtext'};
  foreach my $child ($ptree->children) {
    if (! ref $child) { # text with no markup
      $child =~ s/[\r\n]/ /sg;  # flow newlines
      if ($self->{'in_S'}) {
        $child =~ tr/ /\xA0/;  # non-breaking space
      }
      $richtext->WriteText($child);
      next;
    }
    my $cmd_name = $child->cmd_name;
    if ($cmd_name eq 'Z' || $cmd_name eq 'X') {

    } elsif ($cmd_name eq 'E') {
      my $e = $child->parse_tree->raw_text; # inside of E<>
      #### E: $e
      if (defined (my $char = Pod::Escapes::e2char($e))) {
        $richtext->WriteText($char);
      } else {
        $richtext->WriteText($child->raw_text); # whole E<foo>
      }

    } elsif ($cmd_name eq 'L') {
      my $raw_text = $child->parse_tree->raw_text;
      ### L: $raw_text
      if ($self->{'within_L'}) {
        $richtext->WriteText($raw_text);
      } else {
        my ($text, $inferred, $name, $section, $type)
          = Pod::ParseLink::parselink ($raw_text);
        ### $text
        ### $inferred
        ### $name
        ### $section
        ### $type
        if ($type eq 'url') {
          $richtext->BeginURL ($name);
          $richtext->BeginUnderline;
          $self->write_text($inferred);
          $richtext->EndUnderline;
          $richtext->EndURL;
        } elsif ($type eq 'pod') {
          my $url = 'pod://';
          if (defined $name) { $url .= $name; }
          if (defined $section) { $url .= "#$section"; }
          $richtext->BeginURL ($url);
          $richtext->BeginUnderline;
          $self->write_text($inferred);
          $richtext->EndUnderline;
          $richtext->EndURL;
        } else {
          $richtext->BeginUnderline;
          $self->write_text($inferred);
          $richtext->EndUnderline;
        }
      }

    } elsif ($cmd_name eq 'B') {
      local $self->{'bold'} = 1;
      $richtext->BeginBold;
      $self->write_ptree($child->parse_tree);
      $richtext->EndBold;

    } elsif ($cmd_name eq 'I' || $cmd_name eq 'F') {
      $richtext->BeginItalic;
      $self->write_ptree($child->parse_tree);
      $richtext->EndItalic;

    } elsif ($cmd_name eq 'C') {
      my $font = ($self->{'code_font'} ||= do {
        my $basic_attrs = $richtext->GetBasicStyle;
        my $basic_font = $basic_attrs->GetFont;
        ### basic font facename: $basic_font->GetFaceName
        my $font = Wx::Font->new ($basic_font);
        $font->SetFamily(Wx::wxFONTFAMILY_TELETYPE());
        my $facename = $font->GetFaceName;
        ### $facename
        $font
      });
      if ($self->{'bold'}) {
        $font->SetWeight (Wx::wxFONTWEIGHT_BOLD());
      }
      $richtext->BeginFont($font);
      $self->write_ptree($child->parse_tree);
      $richtext->EndFont;

      # $richtext->BeginTextColour(Wx::wxRED());
      # $richtext->EndTextColour;

      # my $attr = Wx::RichTextAttr->new;
      # my $facename = $font->GetFaceName;
      # ### $facename
      # $attr->SetFontFaceName($facename);
      # $attr->SetFlags (Wx::wxTEXT_ATTR_FONT_FACE());

      # my $start = $richtext->GetInsertionPoint;
      # $self->write_ptree($child->parse_tree);
      # my $end = $richtext->GetInsertionPoint;
      # $richtext->SetStyle(Wx::RichTextRange->new($start,$end),$attr);

      # $richtext->BeginStyle($attr);
      # #$richtext->BeginFont($font);
      # # $richtext->BeginTextColour(Wx::wxRED());
      # # $richtext->EndTextColour;
      # #$richtext->EndFont;
      # $richtext->EndStyle;

    } elsif ($cmd_name eq 'S') {
      local $self->{'in_S'} = 1;
      $self->write_ptree($child->parse_tree);

    } else {
      # carp "Unknown markup $cmd_name<";
      $richtext->WriteText("$cmd_name<");
      $self->write_ptree($child->parse_tree);
      $richtext->WriteText(">");
    }
  }
}

sub verbatim {
  my ($self, $text, $linenum) = @_;

  if (! $accept_begin{$self->{'in_begin'}}) {
    ### ignore: $self->{'in_begin'}
    return ''
  }

  $text =~ s/\s+$//;    # trailing whitespace
  if ($text eq '') {
    ### collapse empty verbatim ...
    return '';
  }

  $text =~ tr/\n/\x1D/; # Wx::wxRichTextLineBreakChar()

  my $richtext = $self->{'richtext'};
  my $basic_attrs = $richtext->GetBasicStyle;
  my $basic_font = $basic_attrs->GetFont;
  my $font = Wx::Font->new ($basic_font->GetPointSize,
                            Wx::wxFONTFAMILY_TELETYPE(),
                            0,
                            0);
  $richtext->BeginLeftIndent($self->{'indent'} + $self->{'indent_step'});
  $richtext->BeginRightIndent(-10000);

  $richtext->BeginFont($font);
  $richtext->WriteText($text);
  $richtext->EndFont;
  $richtext->Newline;

  # $richtext->BeginTextColour(Wx::wxRED());
  # $richtext->EndTextColour;

  $richtext->EndRightIndent;
  $richtext->EndLeftIndent;
  return '';

  # if (my @lines = split /\n/, $text) {
  #   $richtext->WriteText(shift @lines);
  #   foreach my $line (@lines) {
  #     # $richtext->LineBreak;
  #     $richtext->WriteText(chr(29)); # # Wx::wxRichTextLineBreakChar()));
  #     $richtext->WriteText($line);
  #   }
  # }
}

# set the position of $section to $pos
# if $pos is not given then default to the current insertion point
sub set_section_position {
  my ($self, $section, $pos) = @_;
  $section =~ s/\s+$//; # trailing whitespace
  push @{$self->{'heading_list'}}, $section;
  $self->{'section_positions'}->{$section} = $pos;
  $section = lc($section);
  if (! defined $self->{'section_positions'}->{$section}) {
    $self->{'section_positions'}->{$section} = $pos;
  }
}
sub set_item_position {
  my ($self, $item, $pos) = @_;
  $item =~ s/\s+$//; # trailing whitespace
  foreach my $name ($item,
                 ($item =~ /(\w+)/ ? $1 : ())) { # also just the first word
    $self->{'section_positions'}->{$name} = $pos;
    my $lname = lc($name);
    if (! defined $self->{'section_positions'}->{$lname}) {
      $self->{'section_positions'}->{$lname} = $pos;
    }
  }
}

1;
__END__