From b9d6c55a97b05022d1f279c519a9bcf8c4735185 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Fri, 26 Dec 2025 18:18:43 -0700 Subject: [PATCH 1/9] Apply Par margin to previous box in some cases in PGML. Apply the spacing between paragraphs added by the Par block in in HTML to the previous block instead of inserting an empty div with a margin. This adds the margin from the Par block to the previous Indent div, list, or list item block. Based on the code from @dpvc in #1355. --- macros/core/PGML.pl | 56 +++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index 34cba7fc0..e31f84a3b 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1266,19 +1266,21 @@ sub format { } sub string { - my $self = shift; - my $block = shift; + my ($self, $block) = @_; + my $stack = $block->{stack}; my @strings = (); my $string; - foreach my $item (@{ $block->{stack} }) { + for my $i (0 .. $#$stack) { + my $item = $stack->[$i]; + my $next_par = $i < $#$stack && $stack->[ $i + 1 ]{type} eq 'par' ? $stack->[ $i + 1 ] : undef; $self->{item} = $item; $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); for ($item->{type}) { - /indent/ && do { $string = $self->Indent($item); last }; + /indent/ && do { $string = $self->Indent($item, $next_par); last }; /align/ && do { $string = $self->Align($item); last }; /par/ && do { $string = $self->Par($item); last }; - /list/ && do { $string = $self->List($item); last }; - /bullet/ && do { $string = $self->Bullet($item); last }; + /list/ && do { $string = $self->List($item, $next_par); last }; + /bullet/ && do { $string = $self->Bullet($item, $next_par); last }; /text/ && do { $string = $self->Text($item); last }; /variable/ && do { $string = $self->Variable($item, $block); last }; /command/ && do { $string = $self->Command($item); last }; @@ -1523,17 +1525,15 @@ sub Escape { } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $item, $next_par) = @_; return $self->string($item) if $item->{indent} == 0; - my $em = 2.25 * $item->{indent}; - return - $self->nl - . '
' . "\n" - . $self->string($item) - . $self->nl - . "
\n"; + my $em = 2.25 * $item->{indent}; + my $bottom = '0'; + if ($next_par) { + $next_par->{used_on_prev_item} = 1; + $bottom = '1em'; + } + return $self->nl . "
\n" . $self->string($item) . $self->nl . "
\n"; } sub Align { @@ -1562,20 +1562,27 @@ sub Align { ); sub List { - my $self = shift; - my $item = shift; - my $list = $bullet{ $item->{bullet} }; + my ($self, $item, $next_par) = @_; + my $list = $bullet{ $item->{bullet} }; + my $margin = '0'; + if ($next_par) { + $next_par->{used_on_prev_item} = 1; + $margin = '0 0 1em 0'; + } return $self->nl . main::tag( $list->[0], - style => "margin:0; padding-left:2.25em; $list->[1]", + style => "margin: $margin; padding-left: 2.25em; $list->[1]", $self->string($item) . $self->nl ); } sub Bullet { - my $self = shift; - my $item = shift; + my ($self, $item, $next_par) = @_; + if ($next_par) { + $next_par->{used_on_prev_item} = 1; + return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; + } return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; } @@ -1603,9 +1610,8 @@ sub Heading { } sub Par { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . "\n"; + my ($self, $item) = @_; + return $item->{used_on_prev_item} ? '' : $self->nl . '
    ' . "\n"; } sub Break {"
    \n"} From c9a64c59df8ec70e555963b8e27b45fc9dc0ef6c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 30 Dec 2025 11:38:29 -0700 Subject: [PATCH 2/9] Refactor the PGML::format string loop. As suggested by @dvpc, refactor the PGML::format::string loop to call the appropriate method if it exists. Also all methods now have access to the block and current state of the loop to use as needed. --- macros/core/PGML.pl | 92 ++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 59 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index e31f84a3b..fce60022d 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1267,45 +1267,21 @@ sub format { sub string { my ($self, $block) = @_; - my $stack = $block->{stack}; - my @strings = (); - my $string; - for my $i (0 .. $#$stack) { - my $item = $stack->[$i]; - my $next_par = $i < $#$stack && $stack->[ $i + 1 ]{type} eq 'par' ? $stack->[ $i + 1 ] : undef; - $self->{item} = $item; - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); - for ($item->{type}) { - /indent/ && do { $string = $self->Indent($item, $next_par); last }; - /align/ && do { $string = $self->Align($item); last }; - /par/ && do { $string = $self->Par($item); last }; - /list/ && do { $string = $self->List($item, $next_par); last }; - /bullet/ && do { $string = $self->Bullet($item, $next_par); last }; - /text/ && do { $string = $self->Text($item); last }; - /variable/ && do { $string = $self->Variable($item, $block); last }; - /command/ && do { $string = $self->Command($item); last }; - /math/ && do { $string = $self->Math($item); last }; - /answer/ && do { $string = $self->Answer($item); last }; - /bold/ && do { $string = $self->Bold($item); last }; - /italic/ && do { $string = $self->Italic($item); last }; - /heading/ && do { $string = $self->Heading($item); last }; - /quote/ && do { $string = $self->Quote($item, $strings[-1] || ''); last }; - /rule/ && do { $string = $self->Rule($item); last }; - /code/ && do { $string = $self->Code($item); last }; - /pre/ && do { $string = $self->Pre($item); last }; - /verbatim/ && do { $string = $self->Verbatim($item); last }; - /image/ && do { $string = $self->Image($item); last }; - /break/ && do { $string = $self->Break($item); last }; - /forced/ && do { $string = $self->Forced($item); last }; - /comment/ && do { $string = $self->Comment($item); last }; - /table/ && do { $string = $self->Table($item); last }; - /tag/ && do { $string = $self->Tag($item); last }; + my $stack = $block->{stack}; + my $state = { strings => [], i => 0 }; + while ($state->{i} <= $#$stack) { + my $item = $self->{item} = $stack->[ $state->{i}++ ]; + my $method = ucfirst($item->{type}); + $self->{nl} = (!defined($state->{strings}[-1]) || $state->{strings}[-1] =~ m/\n$/ ? '' : "\n"); + if (Value::can($self, $method)) { + my $string = $self->$method($item, $block, $state); + push(@{ $state->{strings} }, $string) unless !defined $string || $string eq ''; + } else { PGML::Warning "Warning: unknown block type '$item->{type}' in " . ref($self) . "::format\n"; } - push(@strings, $string) unless (!defined $string || $string eq ''); } - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); - return join('', @strings); + $self->{nl} = (!defined($state->{strings}[-1]) || $state->{strings}[-1] =~ m/\n$/ ? '' : "\n"); + return join('', @{ $state->{strings} }); } sub nl { @@ -1525,13 +1501,13 @@ sub Escape { } sub Indent { - my ($self, $item, $next_par) = @_; + my ($self, $item, $block, $state) = @_; return $self->string($item) if $item->{indent} == 0; my $em = 2.25 * $item->{indent}; my $bottom = '0'; - if ($next_par) { - $next_par->{used_on_prev_item} = 1; + if (defined $block->{stack}[ $state->{i} ] && $block->{stack}[ $state->{i} ]{type} eq 'par') { $bottom = '1em'; + $state->{i}++; } return $self->nl . "
    \n" . $self->string($item) . $self->nl . "
    \n"; } @@ -1562,12 +1538,12 @@ sub Align { ); sub List { - my ($self, $item, $next_par) = @_; + my ($self, $item, $block, $state) = @_; my $list = $bullet{ $item->{bullet} }; my $margin = '0'; - if ($next_par) { - $next_par->{used_on_prev_item} = 1; + if (defined $block->{stack}[ $state->{i} ] && $block->{stack}[ $state->{i} ]{type} eq 'par') { $margin = '0 0 1em 0'; + $state->{i}++; } return $self->nl . main::tag( @@ -1578,9 +1554,9 @@ sub List { } sub Bullet { - my ($self, $item, $next_par) = @_; - if ($next_par) { - $next_par->{used_on_prev_item} = 1; + my ($self, $item, $block, $state) = @_; + if (defined $block->{stack}[ $state->{i} ] && $block->{stack}[ $state->{i} ]{type} eq 'par') { + $state->{i}++; return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; } return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; @@ -1610,8 +1586,9 @@ sub Heading { } sub Par { - my ($self, $item) = @_; - return $item->{used_on_prev_item} ? '' : $self->nl . '
    ' . "\n"; + my $self = shift; + my $item = shift; + return $self->nl . '
    ' . "\n"; } sub Break {"
    \n"} @@ -1632,10 +1609,9 @@ sub Italic { our %closeQuote = ('"' => "”", "'" => "’"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; + my ($self, $item, $block, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; return $closeQuote{ $item->{token} }; } @@ -1811,10 +1787,9 @@ sub Italic { our %closeQuote = ('"' => "''", "'" => "'"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; + my ($self, $item, $block, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; return $closeQuote{ $item->{token} }; } @@ -1968,10 +1943,9 @@ sub Italic { our %closeQuote = ('"' => "", "'" => ""); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; + my ($self, $item, $block, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; return $closeQuote{ $item->{token} }; } From 12dacaf8b08234544afaad519d9cc5930b9ce4cf Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 30 Dec 2025 12:26:04 -0700 Subject: [PATCH 3/9] Convert span to div to fix HTML validation issue with hr inside. --- macros/core/PGML.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index fce60022d..7b05201f7 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1635,7 +1635,7 @@ sub Rule { . main::tag( 'div', main::tag( - 'span', + 'div', style => "width:$width; display:inline-block; margin:0.3em auto", main::tag( 'hr', style => "width:$width; height:$height; background-color:currentColor; margin:0.3em auto;" From 0e4426ccad283cf322b7fb0eb8d6435e7cc8e80a Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 30 Dec 2025 19:37:00 -0700 Subject: [PATCH 4/9] Remove setting PGML::format::item This was never used anywhere else (that I could find), so no need to store the current item in the PGML::format::string loop. --- macros/core/PGML.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index 7b05201f7..6fa51628e 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1270,7 +1270,7 @@ sub string { my $stack = $block->{stack}; my $state = { strings => [], i => 0 }; while ($state->{i} <= $#$stack) { - my $item = $self->{item} = $stack->[ $state->{i}++ ]; + my $item = $stack->[ $state->{i}++ ]; my $method = ucfirst($item->{type}); $self->{nl} = (!defined($state->{strings}[-1]) || $state->{strings}[-1] =~ m/\n$/ ? '' : "\n"); if (Value::can($self, $method)) { From 1c08db43dbf048676e99f6858f002ec9a058b91c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 30 Dec 2025 21:51:28 -0700 Subject: [PATCH 5/9] PGML::format combine item and block into state hash. All of the format methods now receive a single state hash instead of the item, block, and state as separate inputs. Reduce the number of times some variables are dereferenced from the state hash. Remove some white space to minimize HTML output. Leaving newline characters `\n` alone for now. --- macros/core/PGML.pl | 274 +++++++++++++++++++++----------------------- 1 file changed, 131 insertions(+), 143 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index 6fa51628e..4dcad380b 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1268,20 +1268,21 @@ sub format { sub string { my ($self, $block) = @_; my $stack = $block->{stack}; - my $state = { strings => [], i => 0 }; + my @strings; + my $state = { block => $block, strings => \@strings, i => 0 }; while ($state->{i} <= $#$stack) { - my $item = $stack->[ $state->{i}++ ]; - my $method = ucfirst($item->{type}); - $self->{nl} = (!defined($state->{strings}[-1]) || $state->{strings}[-1] =~ m/\n$/ ? '' : "\n"); + $state->{item} = $stack->[ $state->{i}++ ]; + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); + my $method = ucfirst($state->{item}{type}); if (Value::can($self, $method)) { - my $string = $self->$method($item, $block, $state); - push(@{ $state->{strings} }, $string) unless !defined $string || $string eq ''; + my $string = $self->$method($state); + push(@strings, $string) unless !defined $string || $string eq ''; } else { - PGML::Warning "Warning: unknown block type '$item->{type}' in " . ref($self) . "::format\n"; + PGML::Warning "Warning: unknown block type '$state->{item}{type}' in " . ref($self) . "::format\n"; } } - $self->{nl} = (!defined($state->{strings}[-1]) || $state->{strings}[-1] =~ m/\n$/ ? '' : "\n"); - return join('', @{ $state->{strings} }); + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); + return join('', @strings); } sub nl { @@ -1311,8 +1312,8 @@ sub nl { sub Comment { return "" } sub Table { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "[misplaced $item->{type}]" if $item->{hasWarning}; my @options; foreach my $option (@{ $item->{options} || [] }) { @@ -1333,13 +1334,13 @@ sub Table { } sub Tag { - my ($self, $item) = @_; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } sub Math { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $math = $item->{text}; if ($item->{parsed}) { my $context = $main::context{Typeset}; @@ -1364,8 +1365,8 @@ sub Math { } sub Answer { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $ans = $item->{answer}; my $rule; $item->{width} = length($item->{token}) - 2 if (!defined($item->{width})); @@ -1434,8 +1435,8 @@ sub Answer { } sub Command { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $self->{parser}->replaceCommand($item); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; $text = $self->Escape($text) unless $item->{hasStar}; @@ -1443,25 +1444,24 @@ sub Command { } sub Variable { - my $self = shift; - my $item = shift; - my $cur = shift; - my $text = $self->{parser}->replaceVariable($item, $cur); + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->{parser}->replaceVariable($item, $state->{block}); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; $text = $self->Escape($text) unless $item->{hasStar}; return $text; } sub Text { - my $self = shift; - my $item = shift; - my $text = $self->{parser}->replaceText($item); + my ($self, $state) = @_; + my $text = $self->{parser}->replaceText($state->{item}); $text =~ s/^\n+// if substr($text, 0, 1) eq "\n" && $self->nl eq ""; return $self->Escape($text); } sub Image { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; my $source = $item->{source}; my $width = $item->{width} || ''; @@ -1487,9 +1487,8 @@ package PGML::Format::html; our @ISA = ('PGML::Format'); sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/\&/g; $string =~ s//>/g; @@ -1501,20 +1500,22 @@ sub Escape { } sub Indent { - my ($self, $item, $block, $state) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; my $em = 2.25 * $item->{indent}; my $bottom = '0'; - if (defined $block->{stack}[ $state->{i} ] && $block->{stack}[ $state->{i} ]{type} eq 'par') { + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { $bottom = '1em'; $state->{i}++; } - return $self->nl . "
    \n" . $self->string($item) . $self->nl . "
    \n"; + return $self->nl . "
    \n" . $self->string($item) . $self->nl . "
    \n"; } sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->nl . '
    [1]", + style => "margin:$margin;padding-left:2.25em;$list->[1]", $self->string($item) . $self->nl ); } sub Bullet { - my ($self, $item, $block, $state) = @_; - if (defined $block->{stack}[ $state->{i} ] && $block->{stack}[ $state->{i} ]{type} eq 'par') { + my ($self, $state) = @_; + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { $state->{i}++; - return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; + return $self->nl . '
  • ' . $self->string($state->{item}) . "
  • \n"; } - return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; + return $self->nl . '
  • ' . $self->string($state->{item}) . "
  • \n"; } sub Code { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $class = ($item->{class} ? ' class="' . $item->{class} . '"' : ""); return $self->nl . '
    ' . $self->string($item) . "
    \n"; } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my ($self, $state) = @_; + return $self->nl . '
    ' . $self->string($state->{item}) . "
    \n"; } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; @@ -1586,38 +1589,35 @@ sub Heading { } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . '
    ' . "\n"; } sub Break {"
    \n"} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } our %openQuote = ('"' => "“", "'" => "‘"); our %closeQuote = ('"' => "”", "'" => "’"); sub Quote { - my ($self, $item, $block, $state) = @_; + my ($self, $state) = @_; my $string = $state->{strings}[-1] // ''; - return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = '100%;'; my $height = '1px'; if (defined $item->{width}) { @@ -1645,20 +1645,21 @@ sub Rule { } sub Verbatim { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $self->Escape($item->{text}); $text = "$text" if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my %whitelist = (div => 1, span => 1); my @attributes = ref($item->{html}) eq 'ARRAY' ? @{ $item->{html} } : $item->{html}; my $tag = @attributes % 2 ? (shift @attributes // 'div') : 'div'; @@ -1702,31 +1703,30 @@ package PGML::Format::tex; ); sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined($string); + my ($self, $string) = @_; + return '' unless defined($string); $string =~ s/(["\#\$%&<>\\^_\{|\}~])/$escape{$1}/eg; return $string; } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; my $em = 2.25 * $item->{indent}; return $self->nl . "{\\pgmlIndent\n" . $self->string($item) . $self->nl . "\\par}%\n"; } sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $align = uc(substr($item->{align}, 0, 1)) . substr($item->{align}, 1); return $self->nl . "{\\pgml${align}{}" . $self->string($item) . $self->nl . "\\par}%\n"; } sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->nl . "{\\pgmlIndent\\let\\pgmlItem=\\pgml$item->{bullet}Item\n" @@ -1736,26 +1736,23 @@ sub List { } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . "\\pgmlItem{}" . $self->string($item) . "\n"; + my ($self, $state) = @_; + return $self->nl . "\\pgmlItem{}" . $self->string($state->{item}) . "\n"; } sub Code { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; @@ -1764,38 +1761,35 @@ sub Heading { } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\\vskip\\baselineskip\n"; } sub Break {"\\pgmlBreak\n"} sub Bold { - my $self = shift; - my $item = shift; - return "{\\bfseries{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\bfseries{}" . $self->string($state->{item}) . "}"; } sub Italic { - my $self = shift; - my $item = shift; - return "{\\itshape{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\itshape{}" . $self->string($state->{item}) . "}"; } our %openQuote = ('"' => "``", "'" => "`"); our %closeQuote = ('"' => "''", "'" => "'"); sub Quote { - my ($self, $item, $block, $state) = @_; + my ($self, $state) = @_; my $string = $state->{strings}[-1] // ''; - return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = "100%"; my $height = "1"; $width = $item->{width} if defined $item->{width}; @@ -1809,20 +1803,21 @@ sub Rule { } sub Verbatim { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $self->Escape($item->{text}); $text = "{\\tt{}$text}" if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my ($tex_begin, $tex_end); if (ref($item->{tex}) eq 'ARRAY') { ($tex_begin, $tex_end) = @{ $item->{tex} }; @@ -1839,9 +1834,8 @@ package PGML::Format::ptx; our @ISA = ('PGML::Format'); sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/&/g; $string =~ s//>/g; @@ -1850,15 +1844,14 @@ sub Escape { # No indentation for PTX sub Indent { - my $self = shift; - my $item = shift; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } # No align for PTX sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } @@ -1875,21 +1868,20 @@ sub Align { ); sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $list = $bullet{ $item->{bullet} }; return $self->nl . '<' . $list . '>' . "\n" . $self->string($item) . $self->nl . "\n"; } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . '
  • ' . $self->string($item) . '
  • '; + my ($self, $state) = @_; + return $self->nl . '
  • ' . $self->string($state->{item}) . '
  • '; } sub Code { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $code = ($self->string($item) =~ /\n/) ? $self->nl @@ -1905,8 +1897,7 @@ sub Code { } sub Pre { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; ## PGML pre can have stylized contents like bold, ## and PTX pre cannot have element children return "\n"; @@ -1914,51 +1905,47 @@ sub Pre { # PreTeXt can't use headings. sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\n"; } sub Break {"\n\n"} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } our %openQuote = ('"' => "", "'" => ""); our %closeQuote = ('"' => "", "'" => ""); sub Quote { - my ($self, $item, $block, $state) = @_; + my ($self, $state) = @_; my $string = $state->{strings}[-1] // ''; - return $openQuote{ $item->{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } # No rule for PTX sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return "\n"; } sub Verbatim { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; if ($item->{hasStar}) { #Don't escape most content. Just < and & @@ -1974,12 +1961,13 @@ sub Verbatim { } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my @args = ref($item->{ptx}) eq 'ARRAY' ? @{ $item->{ptx} } : $item->{ptx}; if (my $tag = shift @args) { return NiceTables::tag($self->string($item), $tag, @args); From 8096d988cbeb1004b534b57e16f4043d16f92de6 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Wed, 31 Dec 2025 08:47:47 -0700 Subject: [PATCH 6/9] PGML::Format make escape method lower case. This way only "item" methods are capitalized to distinguish them from other methods which are not a block item type. --- macros/core/PGML.pl | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index 4dcad380b..f74463460 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1292,7 +1292,7 @@ sub nl { return $nl; } -sub Escape { shift; shift } +sub escape { shift; shift } sub Indent { return "" } sub Align { return "" } @@ -1439,7 +1439,7 @@ sub Command { my $item = $state->{item}; my $text = $self->{parser}->replaceCommand($item); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } @@ -1448,7 +1448,7 @@ sub Variable { my $item = $state->{item}; my $text = $self->{parser}->replaceVariable($item, $state->{block}); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } @@ -1456,7 +1456,7 @@ sub Text { my ($self, $state) = @_; my $text = $self->{parser}->replaceText($state->{item}); $text =~ s/^\n+// if substr($text, 0, 1) eq "\n" && $self->nl eq ""; - return $self->Escape($text); + return $self->escape($text); } sub Image { @@ -1486,7 +1486,7 @@ sub Image { package PGML::Format::html; our @ISA = ('PGML::Format'); -sub Escape { +sub escape { my ($self, $string) = @_; return '' unless defined $string; $string =~ s/&/\&/g; @@ -1647,7 +1647,7 @@ sub Rule { sub Verbatim { my ($self, $state) = @_; my $item = $state->{item}; - my $text = $self->Escape($item->{text}); + my $text = $self->escape($item->{text}); $text = "$text" if $item->{hasStar}; return $text; } @@ -1702,7 +1702,7 @@ package PGML::Format::tex; '~' => '{\ttfamily\char126}', ); -sub Escape { +sub escape { my ($self, $string) = @_; return '' unless defined($string); $string =~ s/(["\#\$%&<>\\^_\{|\}~])/$escape{$1}/eg; @@ -1805,7 +1805,7 @@ sub Rule { sub Verbatim { my ($self, $state) = @_; my $item = $state->{item}; - my $text = $self->Escape($item->{text}); + my $text = $self->escape($item->{text}); $text = "{\\tt{}$text}" if $item->{hasStar}; return $text; } @@ -1833,7 +1833,7 @@ sub Tag { package PGML::Format::ptx; our @ISA = ('PGML::Format'); -sub Escape { +sub escape { my ($self, $string) = @_; return '' unless defined $string; $string =~ s/&/&/g; From 1dde95c02dff79d59b5ee501863ad1bdb4e80dd0 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Wed, 31 Dec 2025 09:59:32 -0700 Subject: [PATCH 7/9] PGML::Format::html remove new lines and use main::tag. Since newlines and spaces are extra characters to send, minimize the HTML output by removing any new line or unneeded space characters. In addition switch to using `main::tag` to create the html tags. --- macros/core/PGML.pl | 79 ++++++++++++++++-------------------- t/pg_problems/problem_file.t | 4 +- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index f74463460..cbe1f7150 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1510,32 +1510,25 @@ sub Indent { $bottom = '1em'; $state->{i}++; } - return $self->nl . "
    \n" . $self->string($item) . $self->nl . "
    \n"; + return main::tag('div', style => "margin:0 0 $bottom ${em}em", $self->string($item)); } sub Align { my ($self, $state) = @_; my $item = $state->{item}; - return - $self->nl - . '
    ' . "\n" - . $self->string($item) - . $self->nl - . "
    \n"; + return main::tag('div', style => "text-align:$item->{align};margin:0", $self->string($item)); } our %bullet = ( - bullet => [ 'ul', 'list-style-type: disc;' ], - numeric => [ 'ol', 'list-style-type: decimal;' ], - alpha => [ 'ol', 'list-style-type: lower-alpha;' ], - Alpha => [ 'ol', 'list-style-type: upper-alpha;' ], - roman => [ 'ol', 'list-style-type: lower-roman;' ], - Roman => [ 'ol', 'list-style-type: upper-roman;' ], - disc => [ 'ul', 'list-style-type: disc;' ], - circle => [ 'ul', 'list-style-type: circle;' ], - square => [ 'ul', 'list-style-type: square;' ], + bullet => [ 'ul', 'list-style-type:disc;' ], + numeric => [ 'ol', 'list-style-type:decimal;' ], + alpha => [ 'ol', 'list-style-type:lower-alpha;' ], + Alpha => [ 'ol', 'list-style-type:upper-alpha;' ], + roman => [ 'ol', 'list-style-type:lower-roman;' ], + Roman => [ 'ol', 'list-style-type:upper-roman;' ], + disc => [ 'ul', 'list-style-type:disc;' ], + circle => [ 'ul', 'list-style-type:circle;' ], + square => [ 'ul', 'list-style-type:square;' ], ); sub List { @@ -1548,34 +1541,33 @@ sub List { $margin = '0 0 1em'; $state->{i}++; } - return $self->nl - . main::tag( - $list->[0], - style => "margin:$margin;padding-left:2.25em;$list->[1]", - $self->string($item) . $self->nl - ); + return main::tag($list->[0], style => "margin:$margin;padding-left:2.25em;$list->[1]", $self->string($item)); } sub Bullet { my ($self, $state) = @_; my $next = $state->{block}{stack}[ $state->{i} ]; + my $style; if (defined $next && $next->{type} eq 'par') { $state->{i}++; - return $self->nl . '
  • ' . $self->string($state->{item}) . "
  • \n"; + $style = 'margin-bottom:1em'; } - return $self->nl . '
  • ' . $self->string($state->{item}) . "
  • \n"; + return main::tag('li', $style ? (style => $style) : (), $self->string($state->{item})); } sub Code { my ($self, $state) = @_; - my $item = $state->{item}; - my $class = ($item->{class} ? ' class="' . $item->{class} . '"' : ""); - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my $item = $state->{item}; + return main::tag( + 'pre', + style => 'margin:0', + main::tag('code', $item->{class} ? (class => $item->{class}) : (), $self->string($item)) + ); } sub Pre { my ($self, $state) = @_; - return $self->nl . '
    ' . $self->string($state->{item}) . "
    \n"; + return main::tag('pre', style => 'margin:0', main::tag('code', $self->string($state->{item}))); } sub Heading { @@ -1585,24 +1577,24 @@ sub Heading { my $text = $self->string($item); $text =~ s/^ +| +$//gm; $text =~ s! +(
    )!$1!g; - return '' . $text . "\n"; + return main::tag("h$n", style => 'margin:0', $text); } sub Par { my ($self, $state) = @_; - return $self->nl . '
    ' . "\n"; + return main::tag('div', style => 'margin-top:1em', ''); } -sub Break {"
    \n"} +sub Break {'
    '} sub Bold { my ($self, $state) = @_; - return '' . $self->string($state->{item}) . ''; + return main::tag('strong', $self->string($state->{item})); } sub Italic { my ($self, $state) = @_; - return '' . $self->string($state->{item}) . ''; + return main::tag('em', $self->string($state->{item})); } our %openQuote = ('"' => "“", "'" => "‘"); @@ -1631,24 +1623,23 @@ sub Rule { $height = $item->{size}; $height .= 'px' if ($height =~ /^\d*\.?\d+$/); } - return $self->nl - . main::tag( + return main::tag( + 'div', + main::tag( 'div', + style => "width:$width;display:inline-block;margin:0.3em auto", main::tag( - 'div', - style => "width:$width; display:inline-block; margin:0.3em auto", - main::tag( - 'hr', style => "width:$width; height:$height; background-color:currentColor; margin:0.3em auto;" - ) + 'hr', style => "width:$width;height:$height;background-color:currentColor;margin:0.3em auto;" ) - ); + ) + ); } sub Verbatim { my ($self, $state) = @_; my $item = $state->{item}; my $text = $self->escape($item->{text}); - $text = "$text" if $item->{hasStar}; + $text = main::tag('code', $text) if $item->{hasStar}; return $text; } diff --git a/t/pg_problems/problem_file.t b/t/pg_problems/problem_file.t index a9c6b4605..47a70e34f 100644 --- a/t/pg_problems/problem_file.t +++ b/t/pg_problems/problem_file.t @@ -21,8 +21,8 @@ is($pg->{post_header_text}, '', 'post_header_text is empty'); is( $pg->{body_text}, qq{
    \n} - . qq{Enter a value for .\n} - . qq{
    \n} + . qq{Enter a value for .} + . qq{
    } . qq{
    } . qq{} From 963083f5111267a1246cbc79063604e2feb64645 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Wed, 31 Dec 2025 18:02:35 -0700 Subject: [PATCH 8/9] Use
    (preferred for HTML5) instead of
    in PGML::Format::html. --- macros/core/PGML.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index cbe1f7150..add465be8 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1576,7 +1576,7 @@ sub Heading { my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; - $text =~ s! +(
    )!$1!g; + $text =~ s! +(
    )!$1!g; return main::tag("h$n", style => 'margin:0', $text); } @@ -1585,7 +1585,7 @@ sub Par { return main::tag('div', style => 'margin-top:1em', ''); } -sub Break {'
    '} +sub Break {'
    '} sub Bold { my ($self, $state) = @_; From e6a6a7d5a1aba185d71e05059f0cbbff830dda8c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Thu, 1 Jan 2026 16:10:28 -0700 Subject: [PATCH 9/9] Fix issue with PGML::Format::Table. The table method calls itself and so it also needs to be updated to send itself a "stack" hash. Since only the `item` is used from the hash, send only that. --- macros/core/PGML.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index add465be8..96b0cb455 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1270,7 +1270,7 @@ sub string { my $stack = $block->{stack}; my @strings; my $state = { block => $block, strings => \@strings, i => 0 }; - while ($state->{i} <= $#$stack) { + while ($state->{i} < @$stack) { $state->{item} = $stack->[ $state->{i}++ ]; $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); my $method = ucfirst($state->{item}{type}); @@ -1323,7 +1323,7 @@ sub Table { my $table = []; my $row = []; for my $cell (@{ $item->{stack} }) { - push(@$row, $self->Table($cell)); + push(@$row, $self->Table({ item => $cell })); if ($cell->{hasStar}) { push(@$table, $row); $row = [];