From 0011193240bd2c73d608522ccdb476b2d8ba79ee Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 3 Jul 2025 17:55:52 -0500 Subject: [PATCH 001/111] Fix direct `maketext` calls in the Translator. `maketext` is not defined in the Translator package, and so it cannot be called directly in the default graders defined in the translator. Instead an eval is needed to access the `maketext` in the safe compartment. This fixes issue #1261. --- lib/WeBWorK/PG/Translator.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index 71595dad22..68f19a7076 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -1092,7 +1092,8 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = maketext('You can earn partial credit on this problem.') if keys %$answers > 1; + $problem_result{msg} = eval('main::maketext("You can earn partial credit on this problem.")') + if keys %$answers > 1; # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; @@ -1124,7 +1125,7 @@ sub avg_problem_grader { { $answers->{$credit_name}{score} = 1; $answers->{$credit_name}{ans_message} = - maketext('This answer was marked correct because the primary answer is correct.'); + eval('main::maketext("This answer was marked correct because the primary answer is correct.")'); $credit{$credit_name} = 1; } } From 0a00d342ba96bec41d9e6ca42eccca1bc148a510 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Mon, 7 Jul 2025 11:55:55 -0400 Subject: [PATCH 002/111] Fixes POD errors/warnings in modules Also fixes spelling of SYNOPSIS --- lib/AnswerHash.pm | 259 ++++++------- lib/AnswerIO.pm | 4 +- lib/Applet.pm | 2 +- lib/ChoiceList.pm | 2 +- lib/Circle.pm | 41 +- lib/Fraction.pm | 49 ++- lib/Fun.pm | 4 +- lib/Hermite.pm | 41 +- lib/Label.pm | 8 +- lib/List.pm | 2 +- lib/Matrix.pm | 89 +++-- lib/PGloadfiles.pm | 14 +- lib/Parser/Legacy/PGcomplexmacros.pl | 2 +- lib/Regression.pm | 2 +- lib/Select.pm | 4 +- lib/Value.pm | 473 ++++++++++++----------- lib/Value/AnswerChecker.pm | 325 ++++++++-------- lib/VectorField.pm | 4 +- lib/WWPlot.pm | 4 +- lib/WeBWorK/PG/ConvertToPGML.pm | 64 ++- lib/WeBWorK/PG/EquationCache.pm | 2 +- lib/WeBWorK/PG/ImageGenerator.pm | 2 +- lib/WeBWorK/PG/RestrictedClosureClass.pm | 2 +- lib/WeBWorK/PG/Translator.pm | 2 +- 24 files changed, 734 insertions(+), 667 deletions(-) diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm index 012fe4aba2..7273103cd7 100755 --- a/lib/AnswerHash.pm +++ b/lib/AnswerHash.pm @@ -21,37 +21,18 @@ =head1 NAME - AnswerHash.pm -- located in the courseScripts directory +AnswerHash.pm - a class to store student answers and the answer evaluator - This file contains the packages/classes: - AnswerHash and AnswerEvaluator +=head1 DESCRIPTION - AnswerHash -=head1 SYNPOSIS +This class stores information related to the student's answer. It is little more than a standard perl hash with +a special name, but it does have some access and manipulation methods. - AnswerHash -- this class stores information related to the student's - answer. It is little more than a standard perl hash with - a special name, but it does have some access and - manipulation methods. More of these may be added as it - becomes necessary. +Usage: - Usage: $rh_ans = new AnswerHash; + $rh_ans = AnswerHash->new(); - AnswerEvaluator -- this class organizes the construction of - answer evaluator subroutines which check the - student's answer. By plugging filters into the - answer evaluator class you can customize the way the - student's answer is normalized and checked. Our hope - is that with properly designed filters, it will be - possible to reuse the filters in different - combinations to obtain different answer evaluators, - thus greatly reducing the programming and maintenance - required for constructing answer evaluators. - - Usage: $ans_eval = new AnswerEvaluator; - -=cut - -=head1 DESCRIPTION : AnswerHash +=head2 SYNOPSIS The answer hash class is guaranteed to contain the following instance variables: @@ -120,7 +101,7 @@ The answer hash class is guaranteed to contain the following instance variables: 'error_message' => '', -=head3 AnswerHash Methods: +=head2 METHODS =cut @@ -143,11 +124,13 @@ my %fields = ( ## Initializing constructor -=head4 new +=head3 AnswerHash->new - Useage $rh_anshash = new AnswerHash; +Usage - returns an object of type AnswerHash. + $rh_anshash = new AnswerHash; + +returns an object of type AnswerHash. =cut @@ -179,17 +162,19 @@ sub new { ## Checks to make sure that the keys are valid, ## then sets their value -=head4 setKeys +=head3 setKeys + +Usage: - $rh_ans->setKeys(score=>1, student_answer => "yes"); - Sets standard elements in the AnswerHash (the ones defined - above). Will give error if one attempts to set non-standard keys. + $rh_ans->setKeys(score=>1, student_answer => "yes"); - To set a non-standard element in a hash use +Sets standard elements in the AnswerHash (the ones defined +above). Will give error if one attempts to set non-standard key +To set a non-standard element in a hash use - $rh_ans->{non-standard-key} = newValue; + $rh_ans->{non-standard-key} = newValue; - There are no safety checks when using this method. +There are no safety checks when using this method. =cut @@ -207,40 +192,45 @@ sub setKeys { # access methods -=head4 data +=head3 data + +Usage: + + $rh_ans->data('foo'); # set $rh_ans->{student_ans} = 'foo'; + $student_input = $rh_ans->data(); # retrieve value of $rh_ans->{student_ans} - Usage: $rh_ans->data('foo'); set $rh_ans->{student_ans} = 'foo'; - $student_input = $rh_ans->data(); retrieve value of $rh_ans->{student_ans} +synonym for C - synonym for input +=head3 input -=head4 input +Usage: - Usage: $rh_ans->input('foo') sets $rh_ans->{student_ans} = 'foo'; - $student_input = $rh_ans->input(); + $rh_ans->input('foo') # sets $rh_ans->{student_ans} = 'foo'; + $student_input = $rh_ans->input(); - synonym for data +synonym for C =cut -sub data { #$rh_ans->data('foo') is a synonym for $rh_ans->{student_ans}='foo' +sub data { my $self = shift; $self->input(@_); } -sub input { #$rh_ans->input('foo') is a synonym for $rh_ans->{student_ans}='foo' +sub input { my $self = shift; my $input = shift; $self->{student_ans} = $input if defined($input); $self->{student_ans}; } -=head4 input +=head3 score - Usage: $rh_ans->score(1) - $score = $rh_ans->score(); +Usage: + $rh_ans->score(1) + $score = $rh_ans->score(); - Retrieve or set $rh_ans->{score}, the student's score on the problem. +Retrieve or set $rh_ans->{score}, the student's score on the problem. =cut @@ -251,14 +241,14 @@ sub score { $self->{score}; } -=head4 stringify_hash +=head3 stringify_hash - Usage: $rh_ans->stringify_hash; +Usage: - Turns all values in the hash into strings (so they won't cause trouble outside - the safe compartment). + $rh_ans->stringify_hash; - Hashes and arrays are converted into a JSON string. +Turns all values in the hash into strings (so they won't cause trouble outside +the safe compartment). Hashes and arrays are converted into a JSON string. =cut @@ -278,46 +268,49 @@ sub stringify_hash { # error methods -=head4 throw_error +=head3 throw_error - Usage: $rh_ans->throw_error("FLAG", "message"); +Usage: - FLAG is a distinctive word that describes the type of error. - Examples are EVAL for an evaluation error or "SYNTAX" for a syntax error. - The entry $rh_ans->{error_flag} is set to "FLAG". + $rh_ans->throw_error("FLAG", "message"); - The catch_error and clear_error methods use - this entry. +FLAG is a distinctive word that describes the type of error. +Examples are EVAL for an evaluation error or "SYNTAX" for a syntax error. +The entry $rh_ans->{error_flag} is set to "FLAG". - message is a descriptive message for the end user, defining what error occured. +The catch_error and clear_error methods use this entry. -=head4 catch_error +message is a descriptive message for the end user, defining what error occured. - Usage: $rh_ans->catch_error("FLAG2"); +=head3 catch_error - Returns true (1) if $rh_ans->{error_flag} equals "FLAG2", otherwise it returns - false (empty string). +Usage: + $rh_ans->catch_error("FLAG2"); +Returns true (1) if $rh_ans->{error_flag} equals "FLAG2", otherwise it returns +false (empty string). -=head4 clear_error +=head3 clear_error - Usage: $rh_ans->clear_error("FLAG2"); +Usage: - If $rh_ans->{error_flag} equals "FLAG2" then the {error_flag} entry is set to - the empty string as is the entry {error_message} + $rh_ans->clear_error("FLAG2"); -=head4 error_flag +If $rh_ans->{error_flag} equals "FLAG2" then the {error_flag} entry is set to +the empty string as is the entry {error_message} -=head4 error_message +=head3 error_flag, error_message - Usage: $flag = $rh_ans -> error_flag(); +Usage: - $message = $rh_ans -> error_message(); + $flag = $rh_ans -> error_flag(); - Retrieve or set the {error_flag} and {error_message} entries. + $message = $rh_ans -> error_message(); - Use catch_error and throw_error where possible. +Retrieve or set the {error_flag} and {error_message} entries. + +Use catch_error and throw_error where possible. =cut @@ -410,29 +403,27 @@ sub error_message { # action methods -=head4 OR - - Usage: $rh_ans->OR($rh_ans2); - - Returns a new AnswerHash whose score is the maximum of the scores in $rh_ans and $rh_ans2. - The correct answers for the two hashes are combined with "OR". - The types are concatenated with "OR" as well. - Currently nothing is done with the error flags and messages. - +=head3 OR +Usage: -=head4 AND + $rh_ans->OR($rh_ans2); +Returns a new AnswerHash whose score is the maximum of the scores in $rh_ans and $rh_ans2. +The correct answers for the two hashes are combined with "OR". +The types are concatenated with "OR" as well. +Currently nothing is done with the error flags and messages. - Usage: $rh_ans->AND($rh_ans2); - - Returns a new AnswerHash whose score is the minimum of the scores in $rh_ans and $rh_ans2. - The correct answers for the two hashes are combined with "AND". - The types are concatenated with "AND" as well. - Currently nothing is done with the error flags and messages. +=head3 AND +Usage: + $rh_ans->AND($rh_ans2); +Returns a new AnswerHash whose score is the minimum of the scores in $rh_ans and $rh_ans2. +The correct answers for the two hashes are combined with "AND". +The types are concatenated with "AND" as well. +Currently nothing is done with the error flags and messages. =cut @@ -471,29 +462,31 @@ sub AND { $out_hash; } -=head1 Description: AnswerEvaluator - - - - -=cut - package AnswerEvaluator; use Exporter; use PGUtil qw(not_null pretty_print); -=head3 AnswerEvaluator Methods +=head1 DESCRIPTION: AnswerEvaluator +This class organizes the construction of answer evaluator subroutines which check the +student's answer. By plugging filters into the answer evaluator class you can customize the way the +student's answer is normalized and checked. Our hope is that with properly designed filters, it will be +possible to reuse the filters in different combinations to obtain different answer evaluators, +thus greatly reducing the programming and maintenance required for constructing answer evaluators. +Usage: + $ans_eval = new AnswerEvaluator; +=head2 METHODS +=head3 new +Create a new AnswerEvaluator -=cut - -=head4 new +Usage: + AnswerEvaluator->new(); =cut @@ -562,10 +555,11 @@ sub get_student_answer { $input; } -=head4 evaluate +=head3 evaluate - $answer_evaluator->evaluate($student_answer_string) +Usage: + $answer_evaluator->evaluate($student_answer_string) =cut @@ -686,14 +680,11 @@ sub print_result_if_debug { # $rh_ans; # } -=head4 install_pre_filter - -=head4 install_evaluator - - -=head4 install_post_filter +=head3 install_pre_filter +=head3 install_evaluator +=head3 install_post_filter =cut @@ -770,18 +761,20 @@ sub install_correct_answer_post_filter { @{ $self->{correct_answer_post_filters} }; # return array of all post_filters } -=head4 withPreFilter +=head3 withPreFilter + +Usage: $answerHash->withPreFilter(filter[,options]); - Installs a prefilter (possibly with options), and returns the AnswerHash. This is so that you - can add a filter to a checker without having to save the checker in a variable, e.g., +Installs a prefilter (possibly with options), and returns the AnswerHash. This is so that you +can add a filter to a checker without having to save the checker in a variable, e.g., - ANS(Real(10)->cmp->withPreFilter(...)); + ANS(Real(10)->cmp->withPreFilter(...)); - or +or - ANS(num_cmp(10)->withPreFilter(...)); + ANS(num_cmp(10)->withPreFilter(...)); =cut @@ -791,18 +784,20 @@ sub withPreFilter { return $self; } -=head4 withPostFilter +=head3 withPostFilter + +Usage: $answerHash->withPostFilter(filter[,options]); - Installs a postfilter (possibly with options), and returns the AnswerHash. This is so that you - can add a filter to a checker without having to save the checker in a variable, e.g., +Installs a postfilter (possibly with options), and returns the AnswerHash. This is so that you +can add a filter to a checker without having to save the checker in a variable, e.g., - ANS(Real(10)->cmp->withPostFilter(...)); + ANS(Real(10)->cmp->withPostFilter(...)); - or +or - ANS(num_cmp(10)->withPostFilter(...)); + ANS(num_cmp(10)->withPostFilter(...)); =cut @@ -826,12 +821,13 @@ sub rh_ans { $self->{rh_ans}; } -=head1 Description: Filters +=head1 DESCRIPTION - Filters A filter is a subroutine which takes one AnswerHash as an input, followed by a hash of options. - Usage: filter($ans_hash, option1 =>value1, option2=> value2 ); +Usage: + filter($ans_hash, option1 =>value1, option2=> value2 ); The filter performs some operations on the input AnswerHash and returns an @@ -855,12 +851,9 @@ Setting the flag C<$rh_ans->{done} = 1> will skip the AnswerHash past the remaining post_filters. -=head3 Built in filters +=head2 blank_prefilter -=head4 blank_prefilter - - -=head4 blank_postfilter +=head2 blank_postfilter =cut @@ -904,5 +897,3 @@ sub blank_postfilter { } 1; -#package AnswerEvaluatorMaker; - diff --git a/lib/AnswerIO.pm b/lib/AnswerIO.pm index f9df86a1ee..569767a56b 100644 --- a/lib/AnswerIO.pm +++ b/lib/AnswerIO.pm @@ -1,9 +1,9 @@ =head1 NAME - AnswerIO.pm +AnswerIO.pm -=head1 SYNPOSIS +=head1 SYNOPSIS This is not really an object, but it gives us a place to IO used by answer macros. diff --git a/lib/Applet.pm b/lib/Applet.pm index c1d7bbaadc..234802b3c9 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -17,7 +17,7 @@ Applet.pl - Provides code for inserting GeogebraWebApplets into webwork problems -=head1 SYNPOSIS +=head1 SYNOPSIS ################################### # Create the applet object diff --git a/lib/ChoiceList.pm b/lib/ChoiceList.pm index 156532fe49..5955142a67 100644 --- a/lib/ChoiceList.pm +++ b/lib/ChoiceList.pm @@ -6,7 +6,7 @@ =head1 NAME - ChoiceList.pm -- super-class for all ChoiceList structures +ChoiceList.pm - super-class for all ChoiceList structures =head1 SYNOPSIS diff --git a/lib/Circle.pm b/lib/Circle.pm index 49eba47d2c..d5f9ba0634 100644 --- a/lib/Circle.pm +++ b/lib/Circle.pm @@ -1,14 +1,14 @@ =head1 NAME - Circle +Circle -=head1 SYNPOSIS +=head1 SYNOPSIS use Carp; - use GD; - use WWPlot; - use Fun; + use GD; + use WWPlot; + use Fun; =head1 DESCRIPTION @@ -17,29 +17,28 @@ This module defines a circle which can be inserted as a stamp in a graph (WWPlot =head2 Command: - $circle_object = new Circle( $center_pos_x, $center_pos_y, $radius, $border_color, $fill_color); - + $circle_object = new Circle( $center_pos_x, $center_pos_y, $radius, $border_color, $fill_color); =head2 Examples: - Here is the code used to define the subroutines open_circle - and closed_circle in PGgraphmacros.pl +Here is the code used to define the subroutines open_circle +and closed_circle in PGgraphmacros.pl - sub open_circle { - my ($cx,$cy,$color) = @_; - new Circle ($cx, $cy, 4,$color,'nearwhite'); - } + sub open_circle { + my ($cx,$cy,$color) = @_; + new Circle ($cx, $cy, 4,$color,'nearwhite'); + } - sub closed_circle { - my ($cx,$cy, $color) = @_; - $color = 'black' unless defined $color; - new Circle ($cx, $cy, 4,$color, $color); - } + sub closed_circle { + my ($cx,$cy, $color) = @_; + $color = 'black' unless defined $color; + new Circle ($cx, $cy, 4,$color, $color); + } - $circle_object2 = closed_circle( $x_position, $y_position, $color ); + $circle_object2 = closed_circle( $x_position, $y_position, $color ); - @circle_objects = $graph -> stamps($circle_object2); - # puts a filled dot at ($x_position, $y_position) on the graph -- using real world coordinates. + @circle_objects = $graph -> stamps($circle_object2); + # puts a filled dot at ($x_position, $y_position) on the graph -- using real world coordinates. =cut diff --git a/lib/Fraction.pm b/lib/Fraction.pm index 2e7ffc5a8e..faf86e0268 100644 --- a/lib/Fraction.pm +++ b/lib/Fraction.pm @@ -7,47 +7,46 @@ # numerator/denominator. # VS 7/20/2000 -=head3 Fraction +=head1 NAME - This object is designed to ease the use of fractions +Fraction - This object is designed to ease the use of fractions -=head4 Variables and Methods +=head1 VARIABLES - Variables + numerator # numerator of fraction + denominator # denominator of fraction - numerator #numerator of fraction - denominator #denominator of fraction +=head1 METHODS - Arithmetic Methods #these will all accept a scalar value or - #another fraction as an argument +Arithmetic Methods #these will all accept a scalar value or + #another fraction as an argument - plus #returns the sum of the fraction and argument - minus #returns fraction minus argument - subtractFrom #returns argument minus fraction - divBy #returns fraction divided by argument - divInto #returns argument divided by fraction - times #returns fraction times argument - compare #returns <, =, or > for the relation of fraction to argument + plus #returns the sum of the fraction and argument + minus #returns fraction minus argument + subtractFrom #returns argument minus fraction + divBy #returns fraction divided by argument + divInto #returns argument divided by fraction + times #returns fraction times argument + compare #returns <, =, or > for the relation of fraction to argument - pow #returns fraction raised to argument, a given integer power + pow #returns fraction raised to argument, a given integer power - Other methods +Other methods - reduce #reduces to lowest terms, and makes sure denominator is positive - scalar #returns the scalar value numerator/denominator - print #prints the fraction - print_mixed #prints the fractionas a mixed number - print_inline #prints the fraction like this 2/3 + reduce #reduces to lowest terms, and makes sure denominator is positive + scalar #returns the scalar value numerator/denominator + print #prints the fraction + print_mixed #prints the fractionas a mixed number + print_inline #prints the fraction like this 2/3 -=head4 Synopsis +=head1 SYNOPSIS - The fraction object stores two variables, numerator and denominator. The basic +The fraction object stores two variables, numerator and denominator. The basic arithmatic methods listed above can be performed on a fraction, and it can return its own scalar value for use with functions expecting a scalar (ie, sqrt($frac->scalar) ). - =cut package Fraction; diff --git a/lib/Fun.pm b/lib/Fun.pm index 67cf094d03..85e1100c9c 100644 --- a/lib/Fun.pm +++ b/lib/Fun.pm @@ -31,9 +31,9 @@ =head1 NAME - Fun +Fun -=head1 SYNPOSIS +=head1 SYNOPSIS use Carp; use GD; diff --git a/lib/Hermite.pm b/lib/Hermite.pm index b429793446..837565c253 100644 --- a/lib/Hermite.pm +++ b/lib/Hermite.pm @@ -4,38 +4,31 @@ use Carp; =head1 NAME - Hermite.pm +Hermite.pm -=head1 SYNPOSIS - - Usage: - $obj = new Hermit(\@x_values, \y_valuses \@yp_values); - - #get and set methods - $ra_x_values = $obj -> ra_x(\@x_values); - $ra_y_values = $obj -> ra_y; - $ra_yp_values = $obj -> ra_yp; - - $obj -> initialize; # calculates the approximation - - #get methods - $rf_function = $obj -> rf_f; - $rf_function_derivative = $obj -> rf_fp; - $rf_function_2nd_derivative = $obj -> rf_fpp; - - $rh_critical_points =$obj -> rh_critical_points - $rh_inflection_points =$obj -> rh_inflection_points +=head1 SYNOPSIS +Usage: + $obj = new Hermit(\@x_values, \y_valuses \@yp_values); + #get and set methods + $ra_x_values = $obj -> ra_x(\@x_values); + $ra_y_values = $obj -> ra_y; + $ra_yp_values = $obj -> ra_yp; + $obj -> initialize; # calculates the approximation + #get methods + $rf_function = $obj -> rf_f; + $rf_function_derivative = $obj -> rf_fp; + $rf_function_2nd_derivative = $obj -> rf_fpp; + $rh_critical_points =$obj -> rh_critical_points + $rh_inflection_points =$obj -> rh_inflection_points =head1 DESCRIPTION This module defines an object containing a Hermite spline approximation to a function. - The approximation -consists of a piecewise cubic polynomial which agrees with the original -function and its first derivative at -the node points. +The approximation consists of a piecewise cubic polynomial which agrees with the original +function and its first derivative at the node points. This is useful for creating on the fly graphics. Care must be taken to use a small number of points spaced reasonably far apart, preferably diff --git a/lib/Label.pm b/lib/Label.pm index 52f9b77071..e987335aa0 100644 --- a/lib/Label.pm +++ b/lib/Label.pm @@ -1,10 +1,10 @@ =head1 NAME - Label +Label -=head1 SYNPOSIS +=head1 SYNOPSIS use Carp; use GD; @@ -19,8 +19,8 @@ This module defines labels for the graph objects (WWPlot). =head2 Usage - $label1 = new Label($x_value, $y_value, $label_string, $label_color, @options) - $options is an array with (*'d defaults) + $label1 = new Label($x_value, $y_value, $label_string, $label_color, @options) + $options is an array with (*'d defaults) - one of 'left'*, 'center', 'right' (horizontal alignment) - one of 'bottom', 'center', 'top'* (verical alignment) - one of 'horizontal'*, 'vertical' (orientation) diff --git a/lib/List.pm b/lib/List.pm index 4c49993466..e7059dc741 100644 --- a/lib/List.pm +++ b/lib/List.pm @@ -6,7 +6,7 @@ =head1 NAME - List.pm -- super-class for all list structures +List.pm - super-class for all list structures =head1 SYNOPSIS diff --git a/lib/Matrix.pm b/lib/Matrix.pm index 05b5afaa62..eec59c5984 100644 --- a/lib/Matrix.pm +++ b/lib/Matrix.pm @@ -11,14 +11,7 @@ subroutines in this file are still used behind the scenes by Value::Matrix to perform calculations, such as decompose_LR(). -=head1 DESCRIPTION - - - -=head1 SYNOPSIS - - -=head3 Matrix Methods: +=head1 METHODS =cut @@ -32,9 +25,9 @@ use Carp; $Matrix::DEFAULT_FORMAT = '% #-19.12E '; # allows specification of the format -=head4 Method $matrix->_stringify() +=head2 _stringify - -- overrides MatrixReal1 display mode +overrides MatrixReal1 display mode =cut @@ -69,10 +62,10 @@ sub _stringify { return ($s); } -=head3 Accessor functions +=head1 FUNCTIONS - (these are deprecated for direct use. Use the covering Methods - provided by MathObject Matrices instead.) +(these are deprecated for direct use. Use the covering Methods +provided by MathObject Matrices instead.) L($matrix) - return matrix L of the LR decomposition R($matrix) - return matrix R of the LR decomposition @@ -136,7 +129,7 @@ sub PR { # use this permuation on the right PL*L*R*PR =M } -=head4 Method $matrix->rh_options +=head2 rh_options Meant for internal use when dealing with MatrixReal1 @@ -149,12 +142,12 @@ sub rh_options { $self->[$MatrixReal1::OPTION_ENTRY]; # provides a reference to the options hash MEG } -=head4 Method $matrix->trace +=head2 trace - Returns: scalar which is the trace of the matrix. +Returns: scalar which is the trace of the matrix. - Used by MathObject Matrices for calculating the trace. - Deprecated for direct use in PG questions. +Used by MathObject Matrices for calculating the trace. +Deprecated for direct use in PG questions. =cut @@ -170,9 +163,13 @@ sub trace { $sum; } -=head4 Method $new_matrix = $matrix->new_from_array_ref ([[a,b,c],[d,e,f]]) +=head2 new_from_array_ref + +Usage: + + $new_matrix = $matrix->new_from_array_ref ([[a,b,c],[d,e,f]]) - Deprecated in favor of using creation tools for MathObject Matrices +Deprecated in favor of using creation tools for MathObject Matrices =cut @@ -186,7 +183,7 @@ sub new_from_array_ref { # this will build a matrix or a row vector from [a, $matrix; } -=head4 Method $matrix->array_ref +=head2 array_ref Converts Matrix from an ARRAY to an ARRAY reference. @@ -197,7 +194,7 @@ sub array_ref { $this->[0]; } -=head4 Method $matrix->list +=head2 list Converts a Matrix column vector to an ARRAY (list). @@ -214,9 +211,9 @@ sub list { # this is used only for column vectors @list; } -=head4 Method $matrix->new_row_matrix +=head2 new_row_matrix [DEPRECATED] - Deprecated -- there are better tools for MathObject Matrices. +Deprecated -- there are better tools for MathObject Matrices. Create a row 1 by n matrix from a list. This subroutine appears to be broken @@ -236,10 +233,10 @@ sub new_row_matrix { # this builds a row vector from an array $matrix; } -=head4 Method $matrix->proj +=head2 proj - Provides behind the scenes calculations for MathObject Matrix->proj - Deprecated for direct use in favor of methods of MathObject matrix +Provides behind the scenes calculations for MathObject Matrix->proj +Deprecated for direct use in favor of methods of MathObject matrix =cut @@ -249,10 +246,10 @@ sub proj { $self * $self->proj_coeff($vec); } -=head4 Method $matrix->proj_coeff +=head2 proj_coeff - Provides behind the scenes calculations for MathObject Matrix->proj_coeff - Deprecated for direct use in favor of methods of MathObject matrix +Provides behind the scenes calculations for MathObject Matrix->proj_coeff +Deprecated for direct use in favor of methods of MathObject matrix =cut @@ -271,9 +268,9 @@ sub proj_coeff { $x_vector; } -=head4 Method $matrix->new_column_matrix +=head2 new_column_matrix - Create column matrix from an ARRAY reference (list reference) +Create column matrix from an ARRAY reference (list reference) =cut @@ -290,13 +287,13 @@ sub new_column_matrix { $matrix; } -=head4 Method $matrix->new_from_col_vecs +=head2 new_from_col_vecs - This method takes an array of column vectors, or an array of arrays, - and converts them to a matrix where each column is one of the previous - vectors. +This method takes an array of column vectors, or an array of arrays, +and converts them to a matrix where each column is one of the previous +vectors. - Deprecated: The tools for creating MathObjects Matrices are simpler +Deprecated: The tools for creating MathObjects Matrices are simpler =cut @@ -334,13 +331,13 @@ sub new_from_col_vecs { # Modifications to MatrixReal.pm which allow use of complex entries ###################################################################### -=head3 Overrides of MatrixReal which allow use of complex entries +=head1 Overrides of MatrixReal which allow use of complex entries =cut -=head4 Function: cp() +=head2 cp - Provides ability to use complex numbers. +Provides ability to use complex numbers. =cut @@ -350,7 +347,7 @@ sub cp { # MEG makes new copies of complex number Complex1::cplx($z->Re, $z->Im); } -=head4 Method $matrix->copy +=head2 copy =cut @@ -389,7 +386,7 @@ sub copy { # MEG added 6/25/03 to accomodate complex entries -=head4 Method $matrix->conj +=head2 conj =cut @@ -399,7 +396,7 @@ sub conj { $elem; } -=head4 Method $matrix->transpose +=head2 transpose =cut @@ -439,10 +436,10 @@ sub transpose { $matrix1; } -=head4 Method $matrix->decompose_LR +=head2 decompose_LR - Used by MathObjects Matrix for LR decomposition - Deprecated for direct use in PG problems. +Used by MathObjects Matrix for LR decomposition +Deprecated for direct use in PG problems. =cut diff --git a/lib/PGloadfiles.pm b/lib/PGloadfiles.pm index 9cf76e944e..1962b8fe9b 100644 --- a/lib/PGloadfiles.pm +++ b/lib/PGloadfiles.pm @@ -13,9 +13,15 @@ # Artistic License for more details. ################################################################################ -=head2 loadMacros +=head1 NAME - loadMacros(@macroFiles) +loadMacros - load macros within a PG problem. + +=head1 DESCRIPTION + +Usage: + + loadMacros(@macroFiles) loadMacros takes a list of file names and evaluates the contents of each file. This is used to load macros which define and augment the PG language. The macro @@ -31,7 +37,7 @@ if $macrosPath contains the path to a problem library macros directory which contains a PG.pl file, this file will be loaded and allowed to engage in privileged behavior. -=head3 Overloading macro files +=head2 Overloading macro files An individual course can modify the PG language, for that course only, by duplicating one of the macro files in the system-wide macros directory and @@ -42,7 +48,7 @@ system-wide macros directory. The new file in the course macros directory can by modified by adding macros or modifying existing macros. -=head3 Modifying existing macros +=head2 Modifying existing macros I diff --git a/lib/Parser/Legacy/PGcomplexmacros.pl b/lib/Parser/Legacy/PGcomplexmacros.pl index 696880e563..153be18195 100644 --- a/lib/Parser/Legacy/PGcomplexmacros.pl +++ b/lib/Parser/Legacy/PGcomplexmacros.pl @@ -10,7 +10,7 @@ =head1 NAME Macros for complex numbers for the PG language -=head1 SYNPOSIS +=head1 SYNOPSIS diff --git a/lib/Regression.pm b/lib/Regression.pm index 3cceeccddd..e61f248a35 100644 --- a/lib/Regression.pm +++ b/lib/Regression.pm @@ -14,7 +14,7 @@ use constant DEBUGGING => 0; =head1 NAME - Regression.pm - weighted linear regression package (line+plane fitting) +Regression.pm - weighted linear regression package (line+plane fitting) =head1 DESCRIPTION diff --git a/lib/Select.pm b/lib/Select.pm index 14bd2ed542..51f23a6dbe 100644 --- a/lib/Select.pm +++ b/lib/Select.pm @@ -5,9 +5,9 @@ =head1 NAME - Select.pm -- sub-class of ChoiceList that implements a select list. +Select.pm - sub-class of ChoiceList that implements a select list. - All items accessed by $out = $sl -> item( $in ); +All items accessed by $out = $sl -> item( $in ); =head1 SYNOPSIS diff --git a/lib/Value.pm b/lib/Value.pm index 8c23f9ead0..72c00fcd7f 100644 --- a/lib/Value.pm +++ b/lib/Value.pm @@ -18,44 +18,40 @@ like equality are "fuzzy", meaning that two items are equal when they are "close =cut -=head3 Value context - - ############################################################# - # - # Initialize the context-- flags set - # - The following are list objects, meaning that they involve delimiters (parentheses) - of some type. They get overridden in lib/Parser/Context.pm - - lists => { - 'Point' => {open => '(', close => ')'}, - 'Vector' => {open => '<', close => '>'}, - 'Matrix' => {open => '[', close => ']'}, - 'List' => {open => '(', close => ')'}, - 'Set' => {open => '{', close => '}'}, - }, - - The following context flags are set: - - # For vectors: - # +=head1 Value context + +The following are list objects, meaning that they involve delimiters (parentheses) +of some type. They get overridden in lib/Parser/Context.pm + + lists => { + 'Point' => {open => '(', close => ')'}, + 'Vector' => {open => '<', close => '>'}, + 'Matrix' => {open => '[', close => ']'}, + 'List' => {open => '(', close => ')'}, + 'Set' => {open => '{', close => '}'}, + }; + +The following context flags are set: + +For vectors: + ijk => 0, # print vectors as <...> - # - # For strings: - # + +For strings: + allowEmptyStrings => 1, infiniteWord => 'infinity', - # - # For intervals and unions: - # + +For intervals and unions: + ignoreEndpointTypes => 0, reduceSets => 1, reduceSetsForComparison => 1, reduceUnions => 1, reduceUnionsForComparison => 1, - # - # For fuzzy reals: - # + + For fuzzy reals: + useFuzzyReals => 1, tolerance => 1E-4, tolType => 'relative', @@ -63,9 +59,9 @@ like equality are "fuzzy", meaning that two items are equal when they are "close zeroLevelTol => 1E-12, tolTruncation => 1, tolExtraDigits => 1, - # - # For Formulas: - # + +For Formulas: + limits => [-2,2], num_points => 5, granularity => 1000, @@ -73,8 +69,6 @@ like equality are "fuzzy", meaning that two items are equal when they are "close max_adapt => 1E8, checkUndefinedPoints => 0, max_undefined => undef, - }, - =cut @@ -135,27 +129,25 @@ BEGIN { } -=head3 Implemented MathObject types and their precedence - - # - # Precedence of the various types - # (They will be promoted upward automatically when needed) - # - - 'Number' => 0, - 'Real' => 1, - 'Infinity' => 2, - 'Complex' => 3, - 'Point' => 4, - 'Vector' => 5, - 'Matrix' => 6, - 'List' => 7, - 'Interval' => 8, - 'Set' => 9, - 'Union' => 10, - 'String' => 11, - 'Formula' => 12, - 'special' => 20, +=head1 MathObject types and their precedence + +Precedence of the various types +(They will be promoted upward automatically when needed) + + 'Number' => 0, + 'Real' => 1, + 'Infinity' => 2, + 'Complex' => 3, + 'Point' => 4, + 'Vector' => 5, + 'Matrix' => 6, + 'List' => 7, + 'Interval' => 8, + 'Set' => 9, + 'Union' => 10, + 'String' => 11, + 'Formula' => 12, + 'special' => 20, =cut @@ -176,9 +168,8 @@ $$context->{precedence} = { 'special' => 20, }; -# # Binding of perl operator to class method -# + $$context->{method} = { '+' => 'add', '-' => 'sub', @@ -198,9 +189,8 @@ $$context->{pattern}{-infinity} = '-inf(?:inity)?'; push(@{ $$context->{data}{values} }, 'method', 'precedence'); -# # Copy an item and its data -# + sub copy { my $self = shift; my $copy = { %{$self} }; @@ -209,18 +199,17 @@ sub copy { return bless $copy, ref($self); } -=head3 getFlag +=head2 getFlag -# -# Get the value of a flag from the object itself, or from the -# equation that created the object (if any), or from the AnswerHash -# for the object (if it is being used as the source for an answer -# checker), or from the object's context, or from the current -# context, or use the given default, whichever is found first. -# +Get the value of a flag from the object itself, or from the +equation that created the object (if any), or from the AnswerHash +for the object (if it is being used as the source for an answer +checker), or from the object's context, or from the current +context, or use the given default, whichever is found first. - Usage: $mathObj->getFlag("showTypeWarnings"); - $mathObj->getFlag("showTypeWarnings",1); # default is second parameter +Usage: + $mathObj->getFlag("showTypeWarnings"); + $mathObj->getFlag("showTypeWarnings",1); # default is second parameter =cut @@ -242,9 +231,8 @@ sub getFlag { return shift; } -# # Get or set the context of an object -# + sub context { my $self = shift; my $context = shift; @@ -260,18 +248,14 @@ sub context { return $$Value::context; } -# # Set context but return object -# -sub inContext { my $self = shift; $self->context(@_); $self } -############################################################# +sub inContext { my $self = shift; $self->context(@_); $self } -# # The address of a Value object (actually ANY perl value). # Use this to compare two objects to see of they are # the same object (avoids automatic stringification). -# + sub address { Scalar::Util::refaddr(shift) } sub isBlessed { (Scalar::Util::blessed(shift) // '') ne "" } @@ -290,9 +274,16 @@ sub isHash { } -# example: return Boolean: Value->subclassed($self,"classMatch") -# if $self has the method 'classMath' and 'Value' has the method 'classMatch' -# and the reference to these methods don't agree then the method 'classMatch' has been subclassed. +=head2 subclassed + +Usage: + Value->subclassed($self,"classMatch") + +if $self has the method 'classMath' and 'Value' has the method 'classMatch' +and the reference to these methods don't agree then the method 'classMatch' has been subclassed. + +=cut + sub subclassed { my $self = shift; my $obj = shift; @@ -354,14 +345,17 @@ sub canBeInUnion { && $close =~ m/^[\)\]]$/; } -###################################################################### +=head2 Package + +Usage: + + Value->Package(name[,noerror]]) + +Returns the package name for the specificied Value object class +(as specified by the context's {value} hash, or "Value::name"). + +=cut -# -# Value->Package(name[,noerror]]) -# -# Returns the package name for the specificied Value object class -# (as specified by the context's {value} hash, or "Value::name"). -# sub Package { (shift)->context->Package(@_) } # Check if the object class matches one of a list of classes @@ -386,14 +380,13 @@ sub classMatch { return 0; } -=head3 makeValue +=head2 makeValue - Usage: Value::makeValue(45); +Usage: - Will create a Real mathObject. - # - # Convert non-Value objects to Values, if possible - # + Value::makeValue(45); + +This will create a Real mathObject and convert non-Value objects to Values, if possible =cut @@ -425,16 +418,13 @@ sub makeValue { return $x; } -=head3 showClass +=head2 showClass - Usage: TEXT( $mathObj -> showClass() ); +Usage: - Will print the class of the MathObject + $mathObj->showClass(); - # - # Get a printable version of the class of an object - # (used primarily in error messages) - # +This returns a printable version of the class of an object (used primarily in error messages) =cut @@ -455,17 +445,15 @@ sub showClass { return 'a ' . $class; } -=head3 showType +=head2 showType - Usage: TEXT( $mathObj -> showType() ); +Usage: - Will print the class of the MathObject + $mathObj->showType(); - # - # Get a printable version of the type of an object - # (the class and type are not the same. For example - # a Formula-class object can be of type Number) - # +This will return a printable version of the type of an object +(the class and type are not the same. For example +a Formula-class object can be of type Number) =cut @@ -487,9 +475,12 @@ sub showType { return 'a ' . $type; } -# -# Return a string describing a value's type -# +=head2 getType + +Return a string describing a value's type + +=cut + sub getType { my $equation = shift; my $value = shift; @@ -522,10 +513,12 @@ sub getType { return 'unknown'; } -# -# Get a string describing a value's type, -# and convert the value to a Value object (if needed) -# +=head2 getValueType + +Get a string describing a value's type, and convert the value to a Value object (if needed) + +=cut + sub getValueType { my $equation = shift; my $value = shift; @@ -545,9 +538,12 @@ sub getValueType { return ($value, $type); } -# -# Convert a list of values to a list of formulas (called by Parser::Value) -# +=head2 toFormula + +Convert a list of values to a list of formulas (called by Parser::Value) + +=cut + sub toFormula { my $formula = shift; my $processed = 0; @@ -565,11 +561,14 @@ sub toFormula { return (@f); } -# -# Convert a list of values (and open and close parens) -# to a formula whose type is the list type associated with -# the parens. -# +=head2 formula + +Convert a list of values (and open and close parens) +to a formula whose type is the list type associated with +the parens. + +=cut + sub formula { my $self = shift; my $values = shift; @@ -587,11 +586,14 @@ sub formula { return $formula; } -# -# A shortcut for new() that creates an instance of the object, -# but doesn't do the error checking. We assume the data are already -# known to be good. -# +=head2 make + +A shortcut for C that creates an instance of the object, +but doesn't do the error checking. We assume the data are already +known to be good. + +=cut + sub make { my $self = shift; my $class = ref($self) || $self; @@ -599,19 +601,25 @@ sub make { bless { $self->hashNoInherit, data => [@_], context => $context }, $class; } -# -# Easy method for setting parameters of an object -# (returns a copy with the new values set, but the copy -# is not a deep copy.) -# +=head2 with + +Easy method for setting parameters of an object +(returns a copy with the new values set, but the copy +is not a deep copy.) + +=cut + sub with { my $self = shift; bless { %{$self}, @_ }, ref($self); } -# -# Return a copy with the specified fields removed -# +=head2 without + +Return a copy with the specified fields removed + +=cut + sub without { my $self = shift; $self = bless { %{$self} }, ref($self); @@ -619,11 +627,12 @@ sub without { return $self; } -###################################################################### +=head2 hash + +Return the hash data as an array of key=>value pairs + +=cut -# -# Return the hash data as an array of key=>value pairs -# sub hash { my $self = shift; return %$self if isHash($self); @@ -637,11 +646,14 @@ sub hashNoInherit { return %hash; } -# -# Copy attributes that are not already in the current object -# from the given objects. (Used by binary operators to make sure -# the result inherits the values from the two terms.) -# +=head2 inherit + +Copy attributes that are not already in the current object +from the given objects. (Used by binary operators to make sure +the result inherits the values from the two terms.) + +=cut + sub inherit { my $self = shift; my %copy = (map {%$_} @_, $self); # copy values from given objects @@ -650,22 +662,25 @@ sub inherit { return $self; } -# -# The list of fields NOT to inherit. -# Use the default list plus any specified explicitly in the object itself. -# Subclasses can override and return additional fields, if necessary. -# +=head2 noinherit + +The list of fields NOT to inherit. +Use the default list plus any specified explicitly in the object itself. +Subclasses can override and return additional fields, if necessary. + +=cut + sub noinherit { my $self = shift; ("correct_ans", "correct_ans_latex_string", "original_formula", "equation", @{ $self->{noinherit} || [] }); } -###################################################################### +=head2 Type + +Return a type structure for the item (includes name, length of vectors, and so on) + +=cut -# -# Return a type structure for the item -# (includes name, length of vectors, and so on) -# sub Type { my $name = shift; my $length = shift; @@ -723,9 +738,12 @@ sub class { return $class; } -# -# Get an element from a point, vector, matrix, or list -# +=head2 extract + +Get an element from a point, vector, matrix, or list + +=cut + sub extract { my $M = shift; my $i; @@ -745,8 +763,6 @@ sub extract { return $M; } -###################################################################### - use overload '+' => '_add', '-' => '_sub', @@ -770,9 +786,12 @@ use overload 'nomethod' => 'nomethod', '""' => 'stringify'; -# -# Promote an operand to the same precedence as the current object -# +=head2 promotePrecedence + +Promote an operand to the same precedence as the current object + +=cut + sub promotePrecedence { my $self = shift; my $other = shift; @@ -794,19 +813,24 @@ sub promote { return $self->new($context, $x, @_); } -# -# Return the operators in the correct order -# +=head2 checkOpOrder + +Return the operators in the correct order + +=cut + sub checkOpOrder { my ($l, $r, $flag) = @_; if ($flag) { return ($l, $r, $l, $r) } else { return ($l, $l, $r, $r) } } -# -# Return the operators in the correct order, and promote the -# other value, if needed. -# +=head2 checkOpOrderWithPromote + +Return the operators in the correct order, and promote the other value, if needed. + +=cut + sub checkOpOrderWithPromote { my ($l, $r, $flag) = @_; $r = $l->promote($r); @@ -814,10 +838,13 @@ sub checkOpOrderWithPromote { else { return ($l, $l, $r, $r) } } -# -# Handle a binary operator, promoting the object types -# as needed, and then calling the main method -# +=head2 binOp + +Handle a binary operator, promoting the object types +as needed, and then calling the main method + +=cut + sub binOp { my ($l, $r, $flag, $call) = @_; if ($l->promotePrecedence($r)) { return $r->$call($l, !$flag) } @@ -928,18 +955,25 @@ sub dot { # sub pdot { shift->stringify } -# -# Compare the values of the objects +=head2 compare + +Compare the values of the objects + +=cut + # (list classes should replace this) -# + sub compare { my ($self, $l, $r) = Value::checkOpOrder(@_); return $l->value <=> $r->value; } -# -# Compare the values as strings -# +=head2 compare_string + +Compare the values as strings + +=cut + sub compare_string { my ($l, $r, $flag) = @_; $l = $l->string; @@ -949,9 +983,12 @@ sub compare_string { return $l cmp $r; } -# -# Copy flags from the parent object to its children (recursively). -# +=head2 transferFlags + +Copy flags from the parent object to its children (recursively). + +=cut + sub transferFlags { my $self = shift; foreach my $flag (@_) { @@ -971,24 +1008,15 @@ sub transferTolerances { $other->transferFlags("tolerance", "tolType", "zeroLevel", "zeroLevelTol") if Value::isValue($other); } -=head3 output methods for MathObjects - - # - # Generate the various output formats - # (can be replaced by sub-classes) - # +=head1 MathObjects output methods -=cut +=head2 stringify -=head4 stringify +Usage: - Usage: TEXT($mathObj); or TEXT( $mathObj->stringify() ) ; + $mathObj->stringify(); - Produces text string or TeX output depending on context - Context()->texStrings; - Context()->normalStrings; - - called automatically when object is called in a string context. +Produces text string or TeX output depending on context. =cut @@ -998,12 +1026,13 @@ sub stringify { return $self->string; } -=head4 ->string +=head2 string + +Usage: - Usage: $mathObj->string() + $mathObj->string() - ---produce a string representation of the object - (as opposed to stringify, which can produce TeX or string versions) +produce a string representation of the object (as opposed to stringify, which can produce TeX or string versions) =cut @@ -1038,11 +1067,13 @@ sub TO_JSON { return shift->string; } -=head4 ->TeX +=head2 TeX + +Usage: - Usage: $mathObj->TeX() + $mathObj->TeX() - ---produce TeX prepresentation of the object +produce TeX prepresentation of the object =cut @@ -1118,16 +1149,19 @@ sub ijk { Value::Error("Can't use method 'ijk' with objects of type '%s'", (shift)->class); } -=head3 Error +=head2 Error + +Usage: + + Value->Error("We're sorry..."); - Usage: Value->Error("We're sorry..."); - or $mathObject->Error("We're still sorry..."); +OR - # - # Report an error and die. This can be used within custom answer checkers - # to report errors during the check, or when sub-classing a MathObject to - # report error conditions. - # + $mathObject->Error("We're still sorry..."); + +Report an error and die. This can be used within custom answer checkers +to report errors during the check, or when sub-classing a MathObject to +report error conditions. =cut @@ -1142,9 +1176,12 @@ sub Error { die $message . getCaller(); } -# -# Try to locate the line and file where the error occurred -# +=head2 getCaller + +Try to locate the line and file where the error occurred + +=cut + sub getCaller { my $frame = 2; while (my ($pkg, $file, $line, $subname) = caller($frame++)) { @@ -1188,10 +1225,6 @@ END { use Value::WeBWorK; # stuff specific to WeBWorK } -########################################################################### - our $installed = 1; -########################################################################### - 1; diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index ec6a6b9b13..fb4dacb06c 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -1,42 +1,45 @@ +=head1 NAME + +AnswerChecker - Implements the compare method for Value objects. + =head1 DESCRIPTION - ############################################################# - # - # Implements the ->cmp method for Value objects. - # Otherwise known as MathObjects. This produces - # an answer checker appropriate for the type of object. - # Additional options can be passed to the cmp method to - # modify its action. - # - # Usage: $num = Real(3.45); # Real can be replaced by any other MathObject - # ANS($num->cmp(compareOptionName => compareOptionValue, ... )) - # - # The individual Value packages are modified below to add the - # needed methods. - # - ############################################################# +Implements the ->cmp method for Value objects. +Otherwise known as MathObjects. This produces +an answer checker appropriate for the type of object. +Additional options can be passed to the cmp method to +modify its action. + +Usage: + + $num = Real(3.45); # Real can be replaced by any other MathObject + ANS($num->cmp(compareOptionName => compareOptionValue, ... )) + +The individual Value packages are modified below to add the +needed methods. + +=head1 METHODS =cut package Value; use PGcore; -# # Context can add default values to the answer checkers by class; -# + $Value::defaultContext->{cmpDefaults} = {}; -=head4 $mathObject->cmp_defaults() +=head2 cmp_defaults -# Internal use. -# Set default flags for the answer checker in this object -# showTypeWarnings => 1 -# showEqualErrors => 1 -# ignoreStrings => 1 -# studentsMustReduceUnions => 1 -# showUnionReduceWarnings => 1 -# +Internal use. +Set default flags for the answer checker in this object + + showTypeWarnings => 1 + showEqualErrors => 1 + ignoreStrings => 1 + studentsMustReduceUnions => 1 + showUnionReduceWarnings => 1 =cut @@ -48,9 +51,7 @@ sub cmp_defaults { ( showUnionReduceWarnings => 1, ) } -# # Special Context flags to be set for the student answer -# sub cmp_contextFlags { my $self = shift; @@ -80,9 +81,7 @@ sub cmp_contextFlags { ); } -# # Create an answer checker for the given type of object -# sub cmp { my $self = shift; @@ -116,17 +115,15 @@ sub correct_ans { preformat(shift->string) } sub correct_ans_latex { shift->TeX } sub cmp_diagnostics { } -# # Parse the student answer and compute its value, # produce the preview strings, and then compare the # student and professor's answers for equality. -# + sub cmp_parse { my $self = shift; my $ans = shift; - # + # Do some setup - # my $context = $ans->{correct_value}{context} || $current; Parser::Context->current(undef, $context); # change to correct answser's context my $flags = contextSet($context, $self->cmp_contextFlags($ans)); # save old context flags @@ -138,10 +135,8 @@ sub cmp_parse { $context->clearError(); $context->{answerHash} = $ans; # values here can override context flags - # # Parse and evaluate the student answer - # - $ans->score(0); # assume failure + $ans->score(0); # assume failure $context->flags->set( parseMathQuill => $context->flag("useMathQuill") && (!defined $context->{answerHash}{mathQuillOpts} || $context->{answerHash}{mathQuillOpts} !~ /^\s*disabled\s*$/i) @@ -151,19 +146,18 @@ sub cmp_parse { if defined($ans->{student_formula}) && $ans->{student_formula}->isConstant; $context->flags->set(parseMathQuill => 0); - # # If it parsed OK, save the output forms and check if it is correct # otherwise report an error - # + if (defined $ans->{student_value}) { $ans->{student_value} = $self->Package("Formula")->new($ans->{student_value}) unless Value::isValue($ans->{student_value}); $ans->{student_value}{isStudent} = 1; $ans->{preview_latex_string} = $ans->{student_formula}->TeX; $ans->{preview_text_string} = preformat($ans->{student_formula}->string); - # + # Get the string for the student answer - # + for ($self->getFlag('formatStudentAnswer')) { /evaluated/i and do { $ans->{student_ans} = preformat($ans->{student_value}->string); last }; /parsed/i and do { $ans->{student_ans} = $ans->{preview_text_string}; last }; @@ -191,10 +185,9 @@ sub cmp_parse { return $ans; } -# # Check if the object has an answer array and collect the results # Build the combined student answer and set the preview values -# + sub cmp_collect { my $self = shift; my $ans = shift; @@ -831,17 +824,22 @@ sub getPG { eval('package main; ' . shift); # faster } -############################################################# -############################################################# +=head2 Compare Details for Default MathObjects =head3 Value::Real - Usage ANS( Real(3.56)->cmp() ) - Compares response to a real value using 'fuzzy' comparison - compareOptions and default values: - showTypeWarnings => 1, - showEqualErrors => 1, - ignoreStrings => 1, +Options for C for C: + +Usage: + + ANS( Real(3.56)->cmp() ) + +Compares response to a real value using 'fuzzy' comparison +compareOptions and default values: + + showTypeWarnings => 1, + showEqualErrors => 1, + ignoreStrings => 1, =cut @@ -879,17 +877,23 @@ sub typeMatch { =head3 Value::String - Usage: $s = String("pole"); - ANS($s->cmp(typeMatch => Complex("4+i"))); - # compare to response 'pole', don't complain about complex number responses. +Options for C for C: + +Usage: - compareOptions and default values: - showTypeWarnings => 1, - showEqualErrors => 1, - ignoreStrings => 1, # don't complain about string-valued responses - typeMatch => 'Value::Real' + $s = String("pole"); + ANS($s->cmp(typeMatch => Complex("4+i"))); - Initial and final spaces are ignored when comparing strings. +compare to response 'pole', don't complain about complex number responses. + +compareOptions and default values: + + showTypeWarnings => 1, + showEqualErrors => 1, + ignoreStrings => 1, # don't complain about string-valued responses + typeMatch => 'Value::Real' + +Leading and trailing spaces are ignored when comparing strings. =cut @@ -959,17 +963,23 @@ sub cmp_preprocess { =head3 Value::Point - Usage: $pt = Point("(3,6)"); # preferred - or $pt = Point(3,6); - or $pt = Point([3,6]); - ANS($pt->cmp()); +Options for C for C: + +Usage: - compareOptions: - showTypeWarnings => 1, # warns if student response is of incorrect type - showEqualErrors => 1, - ignoreStrings => 1, - showDimensionHints => 1, # reports incorrect number of coordinates - showCoordinateHints =>1, # flags individual coordinates that are incorrect + $pt = Point("(3,6)"); # preferred + $pt = Point(3,6); + $pt = Point([3,6]); + + ANS($pt->cmp()); + +compareOptions and default values: + + showTypeWarnings => 1, # warns if student response is of incorrect type + showEqualErrors => 1, + ignoreStrings => 1, + showDimensionHints => 1, # reports incorrect number of coordinates + showCoordinateHints =>1, # flags individual coordinates that are incorrect =cut @@ -1034,26 +1044,29 @@ sub ans_array { my $self = shift; $self->ANS_MATRIX(0, '', @_) } sub named_ans_array { my $self = shift; $self->ANS_MATRIX(0, @_) } sub named_ans_array_extension { my $self = shift; $self->ANS_MATRIX(1, @_) } -############################################################# - =head3 Value::Vector - Usage: $vec = Vector("<3,6,7>"); - or $vec = Vector(3,6,7); - or $vec = Vector([3,6,7]); - ANS($vec->cmp()); +Options for C for C: - compareOptions: - showTypeWarnings => 1, # warns if student response is of incorrect type - showEqualErrors => 1, - ignoreStrings => 1, - showDimensionHints => 1, # reports incorrect number of coordinates - showCoordinateHints => 1, # flags individual coordinates which are incorrect - promotePoints => 0, # allow students to enter vectors as points (3,5,6) - parallel => 1, # response is correct if it is parallel to correct answer - sameDirection => 1, # response is correct if it has same orientation as correct answer - # (only has an effect when parallel => 1 is specified) +Usage: + $vec = Vector("<3,6,7>"); + $vec = Vector(3,6,7); + $vec = Vector([3,6,7]); + + ANS($vec->cmp()); + +compareOptions and default values: + + showTypeWarnings => 1, # warns if student response is of incorrect type + showEqualErrors => 1, + ignoreStrings => 1, + showDimensionHints => 1, # reports incorrect number of coordinates + showCoordinateHints => 1, # flags individual coordinates which are incorrect + promotePoints => 0, # allow students to enter vectors as points (3,5,6) + parallel => 1, # response is correct if it is parallel to correct answer + sameDirection => 1, # response is correct if it has same orientation as correct answer + # (only has an effect when parallel => 1 is specified) =cut @@ -1167,21 +1180,24 @@ sub ans_array { my $self = shift; $self->ANS_MATRIX(0, '', @_) } sub named_ans_array { my $self = shift; $self->ANS_MATRIX(0, @_) } sub named_ans_array_extension { my $self = shift; $self->ANS_MATRIX(1, @_) } -############################################################# - =head3 Value::Matrix - Usage $ma = Matrix([[3,6],[2,5]]) or $ma =Matrix([3,6],[2,5]) - ANS($ma->cmp()); +Options for C for C: + +Usage: + + $ma = Matrix([[3,6],[2,5]]) + $ma = Matrix([3,6],[2,5]) - compareOptions: + ANS($ma->cmp()); - showTypeWarnings => 1, # warns if student response is of incorrect type - showEqualErrors => 1, # reports messages that occur during element comparisons - ignoreStrings => 1, - showDimensionHints => 1, # reports incorrect number of coordinates - showCoordinateHints => 1, # flags individual coordinates which are incorrect +compareOptions and default values: + showTypeWarnings => 1, # warns if student response is of incorrect type + showEqualErrors => 1, # reports messages that occur during element comparisons + ignoreStrings => 1, + showDimensionHints => 1, # reports incorrect number of coordinates + showCoordinateHints => 1, # flags individual coordinates which are incorrect =cut @@ -1268,22 +1284,23 @@ sub ans_array { my $self = shift; $self->ANS_MATRIX(0, '', @_) } sub named_ans_array { my $self = shift; $self->ANS_MATRIX(0, @_) } sub named_ans_array_extension { my $self = shift; $self->ANS_MATRIX(1, @_) } -############################################################# - =head3 Value::Interval - Usage: $interval = Interval("(1,2]"); - or $interval = Interval('(',1,2,']'); - ANS($inteval->cmp); +Usage: - compareOptions and defaults: - showTypeWarnings => 1, - showEqualErrors => 1, - ignoreStrings => 1, - showEndpointHints => 1, # show hints about which end point values are correct - showEndTypeHints => 1, # show hints about endpoint types - requireParenMatch => 1, + $interval = Interval("(1,2]"); + $interval = Interval('(',1,2,']'); + ANS($inteval->cmp); + +compareOptions and default values: + + showTypeWarnings => 1, + showEqualErrors => 1, + ignoreStrings => 1, + showEndpointHints => 1, # show hints about which end point values are correct + showEndTypeHints => 1, # show hints about endpoint types + requireParenMatch => 1, =cut @@ -1339,18 +1356,22 @@ sub cmp_postprocess { $self->cmp_Error($ans, @errors); } -############################################################# - =head3 Value::Set - Usage: $set = Set(5,6,'a', 'b') - or $set = Set("{5, 6, a, b}") +Options for C for C: - The object is a finite set of real numbers. It can be used with Union and - Interval. +Usage: - Examples: Interval("(-inf,inf)") - Set(0) - Compute("R-{0}") # in Interval context: Context("Interval"); + $set = Set(5,6,'a', 'b') + $set = Set("{5, 6, a, b}") + +The object is a finite set of real numbers. It can be used with Union and +Interval. + +Examples: + + Interval("(-inf,inf)") - Set(0) + Compute("R-{0}") # in Interval context: Context("Interval"); =cut @@ -1403,14 +1424,13 @@ sub cmp_compare { $self->SUPER::cmp_compare($student, $ans, @_); } -############################################################# - =head3 Value::Union - Usage: $union = Union("[4,5] U [6,7]"); - or $union = Union(Interval("[4,5]",Interval("[6,7]")); - ANS($union->cmp()); +Usage: + $union = Union("[4,5] U [6,7]"); + $union = Union(Interval("[4,5]",Interval("[6,7]")); + ANS($union->cmp()); =cut @@ -1470,30 +1490,34 @@ sub cmp_compare { =head3 Value::List - Usage: $lst = List("1, x, <4,5,6>"); # list of a real, a formula and a vector. - or $lst = List(Real(1), Formula("x"), Vector(4,5,6)); - ANS($lst->cmp(showHints=>1)); - - compareOptions and defaults: - showTypeWarnings => 1, - showEqualErrors => 1, # show errors produced when checking equality of entries - ignoreStrings => 1, # don't show type warnings for strings - studentsMustReduceUnions => 1, - showUnionReduceWarnings => 1, - showHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 - showLengthHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 - showParenHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 - partialCredit => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 - ordered => 0, # 1 = must be in same order as correct answer - entry_type => undef, # determined from first entry - list_type => undef, # determined automatically - typeMatch => $element, # used for type checking the entries - firstElement => $element, - extra => undef, # used to check syntax of incorrect answers - requireParenMatch => 1, # student parens must match correct parens - removeParens => 1, # remove outermost parens, if any - implicitList => 1, # force single answers to be lists (even if they ARE lists) - +Options for C for C: + +Usage: + + $lst = List("1, x, <4,5,6>"); # list of a real, a formula and a vector. + $lst = List(Real(1), Formula("x"), Vector(4,5,6)); + ANS($lst->cmp(showHints=>1)); + +compareOptions and default values: + + showTypeWarnings => 1, + showEqualErrors => 1, # show errors produced when checking equality of entries + ignoreStrings => 1, # don't show type warnings for strings + studentsMustReduceUnions => 1, + showUnionReduceWarnings => 1, + showHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 + showLengthHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 + showParenHints => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 + partialCredit => undef, # automatically set to 1 if $showPartialCorrectAnswers == 1 + ordered => 0, # 1 = must be in same order as correct answer + entry_type => undef, # determined from first entry + list_type => undef, # determined automatically + typeMatch => $element, # used for type checking the entries + firstElement => $element, + extra => undef, # used to check syntax of incorrect answers + requireParenMatch => 1, # student parens must match correct parens + removeParens => 1, # remove outermost parens, if any + implicitList => 1, # force single answers to be lists (even if they ARE lists) =cut @@ -1826,16 +1850,15 @@ sub getOption { return $ans->{showPartialCorrectAnswers}; } -############################################################# - =head3 Value::Formula - Usage: $fun = Formula("x^2-x+1"); - $set = Formula("[-1, x) U (x, 2]"); +Usage: - A formula can have any of the other math object types as its range. - Union, List, Number (Complex or Real), + $fun = Formula("x^2-x+1"); + $set = Formula("[-1, x) U (x, 2]"); +A formula can have any of the other math object types as its range. +Union, List, Number (Complex or Real), =cut diff --git a/lib/VectorField.pm b/lib/VectorField.pm index 6d071229ce..fbec4e9de6 100644 --- a/lib/VectorField.pm +++ b/lib/VectorField.pm @@ -1,9 +1,9 @@ =head1 NAME - VectorField +VectorField - Produce a vector field plot. -=head1 SYNPOSIS +=head1 SYNOPSIS use Carp; use GD; diff --git a/lib/WWPlot.pm b/lib/WWPlot.pm index 6d5175c51a..c3c98a2197 100644 --- a/lib/WWPlot.pm +++ b/lib/WWPlot.pm @@ -8,9 +8,9 @@ =head1 NAME - WWPlot +WWPlot - methods to create plots. -=head1 SYNPOSIS +=head1 SYNOPSIS $graph = new WWPlot(400,400); # creates a graph 400 pixels by 400 pixels diff --git a/lib/WeBWorK/PG/ConvertToPGML.pm b/lib/WeBWorK/PG/ConvertToPGML.pm index 0dd38b4d36..56c1f0db1a 100644 --- a/lib/WeBWorK/PG/ConvertToPGML.pm +++ b/lib/WeBWorK/PG/ConvertToPGML.pm @@ -15,7 +15,7 @@ =head1 NAME -WeBWorK::PG::ConvertToPGML +WeBWorK::PG::ConvertToPGML - convert a file in original PG format to PGML =head1 DESCRIPTION @@ -24,19 +24,44 @@ Converts a pg file to PGML format. This script does a number of conversions: =over -=item Update the loadMacros call to include PGML.pl, eliminate MathObject.pl (since it is loaded by PGML.pl) -and adds PGcourse.pl to the end of the list. -=item Coverts BEGIN_TEXT/END_TEXT (and older versions of this), BEGIN_SOLUTION/END_SOLUTION, BEGIN_HINT/END_HINT -to their newer BEGIN_PGML blocks. -=item Convert math mode in these blocks to PGML style math mode. -=item Convert other styling (bold, italics) to PGML style. -=item Convert variables to the interpolated [$var] PGML style. -=item Convert some of the answer rules to newer PGML style. -=item Remove some outdated code. -=item A few other minor things. + +=item * + +Update the loadMacros call to include C, eliminate C (since it is loaded by C) +and adds C to the end of the list. + +=item * + +Coverts C/C (and older versions of this), C/C, +C/C to their newer C blocks. + +=item * + +Convert math mode in these blocks to PGML style math mode. + +=item * + +Convert other styling (bold, italics) to PGML style. + +=item * + +Convert variables to the interpolated C<[$var]> PGML style. + +=item * + +Convert some of the answer rules to newer PGML style. + +=item * + +Remove some outdated code. + +=item * + +A few other minor things. + =back -=head1 OPTIONS +=head1 FUNCTIONS =cut @@ -48,15 +73,16 @@ use warnings; our @EXPORT = qw(convertToPGML); -# This subroutine converts the file that is passed in as a multi-line string and -# assumed to be an older-style PG file with BEGIN_TEXT/END_TEXT, BEGIN_SOLUTION/END_SOLUTION, -# and BEGIN_HINT/END_HINT blocks. +=head2 convertToPGML -# * parses the loadMacros line(s) to include PGML.pl (and eliminate MathObjects.pl, which) -# is imported by PGML.pl. This also adds PGcourse.pl to the end of the list. +This subroutine converts the file that is passed in as a multi-line string and +assumed to be an older-style PG file with BEGIN_TEXT/END_TEXT, BEGIN_SOLUTION/END_SOLUTION, +and BEGIN_HINT/END_HINT blocks. -# input is a string containing the source of the pg file to be converted. -# returns a string that is the converted input string. +The input is expected to be a string containing the source of the pg file to be converted. +This returns a string that is the converted input string. + +=cut # This stores the answers inside of ANS and related functions. my @ans_list; diff --git a/lib/WeBWorK/PG/EquationCache.pm b/lib/WeBWorK/PG/EquationCache.pm index 47afa5d3a1..8f08126613 100644 --- a/lib/WeBWorK/PG/EquationCache.pm +++ b/lib/WeBWorK/PG/EquationCache.pm @@ -19,7 +19,7 @@ package WeBWorK::PG::EquationCache; WeBWorK::PG::EquationCache - create and cache images of TeX equations. -=head1 SYNPOSIS +=head1 SYNOPSIS my $cache = WeBWorK::PG::EquationCache->new(cacheDB => "/path/to/equationcache.db"); my $imageName = $cache->lookup('\[3x^2\]'); diff --git a/lib/WeBWorK/PG/ImageGenerator.pm b/lib/WeBWorK/PG/ImageGenerator.pm index de9ac15b8c..6f73c9d453 100644 --- a/lib/WeBWorK/PG/ImageGenerator.pm +++ b/lib/WeBWorK/PG/ImageGenerator.pm @@ -20,7 +20,7 @@ package WeBWorK::PG::ImageGenerator; WeBWorK::PG::ImageGenerator - create an object for holding bits of math for LaTeX, and then to process them all at once. -=head1 SYNPOSIS +=head1 SYNOPSIS my $image_generator = WeBWorK::PG::ImageGenerator->new( tempDir => $pg_envir->{directories}{tmp}, diff --git a/lib/WeBWorK/PG/RestrictedClosureClass.pm b/lib/WeBWorK/PG/RestrictedClosureClass.pm index 1798bdd2fc..f4ebdf1005 100644 --- a/lib/WeBWorK/PG/RestrictedClosureClass.pm +++ b/lib/WeBWorK/PG/RestrictedClosureClass.pm @@ -20,7 +20,7 @@ package WeBWorK::PG::RestrictedClosureClass; WeBWorK::PG::RestrictedClosureClass - Protect instance data and only allow calling of specified methods. -=head1 SYNPOSIS +=head1 SYNOPSIS package MyScaryClass; diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index 68f19a7076..7a0dcd6c24 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -19,7 +19,7 @@ package WeBWorK::PG::Translator; WeBWorK::PG::Translator - Evaluate PG code and evaluate answers safely -=head1 SYNPOSIS +=head1 SYNOPSIS my $pt = WeBWorK::PG::Translator->new; # create a translator $pt->environment(\%envir); # provide the environment variable for the problem From 73cbbb42092f592bf03764d2fb1434300be0bf1b Mon Sep 17 00:00:00 2001 From: wwadmin Date: Sun, 29 Jun 2025 12:16:01 -0400 Subject: [PATCH 003/111] Adds functionality to drag-n-drop problems to permit the repository bucket to be continually replenished. --- htdocs/js/DragNDrop/dragndrop.js | 40 +++++++++++++++++++++++++++----- lib/DragNDrop.pm | 2 ++ macros/math/draggableSubsets.pl | 29 +++++++++++++++++++++-- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/htdocs/js/DragNDrop/dragndrop.js b/htdocs/js/DragNDrop/dragndrop.js index e46faad6e7..7dc3d96642 100644 --- a/htdocs/js/DragNDrop/dragndrop.js +++ b/htdocs/js/DragNDrop/dragndrop.js @@ -9,6 +9,7 @@ this.answerName = el.dataset.answerName ?? ''; this.buckets = []; this.removeButtonText = el.dataset.removeButtonText ?? 'Remove'; + this.allowReusingItems = el.dataset.allowReusingItems; this.answerInput = el.parentElement.querySelector(`input[name="${this.answerName}"]`); if (!this.answerInput) { @@ -97,12 +98,39 @@ if (window.MathJax) { MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([this.el])); } - - this.sortable = Sortable.create(this.ddList, { - group: bucketPool.answerName, - animation: 150, - onEnd: () => this.bucketPool.updateAnswerInput() - }); + this.allowReusingItems = this.bucketPool.allowReusingItems; + + if (this.allowReusingItems) { + if (id === 0) { + this.sortable = Sortable.create(this.ddList, { + animation: 150, + sort: false, + onEnd: () => this.bucketPool.updateAnswerInput(), + group: { + name: bucketPool.answerName, + pull: 'clone', + put: false + } + }); + } else { + this.sortable = Sortable.create(this.ddList, { + animation: 150, + onEnd: () => this.bucketPool.updateAnswerInput(), + removeOnSpill: true, + group: { + name: bucketPool.answerName, + put: (to, _from, dragEl) => + !Array.from(to.el.children).some((child) => child.dataset.id === dragEl.dataset.id) + } + }); + } + } else { + this.sortable = Sortable.create(this.ddList, { + group: bucketPool.answerName, + animation: 150, + onEnd: () => this.bucketPool.updateAnswerInput() + }); + } } htmlBucket(label, removable, indices = []) { diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm index 18aa4f5dbd..f2513442c4 100644 --- a/lib/DragNDrop.pm +++ b/lib/DragNDrop.pm @@ -152,6 +152,7 @@ sub new { addButtonText => 'Add Bucket', removeButtonText => 'Remove', multicolsWidth => '300pt', + allowReusingItems => 0, %options, }, ref($self) || $self; @@ -161,6 +162,7 @@ sub HTML { my $self = shift; my $out = qq{
{allowReusingItems}; $out .= ' data-item-list="' . PGcore::encode_pg_and_html(encode_json($self->{itemList})) . '"'; $out .= ' data-default-state="' . PGcore::encode_pg_and_html(encode_json($self->{defaultBuckets})) . '"'; $out .= qq{ data-remove-button-text="$self->{removeButtonText}"}; diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 2eeeaa1fc0..7b530fb61f 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -83,6 +83,7 @@ =head1 USAGE DefaultSubsets => OrderedSubsets => 0 or 1 AllowNewBuckets => 0 or 1 + AllowReusingItems => 0 or 1 BucketLabelFormat => ResetButtonText => AddButtonText => @@ -148,6 +149,12 @@ =head1 EXAMPLE # The default value if not given is 1. AllowNewBuckets => 1, + # 0 is the conventional approach, by which the repository bucket is + # depleted if an item an item is dragged from that bucket. + # 1 means that items are replenished in the repository bucket once + # dragged. The default value if not given is 0. + AllowReusingItems => 0, + # If this option is defined then labels for buckets for which a specific # label is not provided will be created by replacing %s with the bucket # number to this prefix. These labels will also be used for buckets @@ -228,10 +235,15 @@ sub new { ResetButtonText => 'Reset', AddButtonText => 'Add Bucket', RemoveButtonText => 'Remove', + AllowReusingItems => 0, %options }, ref($invocant) || $invocant; + #If AllowReusingItem is set, bucket 0 should contain the full set of elements for grading + my $maxindex = scalar @{ $base->{set} } - 1; #Number of elements -1 + if ($base->{AllowReusingItems}) { $subsets->[0] = [ 0 .. $maxindex ]; } + $base->{order} = do { my @indices = 0 .. $#{ $base->{set} }; [ map { splice(@indices, main::random(0, $#indices), 1) } @indices ]; @@ -245,6 +257,7 @@ sub new { '(' => { close => ')', type => 'List', formList => 1, formMatrix => 0, removable => 0 }, '{' => { close => '}', type => 'Set', formList => 0, formMatrix => 0, removable => 0, emptyOK => 1 } ); + $context->lists->set( 'DraggableSubsets' => { class => 'Parser::List::List', @@ -304,7 +317,8 @@ sub ans_rule { bucketLabelFormat => $self->{BucketLabelFormat}, resetButtonText => $self->{ResetButtonText}, addButtonText => $self->{AddButtonText}, - removeButtonText => $self->{RemoveButtonText} + removeButtonText => $self->{RemoveButtonText}, + allowReusingItems => $self->{AllowReusingItems}, ); my $ans_rule = main::NAMED_HIDDEN_ANS_RULE($self->ANS_NAME); @@ -333,7 +347,18 @@ sub cmp_defaults { sub cmp { my ($self, %options) = @_; - return $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options); + my $nbuc = scalar @{ $self->{set} }; #number of buckets + if ($self->{AllowReusingItems}) { + $tmp = $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options)->withPostFilter(sub { + $ansHash = shift; + $ncor = $ansHash->{score} * $nbuc; #number of buckets correct + $ansHash->{score} = ($ncor - 1) / ($nbuc - 1); + return $ansHash; + }); + return $tmp; + } else { + return $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options); + } } sub TeX { From bf4b8704b1fdf189f084f8b8c28b0fa8a6e1f1e7 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 4 Jul 2025 07:45:14 -0500 Subject: [PATCH 004/111] Add an option to show the "universal set" in `draggableSubsets.pl` problems. This extends @sfiedle1's work in #1258, and implements the structural rework that I suggested in the conversation for that pull request (albeit slightly modified). There are two new options for the `draggableSubsets.pl` macro. They are `ShowUniversalSet` and `UniversalSetLabel`. If `ShowUniversalSet` is 1, then the set of all elements that are passed as the first argument for the `DraggableSubsets` method will be shown as a separate drag and drop bucket. This bucket is always above the other usual drag and drop buckets (both in HTML and TeX). The elements of the universal set can be dragged multiple times to the other buckets. Really this is a copy of the element, so all elements always remain in the universal set. The `UniversalSetLabel` option is a string that will be shown as the label of the universal set bucket. Note that this approach doesn't require any special handling for grading the answer regardless of if `$showPartialCorrectAnswers` is true or not, because the universal set bucket is not part of the answer in any case. It is not listed in any of the answer previews, and is not part of the grading in any way. It is purely a source of elements. I also added some validation of the answers and default subsets that are passed to the DraggableSubsets method. Previously invalid inputs would have caused rather unexpected things. Now they will be errors (specifically `Value::Error`s). In my original comment to #1258 I suggested a `ShowUniversalSet` option and a related `AllowReusableElements` option. The latter option was dropped because I realized it doesn't really make sense in the context of a universal set. I initially implemented it, but basically it made the "universal set" bucket really just a regular bucket just like all of the others, and it really wasn't a "universal set" anymore since it didn't always have all elements. I thought of a way that it could make sense in which it always has all elements, but only one copy was allowed to be dragged to the other buckets. So only one duplicate, no reuse after that. That would take some effort to implement (mostly in the javascript code), but could be done. I don't know that this would really be that useful though, so I didn't implement it for now. A MWE for testing this is as follows: ```perl DOCUMENT(); loadMacros(qw{PGstandard.pl PGML.pl draggableSubsets.pl PGcourse.pl}); $draggable = DraggableSubsets( [ 'orange', 'blue', 'apple' ], [ [ 0, 1 ], [ 0, 2 ] ], DefaultSubsets => [ { label => 'Color', indices => [] }, { label => 'Fruit', indices => [] }, ], ShowUniversalSet => 1, UniversalSetLabel => 'Classify each item below as a color, fruit, or both.', AllowNewBuckets => 0 ); BEGIN_PGML [_]{$draggable} END_PGML ENDDOCUMENT(); ``` To test this you should also test modifications of the above problem with other options. For example, change to `AllowNewBuckets => 1`, and test addition and removal of buckets particularly removal after elements have been added to the bucket. Add indices to the `DefaultSubsets` so elements are initially shown in those and behavior when elements are dragged in an out. --- htdocs/js/DragNDrop/dragndrop.js | 71 ++++++++++++++++--------------- lib/DragNDrop.pm | 30 +++++++++++-- macros/math/draggableSubsets.pl | 73 +++++++++++++++++++++----------- 3 files changed, 113 insertions(+), 61 deletions(-) diff --git a/htdocs/js/DragNDrop/dragndrop.js b/htdocs/js/DragNDrop/dragndrop.js index 7dc3d96642..632442f9c6 100644 --- a/htdocs/js/DragNDrop/dragndrop.js +++ b/htdocs/js/DragNDrop/dragndrop.js @@ -9,7 +9,6 @@ this.answerName = el.dataset.answerName ?? ''; this.buckets = []; this.removeButtonText = el.dataset.removeButtonText ?? 'Remove'; - this.allowReusingItems = el.dataset.allowReusingItems; this.answerInput = el.parentElement.querySelector(`input[name="${this.answerName}"]`); if (!this.answerInput) { @@ -26,6 +25,8 @@ this.defaultState = JSON.parse(el.dataset.defaultState ?? '[]'); this.labelFormat = el.dataset.labelFormat; + this.showUniversalSet = 'showUniversalSet' in el.dataset; + if (this.answerInput.value) { // Need to check for things like (3,2,1) for backwards compatibility. Now it will be {3,2,1}. const matches = this.answerInput.value.match(/((?:\{|\()[^\{\}\(\)]*(?:\}|\)))/g); @@ -48,6 +49,19 @@ } this.updateAnswerInput(); + if (this.showUniversalSet) { + this.universalSetContainer = document.createElement('div'); + this.universalSetContainer.classList.add('dd-pool-bucket-container'); + el.prepend(this.universalSetContainer); + + this.universalSetBucket = new Bucket(this, this.buckets.length, { + isUniversalSet: true, + removable: false, + label: el.dataset.universalSetLabel ?? 'Universal Set', + indices: this.itemList.map((_el, i) => i) + }); + } + el.querySelector('.dd-add-bucket')?.addEventListener('click', () => { // When buckets are removed and added the id's may not be sequential anymore. So the bucket count // cannot directly be used, and an id needs to be found that is not already in use. @@ -92,45 +106,33 @@ this.bucketPool = bucketPool; this.el = this.htmlBucket(bucketData.label, bucketData.removable, bucketData.indices); - bucketPool.bucketContainer.append(this.el); + + if (bucketData.isUniversalSet) bucketPool.universalSetContainer.append(this.el); + else bucketPool.bucketContainer.append(this.el); // Typeset any math content that may be in the added html. if (window.MathJax) { MathJax.startup.promise = MathJax.startup.promise.then(() => MathJax.typesetPromise([this.el])); } - this.allowReusingItems = this.bucketPool.allowReusingItems; - - if (this.allowReusingItems) { - if (id === 0) { - this.sortable = Sortable.create(this.ddList, { - animation: 150, - sort: false, - onEnd: () => this.bucketPool.updateAnswerInput(), - group: { - name: bucketPool.answerName, - pull: 'clone', - put: false - } - }); + + const options = { + group: { name: bucketPool.answerName }, + animation: 150, + onEnd: () => this.bucketPool.updateAnswerInput() + }; + + if (bucketPool.showUniversalSet) { + if (bucketData.isUniversalSet) { + options.group.pull = 'clone'; + options.group.put = false; } else { - this.sortable = Sortable.create(this.ddList, { - animation: 150, - onEnd: () => this.bucketPool.updateAnswerInput(), - removeOnSpill: true, - group: { - name: bucketPool.answerName, - put: (to, _from, dragEl) => - !Array.from(to.el.children).some((child) => child.dataset.id === dragEl.dataset.id) - } - }); + options.removeOnSpill = true; + options.group.put = (to, _from, dragEl) => + !Array.from(to.el.children).some((child) => child.dataset.id === dragEl.dataset.id); } - } else { - this.sortable = Sortable.create(this.ddList, { - group: bucketPool.answerName, - animation: 150, - onEnd: () => this.bucketPool.updateAnswerInput() - }); } + + this.sortable = Sortable.create(this.ddList, options); } htmlBucket(label, removable, indices = []) { @@ -170,8 +172,11 @@ removeButton.addEventListener('click', () => { const firstBucketList = this.bucketPool.buckets[0].ddList; + const firstBucketListItems = Array.from(firstBucketList.querySelectorAll('.dd-item')).map( + (item) => item.dataset.id + ); for (const item of this.ddList.querySelectorAll('.dd-item')) { - firstBucketList.append(item); + if (!firstBucketListItems.includes(item.dataset.id)) firstBucketList.append(item); } bucketElement.remove(); diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm index f2513442c4..7e55aef619 100644 --- a/lib/DragNDrop.pm +++ b/lib/DragNDrop.pm @@ -102,6 +102,16 @@ This sets the size for which the TeX output for hardcopy uses two columns or not If the current C<\linewidth> is greater than or equal to this size then two columns will be used, otherwise only single column is used. +=item showUniversalSet (Default: C<0>) + +If 1 then the set of all elements passed in the C<$itemList> will be shown +in a separate bucket. Elements can be dragged from this set and into other +buckets, but not into it. + +=item universalSetLabel (Default: C<< 'Universal Set' >>) + +Label shown for the universal set bucket if C is 1. + =back =head2 METHODS @@ -152,7 +162,8 @@ sub new { addButtonText => 'Add Bucket', removeButtonText => 'Remove', multicolsWidth => '300pt', - allowReusingItems => 0, + showUniversalSet => 0, + universalSetLabel => 'Universal Set', %options, }, ref($self) || $self; @@ -162,11 +173,12 @@ sub HTML { my $self = shift; my $out = qq{
{allowReusingItems}; $out .= ' data-item-list="' . PGcore::encode_pg_and_html(encode_json($self->{itemList})) . '"'; $out .= ' data-default-state="' . PGcore::encode_pg_and_html(encode_json($self->{defaultBuckets})) . '"'; $out .= qq{ data-remove-button-text="$self->{removeButtonText}"}; $out .= qq{ data-label-format="$self->{bucketLabelFormat}"} if $self->{bucketLabelFormat}; + $out .= " data-show-universal-set" if $self->{showUniversalSet}; + $out .= qq{ data-universal-set-label="$self->{universalSetLabel}"}; $out .= '>'; $out .= '
{showUniversalSet}) { + $out .= "\n\\hrule\n\\vspace{0.5\\baselineskip}\n"; + $out .= "\\parbox{0.9\\linewidth}{\n"; + $out .= "$self->{universalSetLabel}\n"; + $out .= "\\begin{itemize}\n"; + $out .= "\\item $_\n" for (@{ $self->{itemList} }); + $out .= "\\end{itemize}\n"; + $out .= "}\n"; + } + + $out .= "\n\\hrule\n\\vspace{0.5\\baselineskip}\n\\newif\\ifdndcolumns\n" . "\\ifdim\\linewidth<$self->{multicolsWidth}\\relax\\dndcolumnsfalse\\else\\dndcolumnstrue\\fi\n"; diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 7b530fb61f..12f2ebf01a 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -83,11 +83,12 @@ =head1 USAGE DefaultSubsets => OrderedSubsets => 0 or 1 AllowNewBuckets => 0 or 1 - AllowReusingItems => 0 or 1 BucketLabelFormat => ResetButtonText => AddButtonText => RemoveButtonText => + ShowUniversalSet => 0 or 1 + UniversalSetLabel => Their usage is demonstrated in the example below. @@ -149,12 +150,6 @@ =head1 EXAMPLE # The default value if not given is 1. AllowNewBuckets => 1, - # 0 is the conventional approach, by which the repository bucket is - # depleted if an item an item is dragged from that bucket. - # 1 means that items are replenished in the repository bucket once - # dragged. The default value if not given is 0. - AllowReusingItems => 0, - # If this option is defined then labels for buckets for which a specific # label is not provided will be created by replacing %s with the bucket # number to this prefix. These labels will also be used for buckets @@ -177,6 +172,16 @@ =head1 EXAMPLE # removable buckets. The default value if not given is "Remove". RemoveButtonText => 'Delete' + # If this is true then a separate bucket containing the full set passed + # as the first argument above (the universal set) will be shown, and the + # elements of the set can be distributed to the other subsets (or + # buckets) that are shown. The default value if not given is 0. + ShowUniversalSet => 1, + + # Label for the bucket representing the universal set. + # The default value if not given is "Universal Set". + UniversalSetLabel => 'Universal Set', + # These are options that will be passed to the $draggable->cmp method. cmpOptions => { checker => sub { ... } } ); @@ -235,14 +240,43 @@ sub new { ResetButtonText => 'Reset', AddButtonText => 'Add Bucket', RemoveButtonText => 'Remove', - AllowReusingItems => 0, + ShowUniversalSet => 0, + UniversalSetLabel => '', %options }, ref($invocant) || $invocant; - #If AllowReusingItem is set, bucket 0 should contain the full set of elements for grading - my $maxindex = scalar @{ $base->{set} } - 1; #Number of elements -1 - if ($base->{AllowReusingItems}) { $subsets->[0] = [ 0 .. $maxindex ]; } + Value::Error('Answer subsets must be an array reference.') unless ref($subsets) eq 'ARRAY'; + + my %seenIndices; + for my $subset (@$subsets) { + Value::Error('Each answer subset must be a reference to an array of indices.') + unless ref($subset) eq 'ARRAY'; + for (@$subset) { + Value::Error('An index in an answer subset is out of range.') unless $_ < @$set; + Value::Error('An index is repeated in multiple answer subsets. ' + . 'This can only be the case if ShowUniversalSet is 1.') + if !$base->{ShowUniversalSet} && $seenIndices{$_}; + $seenIndices{$_} = 1; + } + } + + Value::Error('Default subsets must be an array reference.') + unless ref($base->{DefaultSubsets}) eq 'ARRAY'; + + %seenIndices = (); + for my $subset (@{ $base->{DefaultSubsets} }) { + Value::Error('Each default subset must be a hash reference.') unless ref($subset) eq 'HASH'; + Value::Error('Each default subset must have "indices" which must be a reference to an array of indices.') + unless ref($subset->{indices}) eq 'ARRAY'; + for (@{ $subset->{indices} }) { + Value::Error('An index in a default subset is out of range.') unless $_ < @$set; + Value::Error('An index is repeated in multiple default subsets.' + . 'This can only be the case if ShowUniversalSet is 1.') + if !$base->{ShowUniversalSet} && $seenIndices{$_}; + $seenIndices{$_} = 1; + } + } $base->{order} = do { my @indices = 0 .. $#{ $base->{set} }; @@ -257,7 +291,6 @@ sub new { '(' => { close => ')', type => 'List', formList => 1, formMatrix => 0, removable => 0 }, '{' => { close => '}', type => 'Set', formList => 0, formMatrix => 0, removable => 0, emptyOK => 1 } ); - $context->lists->set( 'DraggableSubsets' => { class => 'Parser::List::List', @@ -318,7 +351,8 @@ sub ans_rule { resetButtonText => $self->{ResetButtonText}, addButtonText => $self->{AddButtonText}, removeButtonText => $self->{RemoveButtonText}, - allowReusingItems => $self->{AllowReusingItems}, + showUniversalSet => $self->{ShowUniversalSet}, + universalSetLabel => $self->{UniversalSetLabel}, ); my $ans_rule = main::NAMED_HIDDEN_ANS_RULE($self->ANS_NAME); @@ -347,18 +381,7 @@ sub cmp_defaults { sub cmp { my ($self, %options) = @_; - my $nbuc = scalar @{ $self->{set} }; #number of buckets - if ($self->{AllowReusingItems}) { - $tmp = $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options)->withPostFilter(sub { - $ansHash = shift; - $ncor = $ansHash->{score} * $nbuc; #number of buckets correct - $ansHash->{score} = ($ncor - 1) / ($nbuc - 1); - return $ansHash; - }); - return $tmp; - } else { - return $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options); - } + return $self->SUPER::cmp(%{ $self->{cmpOptions} }, %options); } sub TeX { From 214b29bf1deab50a879c28ff198f186a282663ee Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 7 Jul 2025 11:12:27 -0500 Subject: [PATCH 005/111] HTML encode the universal set label so that math works. --- lib/DragNDrop.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/DragNDrop.pm b/lib/DragNDrop.pm index 7e55aef619..6b21c59177 100644 --- a/lib/DragNDrop.pm +++ b/lib/DragNDrop.pm @@ -178,7 +178,7 @@ sub HTML { $out .= qq{ data-remove-button-text="$self->{removeButtonText}"}; $out .= qq{ data-label-format="$self->{bucketLabelFormat}"} if $self->{bucketLabelFormat}; $out .= " data-show-universal-set" if $self->{showUniversalSet}; - $out .= qq{ data-universal-set-label="$self->{universalSetLabel}"}; + $out .= ' data-universal-set-label="' . PGcore::encode_pg_and_html($self->{universalSetLabel}) . '"'; $out .= '>'; $out .= '
Date: Mon, 7 Jul 2025 11:16:41 -0500 Subject: [PATCH 006/111] Make the `UniversalSetLabel` default setting of the `draggableSubsets.pl` macro consistent with what is documented. Add `options.sort = false` for the universal set so that it can not be rearranged from what it is set to. This is for transparency since this doesn't matter anyway. --- htdocs/js/DragNDrop/dragndrop.js | 1 + macros/math/draggableSubsets.pl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/htdocs/js/DragNDrop/dragndrop.js b/htdocs/js/DragNDrop/dragndrop.js index 632442f9c6..4c45cd72c0 100644 --- a/htdocs/js/DragNDrop/dragndrop.js +++ b/htdocs/js/DragNDrop/dragndrop.js @@ -123,6 +123,7 @@ if (bucketPool.showUniversalSet) { if (bucketData.isUniversalSet) { + options.sort = false; options.group.pull = 'clone'; options.group.put = false; } else { diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 12f2ebf01a..5df5a5d5b4 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -241,7 +241,7 @@ sub new { AddButtonText => 'Add Bucket', RemoveButtonText => 'Remove', ShowUniversalSet => 0, - UniversalSetLabel => '', + UniversalSetLabel => 'Universal Set', %options }, ref($invocant) || $invocant; From 9fed370561c59f36ff7f48ac45e8458282d6877b Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 10 Jul 2025 22:54:47 -0700 Subject: [PATCH 007/111] use direct method in POD --- lib/AnswerHash.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm index 7273103cd7..1d50c77b98 100755 --- a/lib/AnswerHash.pm +++ b/lib/AnswerHash.pm @@ -128,7 +128,7 @@ my %fields = ( Usage - $rh_anshash = new AnswerHash; + $rh_anshash = AnswerHash->new; returns an object of type AnswerHash. From 3d1d351e3c4aae62fcce59b076941834681e0e5f Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 15 Jul 2025 08:14:23 -0500 Subject: [PATCH 008/111] Update the `pg.pot` file. There are some new strings that have not been added to the pot file. --- lib/WeBWorK/PG/Localize/pg.pot | 138 ++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/pg.pot b/lib/WeBWorK/PG/Localize/pg.pot index f9f42e18f8..f7d4fdab2b 100644 --- a/lib/WeBWorK/PG/Localize/pg.pot +++ b/lib/WeBWorK/PG/Localize/pg.pot @@ -15,64 +15,68 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1459 +#: /opt/webwork/pg/macros/PG.pl:1455 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1431 +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1239 +#: /opt/webwork/pg/macros/PG.pl:1224 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1132 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1444 +#: /opt/webwork/pg/macros/PG.pl:1440 msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1418 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1409 +#: /opt/webwork/pg/macros/PG.pl:1405 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1075 +#: /opt/webwork/pg/macros/PG.pl:1060 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1255 /opt/webwork/pg/macros/PG.pl:1320 +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1124 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1326 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:575 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" @@ -84,59 +88,59 @@ msgstr "" msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:71 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 msgid "Graded" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1316 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1315 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1128 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1289 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:100 /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:59 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1332 +#: /opt/webwork/pg/macros/PG.pl:1328 msgid "Reveal" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1308 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1307 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "" @@ -144,39 +148,39 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1379 +#: /opt/webwork/pg/macros/PG.pl:1375 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1397 +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1368 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1388 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1127 /opt/webwork/pg/macros/core/PGanswermacros.pl:1688 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 msgid "This answer was marked correct because the primary answer is correct." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:92 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2973 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -184,23 +188,23 @@ msgstr "" msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:574 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:69 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1284 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1095 /opt/webwork/pg/macros/core/PGanswermacros.pl:1656 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" @@ -208,7 +212,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2966 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" @@ -216,39 +220,61 @@ msgstr "" msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:544 /opt/webwork/pg/macros/core/PGbasicmacros.pl:555 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:567 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" # does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 /opt/webwork/pg/lib/Value/Vector.pm:303 /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 /opt/webwork/pg/lib/Value/Vector.pm:303 /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:561 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:550 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:567 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" #: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 From c9b9fe8c07491a2e162654d7c3b2a3c86a88e209 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Thu, 17 Jul 2025 17:15:15 -0600 Subject: [PATCH 009/111] Remove use of shift for parsing parameters in plotly3D. Also remove use for foreach. --- macros/graph/plotly3D.pl | 49 ++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/macros/graph/plotly3D.pl b/macros/graph/plotly3D.pl index f731733b99..320d22657e 100644 --- a/macros/graph/plotly3D.pl +++ b/macros/graph/plotly3D.pl @@ -325,7 +325,7 @@ sub _plotly3D_init { package plotly3D; sub new { - my $self = shift; + my ($self, @options) = @_; my $class = ref($self) || $self; $self = bless { @@ -340,7 +340,7 @@ sub new { image => '', tex_size => 500, tex_border => 1, - @_, + @options, }, $class; return $self; @@ -350,11 +350,7 @@ sub new { sub addCurve { push(@{ shift->{plots} }, plotly3D::Plot::Curve->new(@_)); } sub addFunction { - my $self = shift; - my $func = shift; - my $b1 = shift; - my $b2 = shift; - my %opts = @_; + my ($self, $func, $b1, $b2, %opts) = @_; my @vars = ($opts{variables}) ? @{ $opts{variables} } : ('x', 'y'); my $type = $opts{funcType} || ''; if ($type eq 'perl') { @@ -390,7 +386,7 @@ sub HTML { my $plots = ''; my $scene = ($self->{scene}) ? "scene: { $self->{scene} }," : ''; - foreach (@{ $self->{plots} }) { + for (@{ $self->{plots} }) { $plots .= $_->HTML; } $plots =~ s/^\t//; @@ -446,21 +442,19 @@ sub Print { package plotly3D::Plot; sub cmpBounds { - my $self = shift; - my $bounds = shift; + my ($self, $bounds, $count) = @_; Value::Error('Bounds must be an array with two or three items.') unless (ref($bounds) eq 'ARRAY' && scalar(@$bounds) > 1); - my ($min, $max, $count) = @$bounds; - $count = shift unless $count; + my ($min, $max, $count2) = @$bounds; + $count = $count2 if $count2; my $step = ($max - $min) / $count; $max += $step / 2; # Fudge factor to deal with rounding issues. return ($min, $max, $step); } sub parseFunc { - my $self = shift; - my $func = shift; + my ($self, $func, $bounds1, $bounds2) = @_; Value::Error('First input must be an array with three items.') unless (ref($func) eq 'ARRAY' && scalar(@$func) == 3); @@ -469,10 +463,10 @@ sub parseFunc { } else { ($self->{xFunc}, $self->{yFunc}, $self->{zFunc}) = @$func; if ($self->{nVars} == 2) { - ($self->{uMin}, $self->{uMax}, $self->{uStep}) = $self->cmpBounds(shift, 20); - ($self->{vMin}, $self->{vMax}, $self->{vStep}) = $self->cmpBounds(shift, 20); + ($self->{uMin}, $self->{uMax}, $self->{uStep}) = $self->cmpBounds($bounds1, 20); + ($self->{vMin}, $self->{vMax}, $self->{vStep}) = $self->cmpBounds($bounds2, 20); } else { - ($self->{tMin}, $self->{tMax}, $self->{tStep}) = $self->cmpBounds(shift, 100); + ($self->{tMin}, $self->{tMax}, $self->{tStep}) = $self->cmpBounds($bounds1, 100); } } } @@ -485,7 +479,7 @@ sub genPoints { # Manual data plot, nothing to do. } elsif ($type eq 'jsmd' || $type eq 'js') { if ($type eq 'jsmd') { - foreach ('xFunc', 'yFunc', 'zFunc') { + for ('xFunc', 'yFunc', 'zFunc') { $self->{$_} = $self->funcToJS($self->{$_}); } } @@ -501,8 +495,7 @@ sub genPoints { # Takes a pseudo function string and replaces with JavaScript functions. sub funcToJS { - my $self = shift; - my $func = shift; + my ($self, $func) = @_; my %vars = map { $_ => $_ } @{ $self->{variables} }; my %tokens = ( sqrt => 'Math.sqrt', @@ -653,10 +646,9 @@ package plotly3D::Plot::Surface; our @ISA = ('plotly3D::Plot'); sub new { - my $self = shift; - my $data = shift; - my $uBounds = (ref($_[0]) eq 'ARRAY') ? shift : ''; - my $vBounds = (ref($_[0]) eq 'ARRAY') ? shift : ''; + my ($self, $data, @options) = @_; + my $uBounds = (ref($options[0]) eq 'ARRAY') ? shift @options : ''; + my $vBounds = (ref($options[0]) eq 'ARRAY') ? shift @options : ''; my $class = ref($self) || $self; $self = bless { @@ -666,7 +658,7 @@ sub new { opacity => 1, variables => [ 'u', 'v' ], nVars => 2, - @_, + @options, }, $class; $self->parseFunc($data, $uBounds, $vBounds); @@ -697,9 +689,8 @@ package plotly3D::Plot::Curve; our @ISA = ('plotly3D::Plot'); sub new { - my $self = shift; - my $data = shift; - my $tBounds = (ref($_[0]) eq 'ARRAY') ? shift : ''; + my ($self, $data, @options) = @_; + my $tBounds = (ref($options[0]) eq 'ARRAY') ? shift @options : ''; my $class = ref($self) || $self; $self = bless { @@ -710,7 +701,7 @@ sub new { opacity => 1, variables => ['t'], nVars => 1, - @_, + @options, }, $class; $self->parseFunc($data, $tBounds); From 7bbafe6ddad2540c643d8cf7cef7c6181acbc094 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:59:00 +0000 Subject: [PATCH 010/111] Translate pg.pot in el [Manual Sync] 66% of minimum 1% translated source file: 'pg.pot' on 'el'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/el.po | 239 +++++++++++++++++++++------------- 1 file changed, 150 insertions(+), 89 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/el.po b/lib/WeBWorK/PG/Localize/el.po index fae1eead4c..6524aa5547 100644 --- a/lib/WeBWorK/PG/Localize/el.po +++ b/lib/WeBWorK/PG/Localize/el.po @@ -20,236 +20,297 @@ msgstr "" "Language: el\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "%1 από τις απαντήσεις ΔΕΝ είναι %plural(%1,σωστή, σωστές)." -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "%1 από τις απαντήσεις θα βαθμολογηθούν αργότερα. " +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% σωστό" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 +#: /opt/webwork/pg/macros/PG.pl:1440 msgid "" "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" "%quant(%1,των ερωτήσεων απομένει, των ερωτήσεων απομένουν) αναπάντητες." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "Όλες οι απαντήσεις είναι σωστές." -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." -msgstr "Όλες οι απαντήσεις που παίρνουν βαθμό είναι σωστές." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Σωστό" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "Σωστή Απάντηση" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "Λάθος" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "Λήψη νέας εκδοχής του προβλήματος" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "Επιστροφή σε Μέρος 1" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "Μετάβαση στο επόμενο" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "Στην έντυπη μορφή εμφανίζεται πάντα η αρχική μορφή του προβλήματος." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1402 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 msgid "Hint:" msgstr "Υπόδειξη:" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" "Αν επιστρέψετε αργότερα, ενδέχεται να επανέλθει στην αρχική του μορφή." -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "Λάθος" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "Μήνυμα" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Σημείωση:" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "Προεπισκόπηση" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "Προεπισκόπηση απάντησης" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "Ορισμός τυχαίου αριθμού:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "Απάντηση:" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "Υποβάλετε τις απαντήσεις σας ξανά για να συνεχίσετε." -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "Η απάντηση ΔΕΝ είναι σωστή." -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "Η απάντηση είναι σωστή!" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "Η απάντηση θα βαθμολογηθεί αργότερα." -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "Η ερώτηση δεν έχει απαντηθεί." +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "Η απάντηση θα βαθμολογηθεί αργότερα. " -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "Αυτή είναι μια νέα (επανατυχαιοποιημένη) έκδοση του προβλήματος." -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" -"Εδώ μπορείτε να εισάγετε την απάντησή σας. \n" -"\n" -"Όταν υποβάλετε την απάντησή σας, θα αποθηκευτεί και κατόπιν θα διορθωθεί από τον υπεύθυνο καθηγητή. Εάν ο υπεύθυνος καθηγητής κάνει κάποια σχόλια, τα σχόλια αυτά θα εμφανιστούν σε αυτή τη σελίδα. \n" -"\n" -"Μπορείτε να χρησιμοποιήσετε κώδικα LaTeX στην απάντησή σας, όμως ο κώδικας θα πρέπει να μπει μέσα σε παρενθέσεις και όχι μέσα σε σύμβολα $. " - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3062 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" "Αυτό το πρόβλημα περιέχει ένα βίντεο που πρέπει να προβληθεί στο διαδίκτυο." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "Αυτό το πρόβλημα έχει περισσότερα από ένα μέρη." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "Σωστό" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "Χωρίς βαθμό" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "Εισάγατε" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Μπορείτε να κερδίσετε μερική πίστωση σε αυτό το πρόβλημα." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" "Μπορείτε να λάβετε νέα εκδοχή του προβλήματος μετά την ημερομηνία λήξης." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "Μη δεκτές αλλαγές σε απαντήσεις αν συνεχίσετε στο επόμενο μέρος!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3055 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "Ο περιηγητής σας δεν υποστηρίζει την ετικέτα βίντεο." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "Το σκορ σας σε αυτήν την προσπάθεια είναι μόνο για αυτό το μέρος˙" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" -msgstr "απάντηση" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" -msgstr "στήλη" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" +msgstr "" #. ('j', 'k', '_0') #. ('j', 'k') # does not need to be translated -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "i " -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "εάν" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "αλλιώς" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" -msgstr "μέρος" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" -msgstr "πρόβλημα" +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " +msgstr "" + +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" -msgstr "σειρά" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "όταν υποβάλλετε τις απαντήσεις σας" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "το συνολικό σκορ σας είναι για όλα τα μέρη μαζί. " From 826cf7c3b7d0800e2b0136980f55653c2e3c2d4c Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:59:00 +0000 Subject: [PATCH 011/111] Translate pg.pot in he_IL [Manual Sync] 84% of minimum 1% translated source file: 'pg.pot' on 'he_IL'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/he-IL.po | 204 +++++++++++++++++-------------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/he-IL.po b/lib/WeBWorK/PG/Localize/he-IL.po index 9eaa3bd3a2..bd6f9220fb 100644 --- a/lib/WeBWorK/PG/Localize/he-IL.po +++ b/lib/WeBWorK/PG/Localize/he-IL.po @@ -21,268 +21,292 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1398 +#: /opt/webwork/pg/macros/PG.pl:1455 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "%1 מהתשובות %plural(%1,נבדק,נבדקו)." #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1370 +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "%1 מהתשובות %plural(%1,אינה נכונה,אינן נכונות)." #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1394 +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "%1 מהתשובות ייבדקו בעתיד." #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1195 +#: /opt/webwork/pg/macros/PG.pl:1224 msgid "%1 with message" msgstr "%1 עם הודעה" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1088 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% נכון" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1383 +#: /opt/webwork/pg/macros/PG.pl:1440 msgid "" "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1, מהשאלות לא נענתה, מהשאלות לא נענו)." -#: /opt/webwork/pg/macros/PG.pl:1357 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "כל התשובות הן נכונות." -#: /opt/webwork/pg/macros/PG.pl:1348 +#: /opt/webwork/pg/macros/PG.pl:1405 msgid "All of the computer gradable answers are correct." msgstr "כל התשובות הניתנות לבדיקה אוטומטית הן נכונות." -#: /opt/webwork/pg/macros/PG.pl:1037 +#: /opt/webwork/pg/macros/PG.pl:1060 msgid "Answer Preview" msgstr "תצוגה מקדימה של התשובות" -#: /opt/webwork/pg/macros/PG.pl:1210 +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 msgid "Close" msgstr "סגור" -#: /opt/webwork/pg/macros/PG.pl:1080 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "נכון" -#: /opt/webwork/pg/macros/PG.pl:1261 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "תשובה נכונה" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:495 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr " לא נכון" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "קבל גרסה חדשה של שאלה זו." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "חזור לחלק 1" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "המשך לחלק הבא" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:70 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 msgid "Graded" msgstr "נבדק" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "בקבצי הדפסה תמיד יודפס הגרסה המקורית של השאלה." -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1327 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 msgid "Hint" msgstr "רמז" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1326 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 msgid "Hint:" msgstr "רמז:" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "אם תחזור לשאלה בהמשך, ייתכן שהשאלה תחזור לגרסה המקורית." -#: /opt/webwork/pg/macros/PG.pl:1084 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "לא נכון" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "הערה:" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:144 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "תצוגה מקדימה" -#: /opt/webwork/pg/macros/PG.pl:1242 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "תצוגה מקדימה של התשובה שלך" -#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:100 -#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:59 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 msgid "Quick Entry" msgstr "הזנה מהירה" -#: /opt/webwork/pg/macros/PG.pl:1268 +#: /opt/webwork/pg/macros/PG.pl:1328 msgid "Reveal" msgstr "הצג" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "החלף את זרע ההגרלות אל:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1319 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 msgid "Solution" msgstr "פתרון" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1318 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "פתרון:" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "שלח את התשובות שוב כדי להתקדם לחלק הבא." -#: /opt/webwork/pg/macros/PG.pl:1318 +#: /opt/webwork/pg/macros/PG.pl:1375 msgid "The answer has been graded." msgstr "התשובה נבדקה" -#: /opt/webwork/pg/macros/PG.pl:1336 +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "התשובה איננה נכונה" -#: /opt/webwork/pg/macros/PG.pl:1307 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "התשובה נכונה" -#: /opt/webwork/pg/macros/PG.pl:1317 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "התשובה תבדק בהמשך" -#: /opt/webwork/pg/macros/PG.pl:1327 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr " השאלה לא נענתה" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:91 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "התשובה הזאת תבדק בעתיד" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "הנה גרסה חדשה של השאלה. (הוגרלו מספרים חדשים)" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:158 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" -"זהו תיבת קלט לתשובה מפורטת ומומקת. ניתן להקליד כאן את התשובה שלך. לאחר " -"ההגשה, התשובה תשמר לבדיקה על ידי סגל ההוראה בעתיד. במידה וסגל ההוראה יזינו " -"הערות על התשובה שלך, הן תופיעו בדף זה לאחר בדיקת התשובות. ניתן להשמש בלאטך " -"LaTeX כדי להזין נוסחות מתמטיות יפות. יש לעטוף ביטויים מתמטיים בתחביר לאקך " -"בעזרת סימון המבוסס על סוגריים ולא על ידי סימני דולר. לפני הביטוי יש לרשום " -"backslash ואז פתח סוגריים, ואחריו backslash ואז סגור סוגריים." - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3026 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "השאלה מכילה וידיאו שיש לראות באופן מקוון." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "לשאלה זו יש יותר מחלק אחד." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:494 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "נכון" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:68 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "טרם נבדק" -#: /opt/webwork/pg/macros/PG.pl:1237 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "הזנת" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1615 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "ניתן לקבל ניקוד חלקי בשאלה זו" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "אתה יכול לבקש גרסה שונה של שאלה זו לאחר מועד ההגשה." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "לא ניתן לשנות תשובות כאשר אתה מתקדם לחלק הבא!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3019 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "הדפדפן שלך לא תומך ב-video tag." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "הציון שקבלת בהגשה זו הוא רק לחקל זה;" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:556 -msgid "answer" -msgstr "תשובה" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:568 -msgid "column" -msgstr "עמודה" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" +msgstr "" #. ('j', 'k', '_0') #. ('j', 'k') # does not need to be translated #: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "i" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "כאשר" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "אחרת" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:562 -msgid "part" -msgstr "חלק" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " +msgstr "" + +#. ($1 + 1, $2 + 1) #: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 -msgid "problem" -msgstr "שאלה" +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:568 -msgid "row" -msgstr "שורה" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "כאשר אתה מגיש את תשובותיך" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "הציון הכולל הוא על בסיס כל החלקים." From 6d4c2a04aad9fbdda6a9af9620675237a578db9a Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 012/111] Translate pg.pot in cs_CZ [Manual Sync] 41% of minimum 1% translated source file: 'pg.pot' on 'cs_CZ'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/cs-CZ.po | 274 +++++++++++++++++++------------ 1 file changed, 167 insertions(+), 107 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/cs-CZ.po b/lib/WeBWorK/PG/Localize/cs-CZ.po index 6cebc8f7f9..00ff07110e 100644 --- a/lib/WeBWorK/PG/Localize/cs-CZ.po +++ b/lib/WeBWorK/PG/Localize/cs-CZ.po @@ -1,252 +1,312 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Robert Mařík , 2022-2023 +# Glenn Rice, 2023 +# Robert Mařík , 2024 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:42-0500\n" -"Last-Translator: Robert Mařík , 2022-2023\n" -"Language-Team: Czech (Czech Republic) (http://app.transifex.com/webwork/" -"webwork2/language/cs_CZ/)\n" -"Language: cs_CZ\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Robert Mařík , 2024\n" +"Language-Team: Czech (Czech Republic) (https://app.transifex.com/webwork/teams/16644/cs_CZ/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n " -"<= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" +"Language: cs_CZ\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" + +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% správně" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "Nebylo odpovězeno na %quant(%1,otázku,otázky)." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." -msgstr "" +msgstr "Všechny odpovědi jsou správně." + +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "Všechny odpovědí opravované počítačem jsou správně." + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "Náhled odpovědi" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "Zavřít" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" -msgstr "" +msgstr "Správně" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "Správná odpověď" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "Vyhodnoceno" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" +msgstr "Nápověda" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 msgid "Hint:" msgstr "Nápověda:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" -msgstr "" +msgstr "Nesprávně" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "Zpráva" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Poznámka:" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" -msgstr "" +msgstr "Náhled" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" +msgstr "Náhled odpovědi" + +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" +msgstr "Řešení" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "Řešení:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " -msgstr "" - -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "Správnost odpovědi nebyla vyhodnocena." + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." -msgstr "" +msgstr "Odpověď NENÍ správně." -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." -msgstr "" +msgstr "Odpověď je správně." -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." -msgstr "" +msgstr "Odpověď bude vyhodnocena později." -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." +msgstr "Otázka nebyla zodpovězena." + +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." msgstr "" #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." -msgstr "" +msgstr "Odpověď bude vyhodnocena později." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "Zatím nehodnoceno" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" -msgstr "" +msgstr "Zadaná odpověď" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Za částečné zodpovězení této úlohy můžete obdržet částečné hodnocení." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From 450c2b558bebd552f3087e5d83362fdf0286547e Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 013/111] Translate pg.pot in es [Manual Sync] 11% of minimum 1% translated source file: 'pg.pot' on 'es'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/es.po | 256 +++++++++++++++++++++------------- 1 file changed, 157 insertions(+), 99 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/es.po b/lib/WeBWorK/PG/Localize/es.po index c1767029ca..bda17f08fb 100644 --- a/lib/WeBWorK/PG/Localize/es.po +++ b/lib/WeBWorK/PG/Localize/es.po @@ -1,254 +1,312 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Andrés Forero , 2020 -# Enrique Acosta , 2020 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:43-0500\n" -"Last-Translator: Enrique Acosta , 2020\n" -"Language-Team: Spanish (http://app.transifex.com/webwork/webwork2/language/" -"es/)\n" -"Language: es\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Spanish (https://app.transifex.com/webwork/teams/16644/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? " -"1 : 2;\n" +"Language: es\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" + +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" "%quant(%1,de las preguntas sigue,de las preguntas siguen) sin respuesta." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Correcto" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 msgid "Hint:" msgstr "Pista:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "incorrecto" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Nota:" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "Solución:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 +msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 -msgid "Submit your answers again to go on to the next part." +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Puedes obtener una fracción del puntaje total en este problema." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" -msgstr "Problema" +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " +msgstr "" + +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From c12fe02ec9053efe9283d87552d0cb8b1faba72c Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 014/111] Translate pg.pot in ko [Manual Sync] 14% of minimum 1% translated source file: 'pg.pot' on 'ko'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/ko.po | 248 +++++++++++++++++++++------------- 1 file changed, 154 insertions(+), 94 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/ko.po b/lib/WeBWorK/PG/Localize/ko.po index 78ffde31c8..d0e0d7b0b2 100644 --- a/lib/WeBWorK/PG/Localize/ko.po +++ b/lib/WeBWorK/PG/Localize/ko.po @@ -1,251 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Ji-Young Ham , 2022 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:44-0500\n" -"Last-Translator: Ji-Young Ham , 2022\n" -"Language-Team: Korean (http://app.transifex.com/webwork/webwork2/language/" -"ko/)\n" -"Language: ko\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Korean (https://app.transifex.com/webwork/teams/16644/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "정답" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "정답" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "오답" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "다음 단계로 이동하려면 답변을 다시 제출하십시오." -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "채점되지 않은" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "이 문제의 부분 점수를 받았습니다." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "이 문제의 새 버전은 마감일 이후에 받을 수 있습니다." -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "입력답을 제출할 때에는" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "전체 점수는 모든 부분을 합한 것입니다." From c657c206331b812808f67e5dbfeb9681f180ac51 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 015/111] Translate pg.pot in ru_RU [Manual Sync] 4% of minimum 1% translated source file: 'pg.pot' on 'ru_RU'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/ru-RU.po | 251 +++++++++++++++++++------------ 1 file changed, 155 insertions(+), 96 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/ru-RU.po b/lib/WeBWorK/PG/Localize/ru-RU.po index 0495e38bba..a5f146afe1 100644 --- a/lib/WeBWorK/PG/Localize/ru-RU.po +++ b/lib/WeBWorK/PG/Localize/ru-RU.po @@ -1,252 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:44-0500\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Russian (Russia) (http://app.transifex.com/webwork/webwork2/" -"language/ru_RU/)\n" -"Language: ru_RU\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Russian (Russia) (https://app.transifex.com/webwork/teams/16644/ru_RU/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || " -"(n%100>=11 && n%100<=14)? 2 : 3);\n" +"Language: ru_RU\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Верно" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "Правильные ответы" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Балл за эту задачу может дробиться. " -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From ff95bc035f2c4624285f74f702bf5aa40ee3f12f Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 016/111] Translate pg.pot in de [Manual Sync] 22% of minimum 1% translated source file: 'pg.pot' on 'de'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/de.po | 261 +++++++++++++++++++++------------- 1 file changed, 159 insertions(+), 102 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/de.po b/lib/WeBWorK/PG/Localize/de.po index 50191e5f9f..ebf500339c 100644 --- a/lib/WeBWorK/PG/Localize/de.po +++ b/lib/WeBWorK/PG/Localize/de.po @@ -1,256 +1,313 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Armin Reiser, 2022 -# Armin Reiser, 2022 -# Fabian Gabel, 2022 -# Fabian Gabel, 2022 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:42-0500\n" -"Last-Translator: Fabian Gabel, 2022\n" -"Language-Team: German (http://app.transifex.com/webwork/webwork2/language/" -"de/)\n" -"Language: de\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: German (https://app.transifex.com/webwork/teams/16644/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% richtig" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,Frage wurde,Fragen wurden) nicht beantwortet." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Richtig" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "Lösung" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "Neue Version dieser Aufgabe anfordern" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" -msgstr "Hinweis: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "Hinweis: " -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "Falsch" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "Fehlermeldung" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "Note:" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 msgid "Solution:" msgstr "Lösung:" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " -msgstr "Lösung: " - -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "Dies is eine neu randomisierte Version der Aufgabe." -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "Diese Aufgabe besteht aus mehreren Teilen." -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Für diese Aufgabe können Sie Teilpunkte erhalten." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" "Nach Ablauf der Bearbeitungszeit erhalten Sie eine neue Version dieser " "Aufgabe" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "Antworten können im nächsten Teil nicht mehr geändert werden!" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" -msgstr "Antwort" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" -msgstr "Spalte" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" +msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" -msgstr "Aufgabe" +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " +msgstr "" + +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From ef4668006662adbdb2db1e489e1ec81b9050e403 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 017/111] Translate pg.pot in fr [Manual Sync] 7% of minimum 1% translated source file: 'pg.pot' on 'fr'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/fr.po | 255 +++++++++++++++++++++------------- 1 file changed, 155 insertions(+), 100 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/fr.po b/lib/WeBWorK/PG/Localize/fr.po index 6ad9848616..a5413aada1 100644 --- a/lib/WeBWorK/PG/Localize/fr.po +++ b/lib/WeBWorK/PG/Localize/fr.po @@ -1,256 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Jonathan Desaulniers , 2018 -# Michael E Gage , 2014 -# Michael E Gage , 2011 -# Sébastien Labbé , Université du Québec à Montréal, 2011 -# Stéphanie Lanthier , 2011 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:43-0500\n" -"Last-Translator: Stéphanie Lanthier , 2011\n" -"Language-Team: French (http://app.transifex.com/webwork/webwork2/language/" -"fr/)\n" -"Language: fr\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: French (https://app.transifex.com/webwork/teams/16644/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % " -"1000000 == 0 ? 1 : 2;\n" +"Language: fr\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% correct" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1, des questions restent, des questions reste) sans réponse." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Correct" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "Mise à jour réussie" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Vous pouvez obtenir une partie des points pour ce problème." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From 3157d3e9556dbbd7729fb1356c2b970c2396b969 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 018/111] Translate pg.pot in zh_HK [Manual Sync] 4% of minimum 1% translated source file: 'pg.pot' on 'zh_HK'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/zh-HK.po | 248 +++++++++++++++++++------------ 1 file changed, 154 insertions(+), 94 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/zh-HK.po b/lib/WeBWorK/PG/Localize/zh-HK.po index a9a1d45e72..5dbbc3057f 100644 --- a/lib/WeBWorK/PG/Localize/zh-HK.po +++ b/lib/WeBWorK/PG/Localize/zh-HK.po @@ -1,251 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Liping Chen , 2013 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:45-0500\n" -"Last-Translator: Liping Chen , 2013\n" -"Language-Team: Chinese (Hong Kong) (http://app.transifex.com/webwork/" -"webwork2/language/zh_HK/)\n" -"Language: zh_HK\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Chinese (Hong Kong) (https://app.transifex.com/webwork/teams/16644/zh_HK/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: zh_HK\n" "Plural-Forms: nplurals=1; plural=0;\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% 正确" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "正确" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "这一题你可以得到部分成绩" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From b327336ac7f6321029f6af309a0dc71413282cf2 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 019/111] Translate pg.pot in hu [Manual Sync] 9% of minimum 1% translated source file: 'pg.pot' on 'hu'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/hu.po | 256 +++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 98 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/hu.po b/lib/WeBWorK/PG/Localize/hu.po index e2eedf2d86..62917628fe 100644 --- a/lib/WeBWorK/PG/Localize/hu.po +++ b/lib/WeBWorK/PG/Localize/hu.po @@ -1,251 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# pcsiba , 2014,2016-2017 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:44-0500\n" -"Last-Translator: pcsiba , 2014,2016-2017\n" -"Language-Team: Hungarian (http://app.transifex.com/webwork/webwork2/language/" -"hu/)\n" -"Language: hu\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Hungarian (https://app.transifex.com/webwork/teams/16644/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% helyes" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,kérdés maradt,kérdés maradt) megválaszolatlan." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Helyes" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "Helyes válasz" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "Igaz" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Ezért a feladatért részpontokat kaphat. " -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" -msgstr "válasz" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" -msgstr "oszlop" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" +msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" -msgstr "feladat" +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" -msgstr "sor" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " +msgstr "" + +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " +msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From cee30bcf9a887c6d46210a5c7f7fcfb68dad7354 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 020/111] Translate pg.pot in tr [Manual Sync] 6% of minimum 1% translated source file: 'pg.pot' on 'tr'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/tr.po | 247 +++++++++++++++++++++------------- 1 file changed, 154 insertions(+), 93 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/tr.po b/lib/WeBWorK/PG/Localize/tr.po index a5fa6ad025..bc31070d4f 100644 --- a/lib/WeBWorK/PG/Localize/tr.po +++ b/lib/WeBWorK/PG/Localize/tr.po @@ -1,250 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:44-0500\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Turkish (http://app.transifex.com/webwork/webwork2/language/" -"tr/)\n" -"Language: tr\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Turkish (https://app.transifex.com/webwork/teams/16644/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% doğru" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "Soruların %1 tanesi yanıtsız bırakıldı." -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "Doğru yanıt" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "Bu sorudan kısmi puan alabilirsiniz." -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From 551e7c6fa648a6fb6968a516a4f6567f5aacf5d4 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 10:58:57 +0000 Subject: [PATCH 021/111] Translate pg.pot in zh_CN [Manual Sync] 6% of minimum 1% translated source file: 'pg.pot' on 'zh_CN'. Sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format --- lib/WeBWorK/PG/Localize/zh-CN.po | 250 +++++++++++++++++++------------ 1 file changed, 154 insertions(+), 96 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/zh-CN.po b/lib/WeBWorK/PG/Localize/zh-CN.po index e738394966..5d25da03d7 100644 --- a/lib/WeBWorK/PG/Localize/zh-CN.po +++ b/lib/WeBWorK/PG/Localize/zh-CN.po @@ -1,253 +1,311 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# +# FIRST AUTHOR , YEAR. +# # Translators: -# Liping Chen , 2013 -# Liping Chen , 2013 -# 李 明昊 , 2020 +# Glenn Rice, 2023 +# msgid "" msgstr "" -"Project-Id-Version: webwork2\n" +"Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-10-05 06:45-0500\n" -"Last-Translator: Liping Chen , 2013\n" -"Language-Team: Chinese (China) (http://www.transifex.com/webwork/webwork2/" -"language/zh_CN/)\n" -"Language: zh_CN\n" +"PO-Revision-Date: 2023-11-19 04:33+0000\n" +"Last-Translator: Glenn Rice, 2023\n" +"Language-Team: Chinese (China) (https://app.transifex.com/webwork/teams/16644/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" -#. (@answerNames - $numBlank - $numCorrect - $numEssay) -#: /opt/webwork/pg/macros/PG.pl:1412 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1455 +msgid "%1 of the answers %plural(%1,has,have) been graded." +msgstr "" + +#. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1427 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" -#. ($numEssay) -#: /opt/webwork/pg/macros/PG.pl:1435 +#. ($numManuallyGraded) +#: /opt/webwork/pg/macros/PG.pl:1451 msgid "%1 of the answers will be graded later." msgstr "" +#. ($options{resultTitle}) +#: /opt/webwork/pg/macros/PG.pl:1224 +msgid "%1 with message" +msgstr "" + #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1157 +#: /opt/webwork/pg/macros/PG.pl:1117 msgid "%1% correct" msgstr "%1% 正确" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1425 -msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." +#: /opt/webwork/pg/macros/PG.pl:1440 +msgid "" +"%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "%quant(%1,个问题,个问题) 未完成。" -#: /opt/webwork/pg/macros/PG.pl:1399 +#: /opt/webwork/pg/macros/PG.pl:1414 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1390 -msgid "All of the gradeable answers are correct." +#: /opt/webwork/pg/macros/PG.pl:1405 +msgid "All of the computer gradable answers are correct." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1060 +msgid "Answer Preview" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +msgid "Close" +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1149 +#: /opt/webwork/pg/macros/PG.pl:1109 msgid "Correct" msgstr "正确" -#: /opt/webwork/pg/macros/PG.pl:1308 +#: /opt/webwork/pg/macros/PG.pl:1322 msgid "Correct Answer" msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:314 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:561 msgid "False" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:186 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:187 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:172 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:173 msgid "Get a new version of this problem" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:483 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:483 msgid "Go back to Part 1" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:293 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:498 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:512 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:293 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:498 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:512 msgid "Go on to next part" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:427 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:57 +msgid "Graded" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:413 msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1404 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1405 -msgid "Hint:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1403 -msgid "Hint: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +msgid "Hint:" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:425 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:411 msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1153 +#: /opt/webwork/pg/macros/PG.pl:1113 msgid "Incorrect" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1319 -msgid "Message" -msgstr "" - -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:396 -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:422 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:382 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:408 msgid "Note:" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1108 #: /opt/webwork/pg/macros/core/PGessaymacros.pl:131 msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1295 +#: /opt/webwork/pg/macros/PG.pl:1285 msgid "Preview of Your Answer" msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:188 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:44 +#: /opt/webwork/pg/macros/ui/quickMatrixEntry.pl:85 +msgid "Quick Entry" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1328 +msgid "Reveal" +msgstr "" + +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:174 msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1395 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1396 -msgid "Solution:" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1394 -msgid "Solution: " +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +msgid "Solution:" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:525 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:525 msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1378 +#: /opt/webwork/pg/macros/PG.pl:1375 +msgid "The answer has been graded." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1393 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1351 +#: /opt/webwork/pg/macros/PG.pl:1364 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1360 +#: /opt/webwork/pg/macros/PG.pl:1374 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1369 +#: /opt/webwork/pg/macros/PG.pl:1384 msgid "The question has not been answered." msgstr "" +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +msgid "This answer was marked correct because the primary answer is correct." +msgstr "" + #: /opt/webwork/pg/macros/core/PGessaymacros.pl:78 msgid "This answer will be graded at a later time." msgstr "" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:423 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:145 -msgid "" -"This is an essay answer text box. You can type your answer in here and, " -"after you hit submit, it will be saved so that your instructor can grade it " -"at a later date. If your instructor makes any comments on your answer those " -"comments will appear on this page after the question has been graded. You " -"can use LaTeX to make your math equations look pretty. LaTeX expressions " -"should be enclosed using the parenthesis notation and not dollar signs." -msgstr "" - -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3064 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 msgid "This problem contains a video which must be viewed online." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:631 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:631 msgid "This problem has more than one part." msgstr "" -#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:313 +#: /opt/webwork/pg/macros/parsers/parserPopUp.pl:560 msgid "True" msgstr "" -#: /opt/webwork/pg/macros/core/PGessaymacros.pl:60 +#: /opt/webwork/pg/macros/core/PGessaymacros.pl:55 msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1290 +#: /opt/webwork/pg/macros/PG.pl:1280 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1616 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 +#: /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 msgid "You can earn partial credit on this problem." msgstr "这一题你可以得到部分成绩" -#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:397 +#: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:646 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:646 msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3057 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 msgid "Your browser does not support the video tag." msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:634 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:634 msgid "Your score for this attempt is for this part only;" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:558 -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:569 -msgid "answer" +#. ($name) +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "column" +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +msgid "end image description" msgstr "" -# does not need to be translated #. ('j', 'k', '_0') #. ('j', 'k') -#: /opt/webwork/pg/lib/Parser/List/Vector.pm:39 +# does not need to be translated +#: /opt/webwork/pg/lib/Parser/List/Vector.pm:41 #: /opt/webwork/pg/lib/Value/Vector.pm:303 -#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:95 +#: /opt/webwork/pg/macros/contexts/contextLimitedVector.pl:81 msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:840 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:843 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +msgid "image description" +msgstr "" + +#. (1) +#. ($count) +#. ($i + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 +#: /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +msgid "option %1 " +msgstr "" + +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 msgid "otherwise" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:575 -msgid "part" +#. ($2 + 1) +#. ($radioIndex) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +msgid "part %1 " +msgstr "" + +#. ($1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +msgid "problem %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:564 -msgid "problem" +#. ($1 + 1, $2 + 1) +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +msgid "row %1 column %2 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:581 -msgid "row" +#. ($partIndex) +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +msgid "subpart %1 " msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:485 -#: /opt/webwork/pg/macros/core/compoundProblem.pl:500 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:485 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:500 msgid "when you submit your answers" msgstr "" -#: /opt/webwork/pg/macros/core/compoundProblem.pl:638 +#: /opt/webwork/pg/macros/deprecated/compoundProblem.pl:638 msgid "your overall score is for all the parts combined." msgstr "" From 0b40d6ef344cd7ecceeeadac7ce6e7b34c9b7368 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 20 Jul 2025 07:07:32 -0500 Subject: [PATCH 022/111] Version back to develop. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index fd27d43097..62f68fdd37 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -$PG_VERSION ='2.20'; +$PG_VERSION ='2.20+develop'; $PG_COPYRIGHT_YEARS = '1996-2025'; 1; From 96590b8fc2e4ae32d42544fe0da71e2d22814fdb Mon Sep 17 00:00:00 2001 From: Danny Glin Date: Thu, 24 Jul 2025 15:29:08 -0600 Subject: [PATCH 023/111] Rewrite the PG uniq function to return the array in a consistent order --- macros/core/PGbasicmacros.pl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 8e0ca91060..57f62a3155 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -2634,13 +2634,9 @@ =head2 Sorting and other list macros # uniq gives unique elements of a list: sub uniq { - my @in = @_; - my %temp = (); - while (@in) { - $temp{ shift(@in) }++; - } - my @out = keys %temp; # sort is causing trouble with Safe.?? - @out; + my @in = @_; + my %seen; + return grep { !$seen{$_}++ } @in; } sub lex_sort { From 6e306ae6347d66fba36c0d1089c29fccfa7189d1 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Thu, 24 Jul 2025 23:27:26 -0700 Subject: [PATCH 024/111] don't let a negative score be reported --- macros/contexts/contextFiniteSolutionSets.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/contexts/contextFiniteSolutionSets.pl b/macros/contexts/contextFiniteSolutionSets.pl index bf98737afd..e5452a9ac0 100644 --- a/macros/contexts/contextFiniteSolutionSets.pl +++ b/macros/contexts/contextFiniteSolutionSets.pl @@ -283,6 +283,7 @@ sub _contextFiniteSolutionSets_init { . join(',', map { $_->TeX } (@correctanswers)) . "\\right\\}\\)"); } + $score = 0 if $score < 0; return ($score, @errors); }; } From 0a69869e68074c841fb5e71aa06626016f07c9f7 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Tue, 12 Aug 2025 12:57:02 -0700 Subject: [PATCH 025/111] typo --- lib/WeBWorK/PG.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index 594abb3a1b..dee8594174 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -115,7 +115,7 @@ sub new_helper ($invocant, %options) { translator => $translator, head_text => '', post_header_text => '', - body_text => "Unabled to read problem source file:\n$@\n", + body_text => "Unable to read problem source file:\n$@\n", answers => {}, result => {}, state => {}, From 3fa86d62f2946d3a1ed96011b24ad00962bb4a66 Mon Sep 17 00:00:00 2001 From: "Paul B. Henson" Date: Sun, 24 Aug 2025 19:15:56 -0700 Subject: [PATCH 026/111] Fix parameter check in BeginList function This fixes two issues with a check in BeginList; first, it accessed a parameter without checking it if existed first, resulting in undefined value warnings; and second it tested the validity of the uppercase version of the parameter but then used the literal parameter in the assignment. --- macros/ui/unionLists.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/ui/unionLists.pl b/macros/ui/unionLists.pl index 4ba214cd88..5189253dd4 100644 --- a/macros/ui/unionLists.pl +++ b/macros/ui/unionLists.pl @@ -54,7 +54,7 @@ =head1 unionLists.pl sub BeginList { my $LIST = 'OL'; - $LIST = shift if (uc($_[0]) eq "OL" or uc($_[0]) eq "UL"); + $LIST = uc(shift) if (defined($_[0]) && (uc($_[0]) eq "OL" or uc($_[0]) eq "UL")); my $enum = ($LIST eq 'OL' ? "enumerate" : "itemize"); my %options = @_; $LIST .= ' TYPE="' . $options{type} . '"' if defined($options{type}); From 58a5f9d2fda00daf435666fcfdec5ffcdb249fc6 Mon Sep 17 00:00:00 2001 From: wwadmin Date: Fri, 29 Aug 2025 14:50:07 +0000 Subject: [PATCH 027/111] Charge notation in contextReaction.pl This pull request adds a context flag "showUnity", which governs the TeX display of charges in singly charged ions. Presently, ions are displayed in TeX using 1+/1- notation. This pull request proposes to: A) Change the default notation of singly charged ions to a more conventional +/- notation and B) Permit the original 1+/1- notation with use of the showUnity=>1 flag. Such functionality is desirable for writing chemistry problem since since the display of ions in the problems is passed in TeX form via the [` `] syntax. Below is a MWE that demonstrates its use. DOCUMENT(); loadMacros('PGstandard.pl','PGML.pl','contextReaction.pl',"contextArbitraryString.pl"); $ion_name="sodium ion"; Context("Reaction"); $ion=Formula("Na^+1"); Context("ArbitraryString"); BEGIN_PGML Give the correct ion name for [`[$ion]`] [`\hspace{3em}`][_]{$ion_name} (The answer is: [$ion_name]). END_PGML Context("Reaction")->flags->set(showUnity=>1); $ion=Formula("Na^+1"); Context("ArbitraryString"); BEGIN_PGML Give the correct ion name for [`[$ion]`] [`\hspace{3em}`][_]{$ion_name} (The answer is: [$ion_name]). END_PGML ENDDOCUMENT(); A small additional edit was also proposed in the POD to use C in lieu of C since alkaline earth metals are more commonly used to examplify divalent ions. The second ionization energies of alkali elements such as sodium are sufficinetly prohibitively high, that they unlikely be found in that state in nature. $ vi junk $ cat junk Charge notation in contextReaction.pl This pull request adds a context flag "showUnity", which governs the TeX display of charges in singly charged ions. Presently, ions are displayed in TeX using 1+/1- notation. This pull request proposes to: A) Change the default notation of singly charged ions to a more conventional +/- notation and B) Permit the original 1+/1- notation with use of the showUnity=>1 flag. Such functionality is desirable for writing chemistry problems since since the display of ions in the problems is passed in TeX form via the [` `] syntax. Below is a MWE that demonstrates its use. DOCUMENT(); loadMacros('PGstandard.pl','PGML.pl','contextReaction.pl',"contextArbitraryString.pl"); $ion_name="sodium ion"; Context("Reaction"); $ion=Formula("Na^+1"); Context("ArbitraryString"); BEGIN_PGML Give the correct ion name for [`[$ion]`] [`\hspace{3em}`][_]{$ion_name} (The answer is: [$ion_name]). END_PGML Context("Reaction")->flags->set(showUnity=>1); $ion=Formula("Na^+1"); Context("ArbitraryString"); BEGIN_PGML Give the correct ion name for [`[$ion]`] [`\hspace{3em}`][_]{$ion_name} (The answer is: [$ion_name]). END_PGML ENDDOCUMENT(); A small additional edit was also proposed in the POD to use C in lieu of C since alkaline earth metals are more commonly used to exemplify divalent ions. The second ionization energies of alkali elements such as sodium are typically prohibitively high for them to be observed in nature. --- macros/contexts/contextReaction.pl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 057783ad5e..937c8a9e7e 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -25,10 +25,14 @@ =head1 DESCRIPTION $R = Formula("4P + 5O_2 --> 2P_2O_5"); -Ions can be specified using C<^> to produce superscripts, as in C or -C. Note that the charge may be listed with prefix notation (+2) -or postfix notation (2+). A sign by itself is assumed to have number -1, so that C is equivalent to C. +Ions can be specified using C<^> to produce superscripts, as in C or +C. Note that the charge may be listed with prefix notation (+2) +or postfix notation (2+). By default, TeX will display only the sign of the +charge of singly charged ions, e.g., C and C. However, the context +flag C<< showUnity=>1 >> can be used to direct TeX to use an alternative +notation for singly charged ions, by which the numerical magnitude (1) is +displayed along with the sign of the charge, e.g., C and C. The +default value of C<> is zero. States can be appended to compounds, as in C. So you can make reactions like the following: @@ -259,6 +263,7 @@ sub Init { ); $context->variables->add(map { $_ => $STATE } ('(aq)', '(s)', '(l)', '(g)', '(ppt)')); $context->reductions->clear(); + $context->flags->set(showUnity => 0); $context->flags->set(studentsMustUseStates => 1); $context->flags->set(reduceConstants => 0); $context->{parser}{Number} = 'context::Reaction::Number'; @@ -747,7 +752,11 @@ sub TeX { my $self = shift; my $uop = $self->{def}; my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - return $self->{op}->TeX($uop->{precedence}) . $op; + # my $unity = $self->context->flags->get('showUnity'); + my $magnitude = $self->{op}->TeX($uop->{precedence}); + #if ($unity == 0 && $magnitude == 1) { return $op } + return $op if $self->context->flags->get('showUnity') == 0 && $magnitude == 1; + return $magnitude . $op; } sub TYPE {'a charge'} From 247aee789068d185ed0e6837d069ea4bec37dc59 Mon Sep 17 00:00:00 2001 From: wwadmin Date: Sat, 30 Aug 2025 14:10:55 +0000 Subject: [PATCH 028/111] Comments removed --- macros/contexts/contextReaction.pl | 2 -- 1 file changed, 2 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 937c8a9e7e..efbb2c19e4 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -752,9 +752,7 @@ sub TeX { my $self = shift; my $uop = $self->{def}; my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - # my $unity = $self->context->flags->get('showUnity'); my $magnitude = $self->{op}->TeX($uop->{precedence}); - #if ($unity == 0 && $magnitude == 1) { return $op } return $op if $self->context->flags->get('showUnity') == 0 && $magnitude == 1; return $magnitude . $op; } From 1a9b6b622c5edae72b2534d87b0da7cf8c78915d Mon Sep 17 00:00:00 2001 From: wwadmin Date: Sat, 30 Aug 2025 14:16:48 +0000 Subject: [PATCH 029/111] run-perltidy.pl rerun --- macros/contexts/contextReaction.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index efbb2c19e4..88e2e02900 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -749,9 +749,9 @@ sub string { # Always put signs on the right # sub TeX { - my $self = shift; - my $uop = $self->{def}; - my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); + my $self = shift; + my $uop = $self->{def}; + my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); my $magnitude = $self->{op}->TeX($uop->{precedence}); return $op if $self->context->flags->get('showUnity') == 0 && $magnitude == 1; return $magnitude . $op; From 3399d80bb2f9b93e1e8ad25b031064ddb15b9b76 Mon Sep 17 00:00:00 2001 From: wwadmin Date: Sat, 30 Aug 2025 19:31:42 +0000 Subject: [PATCH 030/111] Recommended edits made to the string and TeX subroutines --- macros/contexts/contextReaction.pl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 88e2e02900..7f61cd2b8a 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -741,8 +741,11 @@ sub equivalent { # sub string { my $self = shift; - my $n = $self->{op}->string($uop->{precedence}); - return ($n eq '1' ? '' : $n) . $self->{def}{string}; + my $uop = $self->{def}; + my $op = $uop->{string}; + my $mag = $self->{op}->string($uop->{precedence}); #magnitude + return $op if !$self->context->flags->get('showUnity') && $mag eq 1; + return $mag . $op; } # @@ -752,9 +755,9 @@ sub TeX { my $self = shift; my $uop = $self->{def}; my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - my $magnitude = $self->{op}->TeX($uop->{precedence}); - return $op if $self->context->flags->get('showUnity') == 0 && $magnitude == 1; - return $magnitude . $op; + my $mag = $self->{op}->TeX($uop->{precedence}); + return $op if !$self->context->flags->get('showUnity') && $mag eq 1; + return $mag . $op; } sub TYPE {'a charge'} From 46b5edf84e39f8e1d767d8e95e434262a247f37b Mon Sep 17 00:00:00 2001 From: wwadmin Date: Sat, 30 Aug 2025 19:35:13 +0000 Subject: [PATCH 031/111] Reran run-perltidy.pl --- macros/contexts/contextReaction.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 7f61cd2b8a..e3921ac2c0 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -743,7 +743,7 @@ sub string { my $self = shift; my $uop = $self->{def}; my $op = $uop->{string}; - my $mag = $self->{op}->string($uop->{precedence}); #magnitude + my $mag = $self->{op}->string($uop->{precedence}); #magnitude return $op if !$self->context->flags->get('showUnity') && $mag eq 1; return $mag . $op; } @@ -752,10 +752,10 @@ sub string { # Always put signs on the right # sub TeX { - my $self = shift; - my $uop = $self->{def}; - my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - my $mag = $self->{op}->TeX($uop->{precedence}); + my $self = shift; + my $uop = $self->{def}; + my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); + my $mag = $self->{op}->TeX($uop->{precedence}); return $op if !$self->context->flags->get('showUnity') && $mag eq 1; return $mag . $op; } From 19d2cc8c11ce8f9eb3f144f2f66b8e9807d08138 Mon Sep 17 00:00:00 2001 From: wwadmin Date: Mon, 1 Sep 2025 18:35:40 +0000 Subject: [PATCH 032/111] Edited the string() and TeX routines to contain only one return line. --- macros/contexts/contextReaction.pl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index e3921ac2c0..d60384f454 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -740,11 +740,12 @@ sub equivalent { # Always put signs on the right # sub string { - my $self = shift; - my $uop = $self->{def}; - my $op = $uop->{string}; - my $mag = $self->{op}->string($uop->{precedence}); #magnitude - return $op if !$self->context->flags->get('showUnity') && $mag eq 1; + my $self = shift; + my $uop = $self->{def}; + my $op = $uop->{string}; + my $isUnity = $self->context->flags->get('showUnity'); + my $mag = $self->{op}->string($uop->{precedence}); #magnitude + $mag = '' if ($mag eq '1' && !$isUnity); return $mag . $op; } @@ -752,11 +753,12 @@ sub string { # Always put signs on the right # sub TeX { - my $self = shift; - my $uop = $self->{def}; - my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - my $mag = $self->{op}->TeX($uop->{precedence}); - return $op if !$self->context->flags->get('showUnity') && $mag eq 1; + my $self = shift; + my $uop = $self->{def}; + my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); + my $mag = $self->{op}->TeX($uop->{precedence}); + my $isUnity = $self->context->flags->get('showUnity'); + $mag = '' if ($mag eq '1' && !$isUnity); return $mag . $op; } From 4f0b591b765886e7fb77bd6f60dd08f6c44f6d83 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 2 Sep 2025 09:09:47 -0500 Subject: [PATCH 033/111] Fix some issues with sample problem documentation generation. When I updated the sample problem documentation to use CodeMirror 6, apparently prettier accidentally ran on the `problem-template.mt` file (due to the format on save feature in my editor). Since that is a Mojolicious template file, prettier does not do the right thing, and messed that file up pretty well. So this fixes the resulting issues. Also, fix several typos in the sample problems that have a PODLINK or PROBLINK that have incorrect macro or sample problem file names. These cause "use of unitialized value" warnings when the `parse-problem-doc.pl` script is executed. These warnings also occur when loading these sample problems via webwork2. Finally, switch the sample problem templates to using the same version of Bootstrap that webwork2 uses. --- .../Geometry/LineSegmentGraphTool.pg | 2 +- .../sample-problems/Parametric/Spacecurve.pg | 4 +- .../ProblemTechniques/FormattingDecimals.pg | 2 +- .../ProblemTechniques/Multianswer.pg | 2 +- .../VectorCalc/VectorLineSegment1.pg | 2 +- .../VectorCalc/VectorLineSegment2.pg | 2 +- .../VectorCalc/VectorParametricLine.pg | 4 +- tutorial/templates/general-layout.mt | 4 +- tutorial/templates/problem-template.mt | 177 +++++++++--------- 9 files changed, 97 insertions(+), 102 deletions(-) diff --git a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg index 2f35a91963..d99cd17659 100644 --- a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg +++ b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg @@ -15,7 +15,7 @@ #:% type = Sample #:% subject = [geometry] #:% categories = [graph, graphtool] -#:% see_also = [VectorGraphTool.pg, TriangleGraphTool.pg, QuadrilateralGraphTool.pg] +#:% see_also = [GraphToolVectors.pg, TriangleGraphTool.pg, QuadrilateralGraphTool.pg] #:% section = preamble #: Load the PODLINK('parserGraphTool.pl') macro to be able to use the GraphTool. diff --git a/tutorial/sample-problems/Parametric/Spacecurve.pg b/tutorial/sample-problems/Parametric/Spacecurve.pg index 468939ba11..28e50b9ba3 100644 --- a/tutorial/sample-problems/Parametric/Spacecurve.pg +++ b/tutorial/sample-problems/Parametric/Spacecurve.pg @@ -23,8 +23,8 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserMultiAnswer.pl', 'PGcourse.pl'); $showPartialCorrectAnswers = 0; #:% section = setup -#: The PODLINK('MultiAnswer') is used to check the answers together since they -#: are interdependent. +#: The PODLINK('parserMultiAnswer.pl') is used to check the answers together +#: since they are interdependent. #: #: The option `singleResult => 1` is used since it doesn't make sense to say #: that `x(t)` is correct but `z(t)` is incorrect since they depend on one diff --git a/tutorial/sample-problems/ProblemTechniques/FormattingDecimals.pg b/tutorial/sample-problems/ProblemTechniques/FormattingDecimals.pg index fca50d6249..21b585e9a0 100644 --- a/tutorial/sample-problems/ProblemTechniques/FormattingDecimals.pg +++ b/tutorial/sample-problems/ProblemTechniques/FormattingDecimals.pg @@ -49,7 +49,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: `useBaseTenLog` flag is 0, and the `log` function is the natural logarithm. #: #: If a function for log base 2 (or another base) is needed see -#: PROBLINK('AddingFunctions.pg') for how to define and add a new function +#: PROBLINK('DefiningFunctions.pg') for how to define and add a new function #: to the context so that students can enter it in their answers. Context()->variables->set(x => { limits => [ 0.1, 4 ] }); diff --git a/tutorial/sample-problems/ProblemTechniques/Multianswer.pg b/tutorial/sample-problems/ProblemTechniques/Multianswer.pg index f6d5e0a8b0..89d770da17 100644 --- a/tutorial/sample-problems/ProblemTechniques/Multianswer.pg +++ b/tutorial/sample-problems/ProblemTechniques/Multianswer.pg @@ -17,7 +17,7 @@ #:% categories = [multianswer] #:% section = preamble -#: Load the PODLINK('parserMultianswer.pl') which provides the `MultiAnswer` +#: Load the PODLINK('parserMultiAnswer.pl') which provides the `MultiAnswer` #: method. DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserMultiAnswer.pl', 'PGcourse.pl'); diff --git a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg index 7feeae4a81..0210fc1ff3 100644 --- a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg +++ b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg @@ -20,7 +20,7 @@ #:% see_also = [VectorParametricLine.pg, VectorLineSegment2.pg] #:% section = preamble -#: The PODLINK('parseParametricLine.pl') macro provides the `ParametricLine` +#: The PODLINK('parserParametricLine.pl') macro provides the `ParametricLine` #: function which us used to check the answer. The #: PODLINK('parserMultiAnswer.pl') macro is needed since the answers are #: interdependent. diff --git a/tutorial/sample-problems/VectorCalc/VectorLineSegment2.pg b/tutorial/sample-problems/VectorCalc/VectorLineSegment2.pg index 2460987b44..15a2d8330f 100644 --- a/tutorial/sample-problems/VectorCalc/VectorLineSegment2.pg +++ b/tutorial/sample-problems/VectorCalc/VectorLineSegment2.pg @@ -20,7 +20,7 @@ #:% see_also = [VectorParametricLine.pg, VectorLineSegment1.pg] #:% section = preamble -#: The PODLINK('parseVectorUtils.pl') macro is loaded for the +#: The PODLINK('parserVectorUtils.pl') macro is loaded for the #: `non_zero_point3D` and `non_zero_vector3D` methods. DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserVectorUtils.pl', 'PGcourse.pl'); diff --git a/tutorial/sample-problems/VectorCalc/VectorParametricLine.pg b/tutorial/sample-problems/VectorCalc/VectorParametricLine.pg index e8b3598a0c..983b415116 100644 --- a/tutorial/sample-problems/VectorCalc/VectorParametricLine.pg +++ b/tutorial/sample-problems/VectorCalc/VectorParametricLine.pg @@ -20,8 +20,8 @@ #:% see_also = [VectorLineSegment1.pg, VectorLineSegment2.pg] #:% section = preamble -#: The PODLINK('parseVectorUtils.pl') macro provides the `non_zero_point3D` and -#: `non_zero_vector3D` methods. The macro PODLINK('parseParametricLine.pl') +#: The PODLINK('parserVectorUtils.pl') macro provides the `non_zero_point3D` and +#: `non_zero_vector3D` methods. The macro PODLINK('parserParametricLine.pl') #: provides the `ParametricLine` function used for the answer. DOCUMENT(); loadMacros( diff --git a/tutorial/templates/general-layout.mt b/tutorial/templates/general-layout.mt index 9ebfefcfea..3f50146abd 100644 --- a/tutorial/templates/general-layout.mt +++ b/tutorial/templates/general-layout.mt @@ -5,7 +5,7 @@ PG Sample Problems - + - + diff --git a/tutorial/templates/problem-template.mt b/tutorial/templates/problem-template.mt index 0320b7b34b..dfd9d3b798 100644 --- a/tutorial/templates/problem-template.mt +++ b/tutorial/templates/problem-template.mt @@ -1,116 +1,111 @@ - - - - <%= $filename %> - - - - + + + + <%= $filename %> - % # Default explanations % my $default = { % preamble => 'These standard macros need to be loaded.', % setup => - 'This perl code sets up the problem.', % statement => 'This is the problem statement in PGML.', % answer => 'This is - used for answer checking.', % solution => 'A solution should be provided here.' % }; + + + + - -
-
-
-

<%= $name %>

-

<%= $description %>

-
- +% # Default explanations +% my $default = { + % preamble => 'These standard macros need to be loaded.', + % setup => 'This perl code sets up the problem.', + % statement => 'This is the problem statement in PGML.', + % answer => 'This is used for answer checking.', + % solution => 'A solution should be provided here.' +% }; + + +
+
+
+

<%= $name %>

+

<%= $description %>

-
-
-

Complete Code

-

Download file: <%= $filename =%>

-
- % if (scalar(@{$metadata->{$filename}{macros}}) > 0 ) { + +
+
+
+

Complete Code

+

Download file: <%= $filename =%>

+
+ % if (scalar(@{$metadata->{$filename}{macros}}) > 0 ) {

POD for Macro Files

    - % for my $macro (@{$metadata->{$filename}{macros}}) { % if ($macro_locations->{$macro}) { -
  • <%= $macro =%>
  • - % } else { -
  • <%= $macro %>
  • - % } % } -
-
- %} % if ($metadata->{$filename}{related} && scalar(@{$metadata->{$filename}{related}}) > 0) { -
-

See Also

-
    - % for (@{$metadata->{$filename}{related}}) { -
  • - - <%= $metadata->{$_}{name} =%> -
  • + % for my $macro (@{$metadata->{$filename}{macros}}) { + % if ($macro_locations->{$macro}) { +
  • <%= $macro =%>
  • + % } else { +
  • <%= $macro %>
  • + % } % }
% } + % if ($metadata->{$filename}{related} && scalar(@{$metadata->{$filename}{related}}) > 0) { +
+

See Also

+
-
-

PG problem file

-

Explanation

-
- % for (@$blocks) { + % } +
+
+

PG problem file

+

Explanation

+
+ % for (@$blocks) {
-
-
+

<%= ucfirst($_->{section}) %>

- % if ($_->{doc}) { <%= $_->{doc} %> %} else { <%= $default->{$_->{section}} %> %} + % if ($_->{doc}) { + <%= $_->{doc} %> + % } else { + <%= $default->{$_->{section}} %> + % }
- % } -
+ % } +
+ + + - for (const btn of document.querySelectorAll('.clipboard-btn')) { - if (navigator.clipboard) - btn.addEventListener('click', () => navigator.clipboard.writeText(btn.dataset.code)); - else btn?.remove(); - } - - From ad4e2e5b06baaf2fb18c78f30e11ed4e39f9c54e Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 10 Aug 2025 08:40:52 -0500 Subject: [PATCH 034/111] Ignore the "separator" for parserCheckboxList.pl and parserRadioButtons.pl in TeX display mode. The default "separator" is a $BR which is defined to be `\\leavemode\\\\\\relax` if the display mode is TeX. This doesn't work if the CheckboxList or RadioButtons happen to be in a table. Furthermore, the separator doesn't make sense in TeX display mode in any case. So this makes it so that the separator is just not used in the TeX display mode. This is to address issue #1300. --- macros/parsers/parserCheckboxList.pl | 5 ++++- macros/parsers/parserRadioButtons.pl | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index 593192d271..8890859819 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -614,7 +614,10 @@ sub CHECKS { $checks[-1] .= "
"; } - return wantarray ? @checks : join($main::displayMode eq 'PTX' ? '' : $self->{separator}, @checks); + return + wantarray + ? @checks + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @checks); } sub protect { diff --git a/macros/parsers/parserRadioButtons.pl b/macros/parsers/parserRadioButtons.pl index 88a1122f28..122b0867dc 100644 --- a/macros/parsers/parserRadioButtons.pl +++ b/macros/parsers/parserRadioButtons.pl @@ -699,7 +699,9 @@ sub BUTTONS { . qq{data-feedback-btn-add-class="ms-3">$radio[0]}; $radio[-1] .= "
"; } - (wantarray) ? @radio : join(($main::displayMode eq 'PTX') ? '' : $self->{separator}, @radio); + wantarray + ? @radio + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @radio); } sub protect { From 8c7b390f6c934bcffd32d51d5e3e93f1fe5bc566 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 12 Aug 2025 17:57:13 -0500 Subject: [PATCH 035/111] Add a comment to the POD that the separator is only used for HTML display modes. --- macros/parsers/parserCheckboxList.pl | 3 ++- macros/parsers/parserRadioButtons.pl | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index 8890859819..69e4498668 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -137,7 +137,8 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the checkboxes. Default: $BR +Specifies the text to put between the checkboxes. Note that this is only used in +HTML display modes, and is ignored for PTX and TeX display modes. Default: $BR =item C choice >>> diff --git a/macros/parsers/parserRadioButtons.pl b/macros/parsers/parserRadioButtons.pl index 122b0867dc..83551ad62e 100644 --- a/macros/parsers/parserRadioButtons.pl +++ b/macros/parsers/parserRadioButtons.pl @@ -140,7 +140,9 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the radio buttons. Default: $BR +Specifies the text to put between the radio buttons. Note that this is only used +in HTML display modes, and is ignored for PTX and TeX display modes. +Default: $BR =item C choice >>> From 666a9badd4683f20c6329ae3c93d33d6db0d03aa Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 22 Jul 2025 18:27:07 -0500 Subject: [PATCH 036/111] Fix several issues with the `contextInteger.pl` macro. Several times in the file the variable `$self` is used where it is not defined. So if the `$self->Error` method is actually called it results in the error "can't call Error on an undefined value". Also the wrong counts are used in the `randomPrime` method resulting in unexpected behavior. Finally, if the `$end` parameter to the `_getPrimesInRange` is prime, then include it in the returned primes in the range. Thus, the `randomPrime` method will potentially be the `$end` parameter. I.e., `randomPrime(1, 11)` could return 11. This is to address the things discussed in #1296. --- macros/contexts/contextInteger.pl | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/macros/contexts/contextInteger.pl b/macros/contexts/contextInteger.pl index b9fbf2fc3c..bab7c7ef07 100644 --- a/macros/contexts/contextInteger.pl +++ b/macros/contexts/contextInteger.pl @@ -74,7 +74,7 @@ =head2 isPrime =cut -loadMacros('MathObjects.pl'); +loadMacros('PGbasicmacros.pl', 'MathObjects.pl'); sub _contextInteger_init { context::Integer::Init() } @@ -124,7 +124,7 @@ sub Init { sub _divisor { my $power = abs(shift); my $a = abs(shift); - $self->Error("Cannot perform divisor function on Zero") if $a == 0; + Value::Error('Cannot perform divisor function on Zero') if $a == 0; $result = 1; $sqrt_a = int(sqrt($a)); for (my $i = 2; $i < $sqrt_a; $i++) { @@ -146,9 +146,9 @@ sub _divisor { sub _getPrimesInRange { my $index = shift; my $end = shift; - $self->Error("Start of range must be a positive number.") if $index < 0; - $self->Error("End of range must be greater than or equal to 2") if $end < 2; - $self->Error("Start or range must be before end of range") if $index > $end; + Value::Error('Start of range must be a positive number.') if $index < 0; + Value::Error('End of range must be greater than or equal to 2') if $end < 2; + Value::Error('Start of range must be before end of range') if $index > $end; @primes = (); # consider switching to set upper limit and static array of primes @@ -156,7 +156,7 @@ sub _getPrimesInRange { push(@primes, 2) if $index <= 2; # ensure index is odd $index++ if $index % 2 == 0; - while ($index < $end) { + while ($index <= $end) { push(@primes, $index) if context::Integer::Function::Numeric::isPrime($index); $index += 2; } @@ -172,8 +172,8 @@ package context::Integer::Function::Numeric; # sub primeFactorization { my $a = abs(shift); - $self->Error("Cannot factor Zero into primes.") if $a == 0; - $self->Error("Cannot factor One into primes.") if $a == 1; + Value::Error('Cannot factor Zero into primes.') if $a == 0; + Value::Error('Cannot factor One into primes.') if $a == 1; my %factors; my $n = $a; @@ -200,7 +200,7 @@ sub primeFactorization { # sub phi { my $a = abs(shift); - $self->Error("Cannot phi on Zero.") if $a == 0; + Value::Error('Cannot phi on Zero.') if $a == 0; $result = $a; $n = $a; for (my $i = 2; ($i**2) <= $n; $i++) { @@ -235,9 +235,8 @@ sub isPrime { sub randomPrime { my ($start, $end) = @_; my @primes = context::Integer::_getPrimesInRange($start, $end); - $self->Error("Could not find any prime numbers in range.") if $#primes == 0; - my $primeIndex = $main::PG_random_generator->random(0, ($#primes - 1), 1); - return $primes[$primeIndex]; + Value::Error('Could not find any prime numbers in range.') unless @primes; + return main::list_random(@primes); } package context::Integer::Function::Numeric2; From fae6c58a3da6060bd313567feb039cdd70f5ead0 Mon Sep 17 00:00:00 2001 From: John E Date: Tue, 16 Sep 2025 14:32:02 -0400 Subject: [PATCH 037/111] propose spelling fix --- htdocs/js/AppletSupport/ww_applet_support.js | 2 +- macros/core/PGbasicmacros.pl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index 4dfd18a01e..395969bf3f 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -108,7 +108,7 @@ class ww_applet { // Communication with the applet is in plain text, not in base64 code. if (state) { - if (this.debug) console.log(`${this.appletName}: Obtain state from calling paramater`); + if (this.debug) console.log(`${this.appletName}: Obtain state from calling parameter`); } else { if (this.debug) console.log(`${this.appletName}: Obtain state from ${this.stateInput}`); // Hidden answer box preserving applet state diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 57f62a3155..2332a760fb 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -223,7 +223,7 @@ =head2 Answer blank macros: When entering radio buttons using the "NAMED" format, you should use NAMED_ANS_RADIO button for the first button and then use NAMED_ANS_RADIO_EXTENSION for the remaining buttons. NAMED_ANS_RADIO requires a -matching answer evalutor, while NAMED_ANS_RADIO_EXTENSION does not. The name +matching answer evaluator, while NAMED_ANS_RADIO_EXTENSION does not. The name used for NAMED_ANS_RADIO_EXTENSION should match the name used for NAMED_ANS_RADIO (and the associated answer evaluator). @@ -3228,7 +3228,7 @@ sub tag { ' ', map { ($_ =~ s/_/-/gr) . (defined $attributes{$_} ? ('="' . encode_pg_and_html($attributes{$_})) . '"' : '') - } + } keys %attributes ); From 44e506edd515e1a080b6072557bca9d9ad7e52ff Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 15 Jul 2025 09:03:33 -0500 Subject: [PATCH 038/111] Update third party npm dependencies. The following dependencies are updated: * jsxgraph: 1.10.1 -> 1.11.1 * plotly.js-dist-min: 2.23.0 -> 3.1.0 * sortablejs: 1.15.2 -> 1.15.6 * autoprefixer: 10.4.19 -> 10.4.21 * chokidar: 3.6.0 -> 4.0.3 * cssnano: 6.1.2 -> 7.1.0 * postcss: 8.4.38 -> 8.5.6 * prettier: 3.2.5 -> 3.6.2 * rtlcss: 4.1.1 -> 4.3.0 * sass: 1.75.0 -> 1.90.0 * terser: 5.30.4 -> 5.43.1 * yargs: 17.7.2 -> 18.0.0 All libraries are now at their latest version. Note that the only change to the PG code required is a minor change to the generate-assets.js script due to a change in the usage of yargs. --- htdocs/generate-assets.js | 5 +- htdocs/js/GraphTool/graphtool.js | 2 +- htdocs/js/MathQuill/mqeditor.js | 10 +- htdocs/package-lock.json | 2382 ++++++++++++++++++------------ htdocs/package.json | 24 +- 5 files changed, 1436 insertions(+), 987 deletions(-) diff --git a/htdocs/generate-assets.js b/htdocs/generate-assets.js index 91647b2c6f..445eff3677 100755 --- a/htdocs/generate-assets.js +++ b/htdocs/generate-assets.js @@ -2,7 +2,8 @@ /* eslint-env node */ -const yargs = require('yargs'); +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); const chokidar = require('chokidar'); const path = require('path'); const { minify } = require('terser'); @@ -14,7 +15,7 @@ const postcss = require('postcss'); const rtlcss = require('rtlcss'); const cssMinify = require('cssnano'); -const argv = yargs +const argv = yargs(hideBin(process.argv)) .usage('$0 Options') .version(false) .alias('help', 'h') diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index dcfcfebf81..8edb82c3c7 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -198,7 +198,7 @@ window.graphTool = (containerId, options) => { } if (this.visProp.useunicodeminus) labelText = labelText.replace(/-/g, '\u2212'); - return addTeXDelims ?? this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + return (addTeXDelims ?? this.visProp.label.usemathjax) ? `\\(${labelText}\\)` : labelText; }; gt.board.defaultAxes.x.defaultTicks.generateLabelText = generateLabelText; diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 7a950370d0..6103311c84 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -550,7 +550,15 @@ toolbarEnabled = !toolbarEnabled; localStorage.setItem('MQEditorToolbarEnabled', toolbarEnabled); if (!toolbarEnabled && answerQuill.toolbar) toolbarRemove(); - menu.hide(); + // Bootstrap tries to focus the triggering element after hiding the menu. However, the menu gets + // disposed of and the hidden link which is the triggering element removed too quickly in the + // hidden.bs.dropdown event, and that causes an exception. So ignore that exception so that the + // answerQuill textarea is focused instead. + try { + menu.hide(); + } catch { + /* ignore */ + } answerQuill.textarea.focus(); }, { once: true } diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index f0d707f6d4..e513ce6e88 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -8,36 +8,33 @@ "license": "GPL-2.0+", "dependencies": { "@openwebwork/mathquill": "^0.11.0", - "jsxgraph": "^1.10.1", + "jsxgraph": "^1.11.1", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.1.0", + "sortablejs": "^1.15.6" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.4.21", + "chokidar": "^4.0.3", + "cssnano": "^7.1.0", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "rtlcss": "^4.3.0", + "sass": "^1.90.0", + "terser": "^5.43.1", + "yargs": "^18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -45,40 +42,35 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -90,20 +82,322 @@ "integrity": "sha512-w5AlhsnreqCFYePP2V8Ve/hxA8KZvYmyHD5uehCD07PDp/HiL3DSo4mtAyLV7pWTXUoPVLFdnZK/WmrSYs+UdQ==", "license": "MPL-2.0" }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=10.13.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -112,46 +406,35 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "funding": [ { @@ -167,12 +450,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -185,26 +469,20 @@ "postcss": "^8.1.0" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -213,9 +491,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -231,11 +509,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -248,13 +527,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -263,9 +544,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true, "funding": [ { @@ -280,89 +561,69 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=20" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, + "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -371,10 +632,11 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -387,12 +649,13 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -400,10 +663,11 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -416,6 +680,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -424,79 +689,82 @@ } }, "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz", + "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.8", + "lilconfig": "^3.1.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz", + "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.6", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/csso": { @@ -504,6 +772,7 @@ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -517,6 +786,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "dev": true, + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -530,13 +800,29 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -556,13 +842,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -574,10 +862,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -588,22 +877,25 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", - "dev": true + "version": "1.5.183", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz", + "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -612,10 +904,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -625,6 +918,8 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -637,6 +932,7 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, @@ -645,92 +941,66 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", - "dev": true + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -743,6 +1013,8 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.12.0" } @@ -750,13 +1022,17 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", + "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", "license": "(MIT OR LGPL-3.0-or-later)", + "dependencies": { + "jsxgraph": "^1.11.0-beta2" + }, "engines": { "node": ">=0.6.0" } @@ -765,6 +1041,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -775,21 +1052,24 @@ "node_modules/jszip-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz", - "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==" + "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==", + "license": "(MIT OR GPL-3.0)" }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -801,24 +1081,42 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -834,26 +1132,27 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -863,6 +1162,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -873,19 +1173,23 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=8.6" }, @@ -894,14 +1198,15 @@ } }, "node_modules/plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.1.0.tgz", + "integrity": "sha512-aihvA/+SnwEQxSufaPn8AWDUzdHFAbsCk2+w/IJResDafK3E2tvCvzW+ZV6JlMciJc7hQ3kCILS5Ao22OZ6kWA==", + "license": "MIT" }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -917,396 +1222,427 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" } }, "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz", + "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^7.0.5" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "dev": true, + "license": "MIT", "dependencies": { "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -1316,47 +1652,51 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.0" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -1370,12 +1710,14 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1387,31 +1729,25 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dev": true, + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -1428,16 +1764,18 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -1445,32 +1783,46 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" }, "node_modules/sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "license": "MIT" }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1480,6 +1832,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -1489,34 +1842,43 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-json-comments": { @@ -1524,6 +1886,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1532,40 +1895,42 @@ } }, "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, + "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -1573,13 +1938,14 @@ } }, "node_modules/terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -1594,13 +1960,16 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -1609,9 +1978,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -1627,9 +1996,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -1644,17 +2014,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -1665,47 +2036,48 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } } }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, @@ -1715,16 +2087,10 @@ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1732,15 +2098,15 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1752,63 +2118,155 @@ "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.0.tgz", "integrity": "sha512-w5AlhsnreqCFYePP2V8Ve/hxA8KZvYmyHD5uehCD07PDp/HiL3DSo4mtAyLV7pWTXUoPVLFdnZK/WmrSYs+UdQ==" }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true + "@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "optional": true, + "requires": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1", + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + } + }, + "@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "dev": true, + "optional": true }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true }, "autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" } }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1820,20 +2278,21 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "optional": true, "requires": { "fill-range": "^7.1.1" } }, "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" } }, "buffer-from": { @@ -1855,53 +2314,31 @@ } }, "caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true }, "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" } }, "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "requires": { - "color-name": "~1.1.4" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -1909,9 +2346,9 @@ "dev": true }, "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true }, "core-util-is": { @@ -1927,9 +2364,9 @@ "requires": {} }, "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "requires": { "boolbase": "^1.0.0", @@ -1940,19 +2377,19 @@ } }, "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "requires": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true }, "cssesc": { @@ -1962,57 +2399,57 @@ "dev": true }, "cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz", + "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==", "dev": true, "requires": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.8", + "lilconfig": "^3.1.3" } }, "cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz", + "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.6", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" } }, "cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "dev": true, "requires": {} }, @@ -2043,6 +2480,13 @@ } } }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true + }, "dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2070,9 +2514,9 @@ } }, "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "requires": { "dom-serializer": "^2.0.0", @@ -2081,15 +2525,15 @@ } }, "electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", + "version": "1.5.183", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz", + "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==", "dev": true }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "entities": { @@ -2099,9 +2543,9 @@ "dev": true }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, "fill-range": { @@ -2109,6 +2553,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "optional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -2119,27 +2564,17 @@ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } + "get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true }, "immediate": { "version": "3.0.6", @@ -2147,9 +2582,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", "dev": true }, "inherits": { @@ -2157,32 +2592,19 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "optional": true, "requires": { "is-extglob": "^2.1.1" } @@ -2191,7 +2613,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "1.0.0", @@ -2199,9 +2622,12 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==" + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", + "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", + "requires": { + "jsxgraph": "^1.11.0-beta2" + } }, "jszip": { "version": "3.10.1", @@ -2228,9 +2654,9 @@ } }, "lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true }, "lodash.memoize": { @@ -2246,27 +2672,39 @@ "dev": true }, "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "optional": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "normalize-range": { @@ -2290,268 +2728,272 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "dev": true, + "optional": true }, "plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.1.0.tgz", + "integrity": "sha512-aihvA/+SnwEQxSufaPn8AWDUzdHFAbsCk2+w/IJResDafK3E2tvCvzW+ZV6JlMciJc7hQ3kCILS5Ao22OZ6kWA==" }, "postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, "postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz", + "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "dev": true, - "requires": {} + "requires": { + "postcss-selector-parser": "^7.1.0" + } }, "postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "dev": true, "requires": {} }, "postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^7.0.5" } }, "postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" } }, "postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "dev": true, "requires": { "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.16" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" } }, "postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "dev": true, "requires": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -2559,22 +3001,22 @@ } }, "postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.0" } }, "postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.0" } }, "postcss-value-parser": { @@ -2584,9 +3026,9 @@ "dev": true }, "prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true }, "process-nextick-args": { @@ -2609,24 +3051,15 @@ } }, "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true }, "rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -2641,25 +3074,32 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "requires": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "@parcel/watcher": "^2.4.1", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" } }, + "sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==" }, "source-map": { "version": "0.6.1", @@ -2668,9 +3108,9 @@ "dev": true }, "source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, "source-map-support": { @@ -2692,23 +3132,23 @@ } }, "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" } }, "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" } }, "strip-json-comments": { @@ -2718,38 +3158,38 @@ "dev": true }, "stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" } }, "svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" } }, "terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -2767,18 +3207,19 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "optional": true, "requires": { "is-number": "^7.0.0" } }, "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, "util-deprecate": { @@ -2787,14 +3228,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } }, "y18n": { @@ -2804,24 +3245,23 @@ "dev": true }, "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "requires": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" } }, "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true } } diff --git a/htdocs/package.json b/htdocs/package.json index 2109abb0ff..2410085950 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -14,22 +14,22 @@ }, "dependencies": { "@openwebwork/mathquill": "^0.11.0", - "jsxgraph": "^1.10.1", + "jsxgraph": "^1.11.1", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.1.0", + "sortablejs": "^1.15.6" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.4.21", + "chokidar": "^4.0.3", + "cssnano": "^7.1.0", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "rtlcss": "^4.3.0", + "sass": "^1.90.0", + "terser": "^5.43.1", + "yargs": "^18.0.0" }, "browserslist": [ "last 10 Chrome versions", From a0b40c56c5973975debf633b2c1711e42418d392 Mon Sep 17 00:00:00 2001 From: John E Date: Wed, 17 Sep 2025 16:12:27 -0400 Subject: [PATCH 039/111] fix some typos --- tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg | 2 +- tutorial/sample-problems/README.md | 2 +- tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg index d99cd17659..97670090ed 100644 --- a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg +++ b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg @@ -29,7 +29,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #: `($sx1, $sy1)` and `($sx2, $sy2)`. For a correct answer, the line segment #: must have the given the endpoints and be solid. #: -#: Mulitple line segments can be added to the graph tool, however, if a +#: Multiple line segments can be added to the graph tool, however, if a #: triangle or quadrilateral is desired, see PROBLINK('TriangleGraphTool.pg') #: and PROBLINK('QuadrilateralGraphTool.pg') for more appropriate tools. $sx1 = random(-8, 8); diff --git a/tutorial/sample-problems/README.md b/tutorial/sample-problems/README.md index 64deacc459..4b8b56ce11 100644 --- a/tutorial/sample-problems/README.md +++ b/tutorial/sample-problems/README.md @@ -40,7 +40,7 @@ Here's an example: #:% categories = [graph] ``` -## Structure of the code documenation +## Structure of the code documentation The comments that are associated with each file in on the WeBWorK wiki have been embedded as formatted diff --git a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg index 0210fc1ff3..06478277b7 100644 --- a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg +++ b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg @@ -1,5 +1,5 @@ ## DESCRIPTION -## A Vector-value parametric line segment with a general paramterization +## A Vector-value parametric line segment with a general parameterization ## ENDDESCRIPTION ## DBsubject(WeBWorK) From 166606e62f54039f50d0d59ca8361accfe445e67 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 19 Sep 2025 16:28:27 -0400 Subject: [PATCH 040/111] Tighten syntactic checks, combine adjacent equal elements, add conversion to molecular form, and allow comparisons based on molecular forms, better handling of charges --- macros/contexts/contextReaction.pl | 541 ++++++++++++++++++++++++----- 1 file changed, 456 insertions(+), 85 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index d60384f454..d315073ab5 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -25,14 +25,15 @@ =head1 DESCRIPTION $R = Formula("4P + 5O_2 --> 2P_2O_5"); -Ions can be specified using C<^> to produce superscripts, as in C or -C. Note that the charge may be listed with prefix notation (+2) -or postfix notation (2+). By default, TeX will display only the sign of the -charge of singly charged ions, e.g., C and C. However, the context -flag C<< showUnity=>1 >> can be used to direct TeX to use an alternative -notation for singly charged ions, by which the numerical magnitude (1) is -displayed along with the sign of the charge, e.g., C and C. The -default value of C<> is zero. +Ions can be specified using C<^> to produce superscripts, as in +C or C. Note that the charge may be listed with prefix +notation (+2) or postfix notation (2+). By default, TeX will display +only the sign of the charge of singly charged ions, e.g., C and +C. However, the context flag C<< showUnity=>1 >> can be used to +direct TeX to use an alternative notation for singly charged ions, by +which the numerical magnitude (1) is displayed along with the sign of +the charge, e.g., C and C. The default value of +C<> is zero. States can be appended to compounds, as in C. So you can make reactions like the following: @@ -48,9 +49,9 @@ =head1 DESCRIPTION These can be used in reactions as with any other compound. -Reactions know how to create their own TeX versions (via C<< $R->TeX >>), and -know how to check student answers (via C<< $R->cmp >>), just like any other -MathObject. +Reactions know how to create their own TeX versions (via C<< $R->TeX >>), +and know how to check student answers (via C<< $R->cmp >>), just like +any other MathObject. The Reaction Context also allows you to create parts of reactions. E.g., you could create @@ -112,6 +113,48 @@ =head1 DESCRIPTION to allow a state of C<(x)> for a compound. +By default, the Reaction context checks the student answer against the +correct answer in the form the correct answer is given (molecular or +condensed structural form). If you want to allow either form, then +you can set the flag C to C<1>, in which case, +the student's answwer is compared first to the original correct answer +and then to its molecular form. For example, if the correct answer is +`(NH_4)_3PO_4`, then a student answer of `N_3H_12PO_4` would also be +considered correct when this flag is set to C<1>. + +You can convert a compound (or every term of a complete reaction) to +molecular form using the C method of any Reaction +object. For example + + $R = Compute("CH_3CH_2CH_3")->molecularForm; + +would make C<$R> be the quivalent of C. + +Note that student answers aren't reduced to molecular form when +C is true. If you want to allow students to +enter compounds in any form, you can set the C flag +to C<1>, in which case both the correct and student answers are +formatted in molecular form internally before they are compared. + +The molecular form will have the elements in the order that they first +appeared in the compound, and the student must use the same order for +their answer to be considered correct. If you want to allow the +student to enter the elements in any order, you can set the +C flag to C<0> which will cause the molecular form +to be in alphabetical order. That way, if the student has the correct +number of each element, in any order, the two answers will match. + +The Reaction context can perform an automatic reduction that combines +adjacent elements that are the same. This is controlled by the +C flag, which is C<1> by default. For +example, + + $R1 = Compute("COOH"); + $R2 = Compute("CO_2H"); + +would equal each other when the flag is set, even when +C is not set. + =cut ###################################################################### @@ -146,19 +189,16 @@ sub Init { $context->functions->clear(); $context->strings->clear(); $context->constants->clear(); - $context->lists->clear(); - $context->lists->add( + $context->lists->are( 'List' => { class => 'context::Reaction::List::List', open => '', close => '', separator => ' + ' }, 'Complex' => { class => 'context::Reaction::List::Complex', open => '[', close => ']', separator => '' }, ); - $context->parens->clear(); - $context->parens->add( + $context->parens->are( '(' => { close => ')', type => 'List', formList => 1, removable => 1 }, '{' => { close => '}', type => 'List', removable => 1 }, '[' => { close => ']', type => 'Complex' }, ); - $context->operators->clear(); - $context->operators->set( + $context->operators->are( '-->' => { precedence => 1, associativity => 'left', @@ -263,9 +303,15 @@ sub Init { ); $context->variables->add(map { $_ => $STATE } ('(aq)', '(s)', '(l)', '(g)', '(ppt)')); $context->reductions->clear(); - $context->flags->set(showUnity => 0); - $context->flags->set(studentsMustUseStates => 1); - $context->flags->set(reduceConstants => 0); + $context->flags->set( + showUnity => 0, + studentsMustUseStates => 1, + reduceConstants => 1, + combineAdjacentElements => 1, + acceptMolecularForm => 0, + compareMolecular => 0, + keepElementOrder => 1, + ); $context->{parser}{Number} = 'context::Reaction::Number'; $context->{parser}{Variable} = 'context::Reaction::Variable'; $context->{parser}{Formula} = 'context::Reaction'; @@ -282,14 +328,14 @@ sub Init { # Handle postfix - and + in superscripts # sub Op { - my $self = shift; - my $name = shift; - my $ref = $self->{ref} = shift; + my $self = shift; + my $name = shift; + my $ref = $self->{ref} = shift; + my $context = $self->{context}; + my $op; + ($name, $op) = $context->operators->resolve($name); + ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($self->state eq 'operand') { - my $context = $self->{context}; - my $op; - ($name, $op) = $context->operators->resolve($name); - ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($op->{type} eq 'both' && $context->{operators}{"p$name"} && $self->top->{value}->class eq 'Number' @@ -297,23 +343,32 @@ sub Op { && $self->prev->{name} eq '^') { ($name, $op) = $context->operators->resolve("p$name"); - $self->pushOperand($self->Item('UOP')->new($self, $name, $self->pop->{value}, $ref)); + $self->pushCharge($name, $self->pop->{value}); return; } - } elsif ($self->state eq 'operator' && $self->top->{name} =~ m/^u/) { - $self->SimpleCharge; } $self->SUPER::Op($name, $ref); } # -# Handle superscripts of just + or - +# Handle superscripts of just + or - or postscript + or - # sub Close { my $self = shift; my $type = shift; my $ref = $self->{ref} = shift; $self->SimpleCharge if $self->state eq 'operator' && $self->top->{name} =~ m/^u/; + my $name = $self->top->{name}; + my $context = $self->{context}; + if ($self->state eq 'operator' + && $context->{operators}{"p$name"} + && $self->prev->{type} eq 'operand' + && $self->top(-2)->{type} eq 'open') + { + $self->pop; + ($name) = $context->operators->resolve("p$name"); + $self->pushCharge($name, $self->pop->{value}); + } $self->SUPER::Close($type, $ref); } @@ -327,11 +382,18 @@ sub pushOperand { $self->push({ type => 'operand', ref => $self->{ref}, value => $value }); } +sub pushCharge { + my $self = shift; + my $op = shift; + my $value = shift; + $self->pushOperand($self->Item('UOP')->new($self, $op, $value, $self->{ref})); +} + sub SimpleCharge { my $self = shift; my $top = $self->pop; my $one = $self->Item('Number')->new($self, 1, $self->{ref}); - $self->pushOperand($self->Item('UOP')->new($self, $top->{name}, $one, $self->{ref})); + $self->pushCharge($top->{name}, $one); } # @@ -346,11 +408,20 @@ sub compare { } # -# Don't allow evaluation +# Don't allow evaluation except for numbers # sub eval { my $self = shift; - $self->Error("Can't evaluate " . $self->TYPE); + return $self->Package('Real')->new($self->{tree}{value}) if $self->{tree}->class eq 'Number'; + $self->Error("Can't evaluate " . $self->{tree}->TYPE); +} + +# +# Get the molecular form for everything in the reaction +# +sub molecularForm { + my $self = shift; + return $self->new($self->{tree}->molecularForm); } # @@ -388,12 +459,71 @@ sub typeMatch { return 1; } +###################################################################### +# +# Common functions to multiple classes +# +package context::Reaction::common; + +# +# Shorthand for creating parser items +# +sub ITEM { + my $self = shift; + return $self->Item(shift)->new($self->{equation}, @_); +} + +# +# Convert an item to a compound +# +sub COMPOUND { + my $self = shift; + return $self->ITEM('BOP', ' ', $self->ITEM('Number', 1), $self->copy); +} + +# +# Make a charge item from a number +# +sub CHARGE { + my ($self, $n) = @_; + return $self->ITEM('UOP', $n < 0 ? 'u-' : 'u+', $self->ITEM('Number', CORE::abs($n))); +} + +# +# Add a term into an item's data +# +sub combineData { + my ($self, $other) = @_; + return unless $other->{order}; + my ($order, $elements) = ($self->{order}, $self->{elements}); + for my $element (@{ $other->{order} }) { + if (!$elements->{$element}) { + push(@$order, $element); + $elements->{$element} = 0; + } + $elements->{$element} += $other->{elements}{$element}; + } + $self->{charge} += $other->{charge}; + $self->{factor} *= $other->{factor}; +} + +# +# Molecular form is same as original +# +sub molecularForm { shift->copy } + +# +# Default is not a checmicla or sum +# +sub isChemical {0} +sub isSum {0} + ###################################################################### # # The replacement for the Parser:Number class # package context::Reaction::Number; -our @ISA = ('Parser::Number'); +our @ISA = ('Parser::Number', 'context::Reaction::common'); # # Equivalent is equal @@ -405,8 +535,6 @@ sub equivalent { return $self->eval == $other->eval; } -sub isChemical {0} - sub class {'Number'} sub TYPE {'a Number'} @@ -415,7 +543,21 @@ sub equivalent { # The replacement for Parser::Variable. We hold the elements here. # package context::Reaction::Variable; -our @ISA = ('Parser::Variable'); +our @ISA = ('Parser::Variable', 'context::Reaction::common'); + +# +# Save the element data +# +sub new { + my $self = shift->SUPER::new(@_); + if ($self->type ne 'State') { + $self->{order} = [ $self->{name} ]; + $self->{elements} = { $self->{name} => 1 }; + $self->{charge} = 0; + $self->{factor} = 1; + } + return $self; +} # # Two elements are equivalent if their names are equal @@ -457,7 +599,7 @@ sub TYPE { # are subclasses of this). # package context::Reaction::BOP; -our @ISA = ('Parser::BOP'); +our @ISA = ('Parser::BOP', 'context::Reaction::common'); # # Binary operators produce chemicals (unless overridden, as in arrow) @@ -466,6 +608,17 @@ package context::Reaction::BOP; sub eval { context::Reaction::eval(@_) } +# +# Form molecular form by combining molecular forms of the operands +# +sub molecularForm { + my $self = shift; + $self = bless {%$self}, ref($self); + $self->{lop} = $self->{lop}->molecularForm; + $self->{rop} = $self->{rop}->molecularForm; + return $self; +} + # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -473,13 +626,19 @@ package context::Reaction::BOP; sub equivalent { my $self = shift; my $other = shift; - # return $other->equivalent($self) - # if $other->class eq 'BOP' && $other->{rop}->type eq 'State' && $self->{rop}->type ne 'State'; - return 0 unless $other->class eq 'BOP'; - return 0 unless $self->{bop} eq $other->{bop}; + return 0 unless $other->class eq 'BOP' && $self->{bop} eq $other->{bop}; return $self->{lop}->equivalent($other->{lop}) && $self->{rop}->equivalent($other->{rop}); } +# +# Check for equivalence using string representations of compounds +# +sub equivalentTo { + my ($self, $other, $states) = @_; + $other = $other->string; + return $self->string eq $other || (!$states && $self->{hasState} && $self->{lop}->string eq $other); +} + ###################################################################### # # Implements the --> operator @@ -497,10 +656,11 @@ package context::Reaction::BOP::arrow; # sub _check { my $self = shift; - $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $self->{lop}->TYPE) - unless $self->{lop}->isChemical; - $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $self->{rop}->TYPE) - unless $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $lop->TYPE) + unless $lop->isChemical || $lop->isSum; + $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $rop->TYPE) + unless $rop->isChemical || $rop->isSum; $self->{type} = $REACTION->{type}; } @@ -519,8 +679,9 @@ package context::Reaction::BOP::add; # sub _check { my $self = shift; - $self->Error("Can't add %s and %s", $self->{lop}->TYPE, $self->{rop}->TYPE) - unless $self->{lop}->isChemical && $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + unless ($lop->isChemical || $lop->isSum) && ($rop->isChemical || $rop->isSum); $self->SUPER::_check(@_); } @@ -539,6 +700,11 @@ sub equivalent { sub TYPE {'a sum of Compounds'} +# +# It is a sum +# +sub isSum {1} + ###################################################################### # # Implements concatenation, which produces compounds or integer @@ -553,55 +719,186 @@ package context::Reaction::BOP::multiply; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + $self->Error("Can't use a numeric prefix with %s", $rop->TYPE) unless ($lop->class eq 'Number' || $lop->isChemical) && $rop->isChemical; $self->Error("Compound already has a state") if $lop->{hasState} && $rop->type eq 'State'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $lop->{hasState} && $rop->isChemical; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $rop->{hasState} && $lop->isChemical; $self->Error("Can't combine %s with %s", $lop->{name}, $rop->TYPE) if $lop->type eq 'Constant'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->{name}) if $rop->type eq 'Constant'; + $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) + if $lop->class ne 'Number' && $rop->{hasNumber}; $self->{type} = $COMPOUND->{type}; - if ($self->{lop}{hasNumber}) { - my $n = $self->{lop}{lop}; - $self->{lop}{lop} = $self->{lop}{rop}; - $self->{lop}{rop} = $self->{rop}; - $self->{lop}{hasState} = 1 if $self->{rop}->type eq 'State'; - delete $self->{lop}{hasNumber}; - $self->{rop} = $self->{lop}; - $self->{lop} = $n; + $self->getCompound; + $self->{hasState} = 1 if $rop->type eq 'State'; + $self->{hasNumber} = 1 if $lop->type eq 'Number' || $lop->{hasNumber}; + + if ($lop->{hasNumber} && $rop->type ne 'State') { + my $n = $lop->{lop}; + $lop->{lop} = $lop->{rop}; + $lop->{rop} = $rop; + delete $lop->{hasNumber}; + $rop = $self->{rop} = $lop; + $lop = $self->{lop} = $n; } - $self->{hasState} = 1 if $self->{rop}->type eq 'State'; - $self->{hasNumber} = 1 if $self->{lop}->class eq 'Number'; + + $self->combineAdjacentNumbers if $self->context->flag('reduceConstants'); + $self->combineAdjacentElements if $self->context->flag('combineAdjacentElements'); } # -# Remove ground state, if needed +# Get the order/element/charge data for a compound +# +sub getCompound { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = $lop->type eq 'Number' ? $lop->eval : 1; + $self->combineData($lop); + $self->combineData($rop); +} + +# +# Combine adjacent numbers (e.g., 2(3CO)) +# +sub combineAdjacentNumbers { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + if ($rop->{hasNumber} && $lop->class eq 'Number') { + if ($rop->{hasState}) { + $lop->{value} *= $rop->{lop}{lop}{value}; + $rop->{lop} = $rop->{lop}{rop}; + } else { + $lop->{value} *= $rop->{lop}{value}; + $rop = $self->{rop} = $rop->{rop}; + } + } +} + +# +# Combine adjacent elements if they are the same +# +sub combineAdjacentElements { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + return unless $lop->{order} && $rop->{order} && scalar(@{ $rop->{order} }) == 1; + my $name = $rop->{order}[0]; + if ($lop->type eq 'Compound') { + $self->combineCompound($lop, $rop); + } else { + $self->combineSimple($lop, $rop); + } +} + +# +# Combine elements when the left operand is a compound +# +sub combineCompound { + my ($self, $lop, $rop) = @_; + my $last = $lop->{rop}; + my $name = $rop->{order}[0]; + return unless $last->{order} && scalar(@{ $last->{order} }) == 1 && $last->{order}[0] eq $name; + $self->{lop} = $lop->{lop}; + my $n = $self->ITEM('Number', $last->{elements}{$name} + $rop->{elements}{$name}); + $self->{rop} = $self->ITEM('BOP', '_', $self->ITEM('Variable', $name), $n); + my $charge = $last->{charge} + $rop->{charge}; + $self->{rop} = $self->ITEM('BOP', '^', $self->{rop}, $self->CHARGE($charge)) if $charge; +} + +# +# Combine elements when the left is not a compound +# +sub combineSimple { + my ($self, $lop, $rop) = @_; + my $name = $rop->{order}[0]; + return unless scalar(@{ $lop->{order} }) == 1 && $lop->{order}[0] eq $name; + my $n = $self->ITEM('Number', $lop->{elements}{$name} + $rop->{elements}{$name}); + my $element = $self->ITEM('Variable', $name); + my $charge = $lop->{charge} + $rop->{charge}; + if ($charge) { + $self->mutate('^', $self->ITEM('BOP', '_', $element, $n), $self->CHARGE($charge)); + } else { + $self->mutate('_', $element, $n); + } +} + +# +# Mutate the BOP into a different one +# +sub mutate { + my $self = shift; + my $other = $self->ITEM('BOP', @_); + delete $self->{$_} for (keys %$self); + $self->{$_} = $other->{$_} for (keys %$other); + bless $self, ref($other); +} + +# +# Create the molecular form for a compound +# +sub molecularForm { + my $self = shift; + my $elements = $self->{elements}; + my $state = $self->{hasState} ? $self->{rop} : undef; + my $number = $self->{factor} > 1 ? $self->ITEM('Number', $self->{factor}) : undef; + my @order = @{ $self->{order} }; + @order = main::lex_sort(@order) if !$self->context->flag('keepElementOrder'); + my $compound; + for (@order) { + my $term = $self->ITEM('Variable', $_); + $term = $self->ITEM('BOP', '_', $term, $self->ITEM('Number', $elements->{$_})) if $elements->{$_} > 1; + $compound = $compound ? $self->ITEM('BOP', ' ', $compound, $term) : $term; + } + $compound = $self->ITEM('BOP', '^', $compound, $self->CHARGE($self->{charge})) if $self->{charge}; + $compound = $self->ITEM('BOP', ' ', $number, $compound) if $number; + $compound = $self->ITEM('BOP', ' ', $compound, $state->copy) if $state; + return $compound; +} + +# +# Check for equivalence of two compounds +# removing states if needed and student answers can be without them # sub equivalent { my $self = shift; my $other = shift; if ($other->class eq 'List') { my $parens = $self->context->parens->get('('); - my $list = $self->Item('List')->new($self->{equation}, [$self], 1, $parens); + my $list = $self->ITEM('List', $self->{equation}, [$self], 1, $parens); return $list->equivalent($other); } - my $states = $self->context->flags->get('studentsMustUseStates'); - my $equiv = $self->SUPER::equivalent($other); - return ($equiv || !$self->{hasState} || $states ? $equiv : $self->{lop}->equivalent($other)); + return 0 unless $other->isChemical; + $other = $other->COMPOUND unless $other -type eq 'Compound'; + my $states = $self->context->flag('studentsMustUseStates'); + my $molecular = $self->context->flag('compareMolecular'); + my $both = $self->context->flag('acceptMolecularForm'); + return $self->molecularForm->equivalentTo($other->molecularForm, $states) if $molecular; + return $self->equivalentTo($other, $states) || ($both && $self->molecularForm->equivalentTo($other, $states)); } # # No space in output for implied multiplication +# and add parentheses for compounds that need it # sub string { my $self = shift; - return $self->{lop}->string . $self->{rop}->string; + my ($l, $r) = ($self->{lop}->string, $self->{rop}->string); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } sub TeX { my $self = shift; - return $self->{lop}->TeX . $self->{rop}->TeX; + my ($l, $r) = ($self->{lop}->TeX, $self->{rop}->TeX); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } # @@ -609,7 +906,9 @@ sub TeX { # sub TYPE { my $self = shift; - return $self->{rop}->TYPE eq 'a state' ? $self->{lop}->TYPE . ' with state' : 'a compound'; + return $self->{rop}->TYPE eq 'a state' + ? $self->{lop}->TYPE . ' with state' + : 'a compound' . ($self->{hasNumber} ? ' with number' : ''); } ###################################################################### @@ -626,10 +925,33 @@ sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); $self->Error("The left-hand side of '_' must be an element, compound, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState}; + unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '_' must be a number, not %s", $rop->TYPE) unless $rop->class eq 'Number'; $self->{type} = $MOLECULE->{type}; + $self->getMolecule; +} + +# +# Get the order/elements/charge data for a molecule +# +sub getMolecule { + my $self = shift; + my $lop = $self->{lop}; + my $n = $self->{rop}->eval; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{elements}{$_} *= $n for (keys %{ $self->{elements} }); + $self->{charge} = $n * $lop->{charge}; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -667,11 +989,32 @@ package context::Reaction::BOP::superscript; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("The left-hand side of '^' must be an element, molecule, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState}; + $self->Error("The left-hand side of '^' must be an element, molecule, or compound, not %s", $lop->TYPE) + unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '^' must be %s, not %s", context::Reaction::UOP->TYPE, $rop->TYPE) - unless $rop->class eq 'UOP'; + unless $rop->TYPE eq 'a charge'; $self->{type} = $ION->{type}; + $self->getIon; +} + +# +# Get the order/elements/charge data for an ion +# +sub getIon { + my $self = shift; + my $lop = $self->{lop}; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{charge} = $self->{rop}->eval; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -680,7 +1023,6 @@ sub _check { sub TeX { my $self = shift; my $left = $self->{lop}->TeX; - $left = "\\left($left\\right)" if $self->{lop}->type eq 'Compound'; return $left . '^{' . $self->{rop}->TeX . '}'; } @@ -691,7 +1033,6 @@ sub string { my $self = shift; my $left = $self->{lop}->string; my $right = $self->{rop}->string; - $left = "($left)" if $self->{lop}->type eq 'Compound'; $right = "($right)" unless $right eq '-' || $right eq '+'; return "$left^$right"; } @@ -703,7 +1044,7 @@ sub string { # General unary operator (minus and plus are subclasses of this). # package context::Reaction::UOP; -our @ISA = ('Parser::UOP'); +our @ISA = ('Parser::UOP', 'context::Reaction::common'); sub _check { my $self = shift; @@ -714,13 +1055,6 @@ sub _check { $self->Error("Can't use unary '%s' with %s", $name, $self->{op}->TYPE); } -# -# Unary operators produce numbers -# -sub isChemical {0} - -sub eval { context::Reaction::eval(@_) } - # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -736,6 +1070,11 @@ sub equivalent { return $self->{op}->equivalent($other->{op}); } +# +# Don't allow reduction of UOP plus (or minus) +# +sub isNeg {1} + # # Always put signs on the right # @@ -771,6 +1110,8 @@ sub TeX { package context::Reaction::UOP::minus; our @ISA = ('context::Reaction::UOP'); +sub eval { -(shift->{op}{value}) } + ###################################################################### # # Positive numbers (for ion exponents) @@ -778,12 +1119,14 @@ package context::Reaction::UOP::minus; package context::Reaction::UOP::plus; our @ISA = ('context::Reaction::UOP'); +sub eval { shift->{op}{value} } + ###################################################################### # # Implements sums of compounds as a list # package context::Reaction::List::List; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); # # Two sums are equivalent if their terms agree in any order. @@ -853,9 +1196,19 @@ sub TeX { return join(' + ', @coords); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + my $coords = [ map { $_->molecularForm } @{ $self->{coords} } ]; + my $paren = $self->context->parens->get('('); + return $self->new($self->{equation}, $coords, 0, $paren); +} + sub eval { context::Reaction::eval(@_) } -sub isChemical {1} +sub isSum {1} sub TYPE {'a sum of compounds'} @@ -864,7 +1217,7 @@ sub TeX { # Implements complexes as a list # package context::Reaction::List::Complex; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); sub _check { my $self = shift; @@ -874,6 +1227,16 @@ sub _check { $self->Error("The contents of a complex must be an element, molecule, ion, or compound, not %s", $arg->TYPE) unless $arg->type =~ /Element|Molecule|Ion|Compound/ && !$arg->{hasState}; $self->{type} = $COMPLEX->{type}; + $self->getComplex; +} + +sub getComplex { + my $self = shift; + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = 1; + $self->combineData($self->{coords}[0]); } # @@ -887,6 +1250,14 @@ sub equivalent { return $self->{coords}[0]->equivalent($other->{coords}[0]); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + return $self->{coords}[0]->molecularForm; +} + sub eval { context::Reaction::eval(@_) } sub isChemical {1} From be82dd094f1488687c110f220656fc704ac70bff Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 19 Sep 2025 18:00:18 -0400 Subject: [PATCH 041/111] Fix typo pointed out by @sfiedle1 --- macros/contexts/contextReaction.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index d315073ab5..60fb6ebf80 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -871,7 +871,7 @@ sub equivalent { return $list->equivalent($other); } return 0 unless $other->isChemical; - $other = $other->COMPOUND unless $other -type eq 'Compound'; + $other = $other->COMPOUND unless $other->type eq 'Compound'; my $states = $self->context->flag('studentsMustUseStates'); my $molecular = $self->context->flag('compareMolecular'); my $both = $self->context->flag('acceptMolecularForm'); From 31a9e8a7ba68bb84d5c647de620813e1db48d367 Mon Sep 17 00:00:00 2001 From: John E Date: Sat, 20 Sep 2025 10:09:23 -0400 Subject: [PATCH 042/111] a few more proposed typos --- assets/tex/pg.sty | 4 ++-- bin/parse-problem-doc.pl | 2 +- bin/run-perltidy.pl | 4 ++-- bin/update-localization-files | 2 +- lib/Parser.pm | 2 +- lib/Value/AnswerChecker.pm | 2 +- lib/WeBWorK/PG/Constants.pm | 2 +- macros/misc/randomPerson.pl | 2 +- t/macros/numerical_methods.t | 4 ++-- t/math_objects/matrix.t | 4 ++-- t/units/electron_volts.t | 2 +- tutorial/sample-problems/Algebra/GraphToolNumberLine.pg | 2 +- tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg | 2 +- tutorial/sample-problems/Misc/RandomPerson.pg | 2 +- tutorial/sample-problems/Parametric/SpaceCurveGraph.pg | 2 +- tutorial/sample-problems/Parametric/SurfaceGraph.pg | 2 +- tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg | 2 +- .../sample-problems/ProblemTechniques/DisableFunctions.pg | 2 +- tutorial/sample-problems/ProblemTechniques/Images.pg | 2 +- tutorial/sample-problems/Statistics/ScatterPlot.pg | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/assets/tex/pg.sty b/assets/tex/pg.sty index 7d8ce2c51b..be1921fd80 100644 --- a/assets/tex/pg.sty +++ b/assets/tex/pg.sty @@ -26,7 +26,7 @@ \input{PGML.tex} % The macro alternatives for < should be used in math in PG problems to help -% avoid issus with a bare < in HMTL or XML output. The alternatives for > are +% avoid issues with a bare < in HTML or XML output. The alternatives for > are % provided for parity. \newcommand{\lt}{<} \newcommand{\gt}{>} @@ -36,7 +36,7 @@ % semantic macro definitions used by PG \newcommand{\answerRule}[2][]{\raisebox{-3pt}{\parbox[t]{#2ex}{\hrulefill}}} -% height of a strut, used for example to possition the top border of an inline image +% height of a strut, used for example to position the top border of an inline image % unit is initialized here, but value is set locally where needed \newlength{\strutheight} diff --git a/bin/parse-problem-doc.pl b/bin/parse-problem-doc.pl index 84f9a303c7..b2274b198a 100755 --- a/bin/parse-problem-doc.pl +++ b/bin/parse-problem-doc.pl @@ -92,7 +92,7 @@ ($filename, %global) return; } -# Ouput index files. +# Output index files. for (qw(categories subjects macros techniques)) { my $options = { metadata => $index_table, diff --git a/bin/run-perltidy.pl b/bin/run-perltidy.pl index 9f2eeff8cb..a755f0d845 100755 --- a/bin/run-perltidy.pl +++ b/bin/run-perltidy.pl @@ -64,8 +64,8 @@ =head1 OPTIONS # Validate options that were passed. my %options; my $err = Perl::Tidy::perltidy(argv => \@args, dump_options => \%options); -exit $err if $err; -die "The -pro option is not suppored by this script.\n" if defined $options{profile}; +exit $err if $err; +die "The -pro option is not supported by this script.\n" if defined $options{profile}; unshift(@args, '-bext=/') unless defined $options{'backup-file-extension'}; diff --git a/bin/update-localization-files b/bin/update-localization-files index 50da33991a..05e1d0a746 100755 --- a/bin/update-localization-files +++ b/bin/update-localization-files @@ -45,7 +45,7 @@ if [ -z "$PG_ROOT" ]; then fi command -v xgettext.pl >/dev/null 2>&1 || { - echo >&2 "xgettext.pl needs to be installed. It is inlcuded in the perl package Locale::Maketext::Extract. Aborting."; + echo >&2 "xgettext.pl needs to be installed. It is included in the perl package Locale::Maketext::Extract. Aborting."; exit 1; } diff --git a/lib/Parser.pm b/lib/Parser.pm index c943441e33..c72651be78 100644 --- a/lib/Parser.pm +++ b/lib/Parser.pm @@ -191,7 +191,7 @@ sub push { push(@{ (shift)->{stack} }, @_) } sub state { (shift)->top->{type} } # -# Report an error at a given possition (if possible) +# Report an error at a given position (if possible) # sub Error { my $self = shift; diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index fb4dacb06c..27aa36dc3f 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -1333,7 +1333,7 @@ sub cmp_compare { } # -# Check for wrong enpoints and wrong type of endpoints +# Check for wrong endpoints and wrong type of endpoints # sub cmp_postprocess { my $self = shift; diff --git a/lib/WeBWorK/PG/Constants.pm b/lib/WeBWorK/PG/Constants.pm index 7e83e511b3..835687d9f1 100644 --- a/lib/WeBWorK/PG/Constants.pm +++ b/lib/WeBWorK/PG/Constants.pm @@ -11,7 +11,7 @@ use warnings; # ImageGenerator -# Arguments to pass to dvipng. This is dependant on the version of dvipng. +# Arguments to pass to dvipng. This is dependent on the version of dvipng. # # For dvipng versions 0.x # $ImageGenerator::DvipngArgs = "-x4000.5 -bgTransparent -Q6 -mode toshiba -D180"; diff --git a/macros/misc/randomPerson.pl b/macros/misc/randomPerson.pl index d3bf6b66a8..06ee34a7ab 100644 --- a/macros/misc/randomPerson.pl +++ b/macros/misc/randomPerson.pl @@ -49,7 +49,7 @@ =head2 Usage Depending on the he/she/they pronoun, the methods C, C, C and C (with or without capitalization) will select the correct subject, object, -possession, possesive forms of the pronoun. Also, note that C is one of a +possession, possessive forms of the pronoun. Also, note that C is one of a few special verbs with irregular conjugation. If you would like multiple people to be randomly choosen with unique names, use diff --git a/t/macros/numerical_methods.t b/t/macros/numerical_methods.t index b5a00a2a31..bfcbd89635 100644 --- a/t/macros/numerical_methods.t +++ b/t/macros/numerical_methods.t @@ -101,8 +101,8 @@ subtest 'Quadrature' => sub { is trapezoid($f, 0, 2, steps => 4), 2.75, 'Trapezoid rule of x^2 on [0,2]'; - is romberg($f, 0, 2), 8 / 3, 'Romberg interation for x^2 on [0,2]'; - is romberg($g, 0, 1), exp(1) - 1, 'Romberg interation on e^x on [0,1]'; + is romberg($f, 0, 2), 8 / 3, 'Romberg integration for x^2 on [0,2]'; + is romberg($g, 0, 1), exp(1) - 1, 'Romberg integration on e^x on [0,1]'; is inv_romberg($g, 0, exp(1) - 1), 1.0, 'Inverse Romberg to find b with int of e^x on [0,b] returns 1'; }; diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index dfdc890c35..dbf03b02e1 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -183,7 +183,7 @@ subtest 'Test if Matrix is in (R)REF' => sub { subtest 'Transpose a Matrix' => sub { my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]); - is $A->transpose->TeX, $B->TeX, 'Test the tranpose of a matrix'; + is $A->transpose->TeX, $B->TeX, 'Test the transpose of a matrix'; my $row = Matrix([ 1, 2, 3, 4 ]); my $row_trans = Matrix([ [1], [2], [3], [4] ]); @@ -191,7 +191,7 @@ subtest 'Transpose a Matrix' => sub { my $C = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); my $D = Matrix([ [ [ 1, 3 ], [ 2, 4 ] ], [ [ 5, 7 ], [ 6, 8 ] ] ]); - is $C->transpose->TeX, $D->TeX, 'Test the tranpose of a degree 3 tensor'; + is $C->transpose->TeX, $D->TeX, 'Test the transpose of a degree 3 tensor'; }; subtest 'Extract an element' => sub { diff --git a/t/units/electron_volts.t b/t/units/electron_volts.t index 6b5b7c4c3a..36ae87c8b1 100644 --- a/t/units/electron_volts.t +++ b/t/units/electron_volts.t @@ -26,7 +26,7 @@ is \%tev, by_factor(10**12, \%electron_volt), 'tera is factor 10^1 done_testing(); # this sub is useful when reusing units for testing -# NumberWithUnits is mutable and test order dependant +# NumberWithUnits is mutable and test order dependent sub by_factor { my ($value, $unit) = @_; my $new_unit = {%$unit}; # shallow copy hash values diff --git a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg index 7b4d5fac23..d9b3d2177e 100644 --- a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg +++ b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg @@ -24,7 +24,7 @@ DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #:% section = setup -#: Two intervals are created with random enpoints. The first +#: Two intervals are created with random endpoints. The first #: one is a bounded interval, and the second an unbounded interval. #: #: The `GraphTool` method creates the graph tool object. The only argument is diff --git a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg index 84a691e5f6..b0d42277c6 100644 --- a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg +++ b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg @@ -28,7 +28,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); #: #: First create a `Plot` object with a plotting window that will capture the #: transformed function. The function is then added to the plot with fill -#: characteristics. Note: it is important that the `name` atrribute is defined. +#: characteristics. Note: it is important that the `name` attribute is defined. $a = random(-4, 2); $b = random( 1, 4); diff --git a/tutorial/sample-problems/Misc/RandomPerson.pg b/tutorial/sample-problems/Misc/RandomPerson.pg index 4b5d18fff5..f20e04da63 100644 --- a/tutorial/sample-problems/Misc/RandomPerson.pg +++ b/tutorial/sample-problems/Misc/RandomPerson.pg @@ -48,7 +48,7 @@ $c = random(4, 8, 2); #: capitalized pronouns. #: #: Alternatively, a more-natural set of methods, `they, them, their` and `theirs` -#: will generate the correct subject, object, possession, possesive forms of the +#: will generate the correct subject, object, possession, possessive forms of the #: pronoun. #: #: In addition, there is a `verb` method to conjugate most diff --git a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg index c302547295..2b669f9d0f 100644 --- a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg +++ b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric curve is added to the graph with the `addCurve` method, which diff --git a/tutorial/sample-problems/Parametric/SurfaceGraph.pg b/tutorial/sample-problems/Parametric/SurfaceGraph.pg index ee29e38b38..6da4bec911 100644 --- a/tutorial/sample-problems/Parametric/SurfaceGraph.pg +++ b/tutorial/sample-problems/Parametric/SurfaceGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric surface is added to the graph with the `addSurface` method, diff --git a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg index 4ee1bf2542..343d5c7756 100644 --- a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg +++ b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg @@ -23,7 +23,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #:% section = setup #: Several context flags are set for this problem. The meaning of these flags -#: are decribed in the following list. +#: are described in the following list. #: #: * The flag `tolType => 'digits'` switches from the default `'relative'` #: tolerance type to the `'digits'` tolerance type. diff --git a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg index 483d402212..54ef7f4be0 100644 --- a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg +++ b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg @@ -59,7 +59,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: `sgn` #: * `Vector`: disables `norm`, `unit` #: * `Complex`: disables `arg`, `mod`, `Re`, `Im`, `conj` -#: * `All`: diables all predefined functions +#: * `All`: disables all predefined functions #: #: Alternatively, the following syntax can be used. #: diff --git a/tutorial/sample-problems/ProblemTechniques/Images.pg b/tutorial/sample-problems/ProblemTechniques/Images.pg index 7f2ae9344f..b8675fad1a 100644 --- a/tutorial/sample-problems/ProblemTechniques/Images.pg +++ b/tutorial/sample-problems/ProblemTechniques/Images.pg @@ -78,7 +78,7 @@ $gt = GraphTool('{circle, solid, (1, 1), (2, 2)}'); #: graphs. It is intended to be used to show an interactive graphing tool in #: which student's are expected to graph requested objects, in this case the #: circle that has center `(1, 1)` and passes through the point `(2, 2)`. -#: However, static images can be diplayed as is shown in this example. The +#: However, static images can be displayed as is shown in this example. The #: intent for this usage is to show the correct graph in a solution. BEGIN_PGML * A static image: [!Graph of an exponential!]{'image.png'}{120} diff --git a/tutorial/sample-problems/Statistics/ScatterPlot.pg b/tutorial/sample-problems/Statistics/ScatterPlot.pg index 9885ed7ffb..737a9b2ae0 100644 --- a/tutorial/sample-problems/Statistics/ScatterPlot.pg +++ b/tutorial/sample-problems/Statistics/ScatterPlot.pg @@ -34,7 +34,7 @@ loadMacros( #: components. #: #: For the plot, first set up the `Plot` with a plotting window that will -#: caputure the data. The `add_dataset` method adds the data to the plot +#: capture the data. The `add_dataset` method adds the data to the plot #: with circles and color blue. #: #: The line is added to the dataset with the `add_function` method. The 3rd From 5b6e28a36b6d6ffc056b47ba217fd81d631744e9 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 23 Sep 2025 12:05:41 -0600 Subject: [PATCH 043/111] Fix typo in POD generation of parserCheckboxList.pl --- macros/parsers/parserCheckboxList.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index 69e4498668..d2e80c6d36 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -158,6 +158,7 @@ =head1 DESCRIPTION the literal correct answer, not an index to it. Default: 0 =item C 0 or 1 >>> + In static output, such as PDF or PTX, this controls whether or not the list of answer options is displayed. (The text preceding the list of answer options might make printing the answer option list From a4463cfe5867f2dea93373629106c47cbe18933c Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 7 Aug 2025 05:36:39 -0500 Subject: [PATCH 044/111] Fix issues with the legacy NumberWithUnits. See issue #1301 for details of the changes and examples to test with. --- lib/Parser/Legacy/NumberWithUnits.pm | 27 ++++++++++++++++++------ macros/parsers/parserMultiAnswer.pl | 3 ++- macros/parsers/parserRadioMultiAnswer.pl | 3 ++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/Parser/Legacy/NumberWithUnits.pm b/lib/Parser/Legacy/NumberWithUnits.pm index e67859e8cf..025ad20d93 100644 --- a/lib/Parser/Legacy/NumberWithUnits.pm +++ b/lib/Parser/Legacy/NumberWithUnits.pm @@ -92,7 +92,7 @@ sub splitUnits { : $aUnit . '(?:\s*[/* ]\s*' . $aUnit . ')*'; $unitPattern = $unitPattern . '(?:\/' . $unitPattern . ')*' if $parseMathQuill; my $unitSpace = "($aUnit) +($aUnit)"; - my ($num, $units) = $string =~ m!^(.*?(?:[)}\]0-9a-z]|\d\.))\s*($unitPattern)\s*$!; + my ($num, $units) = $string =~ m!^(.*?(?:[)}\]0-9a-z]|\d\.))?\s*($unitPattern)\s*$!; if ($units) { while ($units =~ s/$unitSpace/$1*$2/) { } $units =~ s/ //g; @@ -238,20 +238,26 @@ sub unitsPreFilter { $ans->{correct_value}{context} && $ans->{correct_value}->context->flag('useMathQuill') && (!defined $ans->{mathQuillOpts} || $ans->{mathQuillOpts} !~ /^\s*disabled\s*$/i)); + if (defined($units) && $units ne '' && $num eq '') { + $self->cmp_Error($ans, "Units must follow a number"); + $ans->{unit_error} = $ans->{ans_message}; + $ans->{student_ans} = ''; + return $ans; + } unless (defined($num) && defined($units) && $units ne '') { $self->cmp_Error($ans, "Your answer doesn't look like " . lc($self->cmp_class)); - $ans->{error_flag} = 'UNITS_NONE'; + $ans->{unit_error} = $ans->{ans_message}; return $ans; } if ($units =~ m!/.*/!) { $self->cmp_Error($ans, "Your units can only contain one division"); - $ans->{error_flag} = 'UNITS_DIVISION'; + $ans->{unit_error} = $ans->{ans_message}; return $ans; } my $ref = { getUnits($units) }; if ($ref->{ERROR}) { $self->cmp_Error($ans, $ref->{ERROR}); - $ans->{error_flag} = 'UNITS_BAD'; + $ans->{unit_error} = $ans->{ans_message}; return $ans; } $ans->{units} = $units; @@ -263,6 +269,14 @@ sub cmp_preprocess { my $self = shift; my $ans = shift; + if ($ans->{unit_error}) { + $ans->{ans_message} = $ans->{error_message} = $ans->{unit_error}; + if ($ans->{student_ans} eq '') { + $ans->{student_ans} = $ans->{original_student_ans}; + $ans->{preview_latex_string} = TeXunits($ans->{student_ans}); + } + return; + } my $units = $ans->{units}; return $ans unless $units; $ans->{student_ans} .= " " . $units; @@ -272,8 +286,8 @@ sub cmp_preprocess { if (!defined($ans->{student_value}) || $self->checkStudentValue($ans->{student_value})) { $ans->{student_value} = undef; $ans->score(0); - $ans->{error_flag} = 'UNITS_NO_NUMBER'; $self->cmp_Error($ans, "Units must follow a number"); + $ans->{unit_error} = $ans->{ans_message}; return; } @@ -284,7 +298,7 @@ sub cmp_preprocess { sub cmp_equal { my $self = shift; my $ans = shift; - if (!$ans->{error_flag}) { + if (!$ans->{unit_error}) { my $meth = @{ ref($self) . '::ISA' }[-1] . '::cmp_equal'; $meth = 'Value::cmp_equal' unless defined &$meth; &$meth($self, $ans, @_); @@ -298,7 +312,6 @@ sub cmp_postprocess { $self->cmp_Error($ans, "The units for your answer are not correct") unless $ans->{correct_value}->uPowers eq $ans->{student_value}->uPowers; } - $ans->{error_flag} = undef if $ans->{error_flag} =~ m/^UNITS_/; return $ans; } diff --git a/macros/parsers/parserMultiAnswer.pl b/macros/parsers/parserMultiAnswer.pl index 3472f64abc..2bc718521c 100644 --- a/macros/parsers/parserMultiAnswer.pl +++ b/macros/parsers/parserMultiAnswer.pl @@ -104,8 +104,9 @@ sub cmp { } if ($self->{allowBlankAnswers}) { + my $blankCheck = AnswerEvaluator->new()->{pre_filters}[0][0]; foreach my $cmp (@{ $self->{cmp} }) { - $cmp->install_pre_filter('erase'); + $cmp->{pre_filters} = [ grep { $_->[0] != $blankCheck } @{ $cmp->{pre_filters} } ]; $cmp->install_pre_filter(sub { my $ans = shift; $ans->{student_ans} =~ s/^\s+//g; diff --git a/macros/parsers/parserRadioMultiAnswer.pl b/macros/parsers/parserRadioMultiAnswer.pl index 8fdb61980e..3cee48a56f 100644 --- a/macros/parsers/parserRadioMultiAnswer.pl +++ b/macros/parsers/parserRadioMultiAnswer.pl @@ -354,9 +354,10 @@ sub cmp { } if ($self->{allowBlankAnswers}) { + my $blankCheck = AnswerEvaluator->new()->{pre_filters}[0][0]; for (@{ $self->{cmp} }) { for my $cmp (@$_) { - $cmp->install_pre_filter('erase'); + $cmp->{pre_filters} = [ grep { $_->[0] != $blankCheck } @{ $cmp->{pre_filters} } ]; $cmp->install_pre_filter(sub { my $ans = shift; $ans->{student_ans} =~ s/^\s+//g; From a179902885fd50a057dff8efba32e5140a3dc513 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 7 Aug 2025 07:33:47 -0500 Subject: [PATCH 045/111] Fix the unit test that attempts to construct a `NumberWithUnits` with no number. --- lib/Parser/Legacy/NumberWithUnits.pm | 2 +- t/units/basic_parser.t | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/Parser/Legacy/NumberWithUnits.pm b/lib/Parser/Legacy/NumberWithUnits.pm index 025ad20d93..1a357ca140 100644 --- a/lib/Parser/Legacy/NumberWithUnits.pm +++ b/lib/Parser/Legacy/NumberWithUnits.pm @@ -363,7 +363,7 @@ sub makeValue { my $value = shift; my %options = (context => $self->context, @_); my $num = Value::makeValue($value, %options); - return bless $num, 'Parser::Legacy::FormulaWithUnits' if $num->classMatch('Formula'); + return bless $num, 'Parser::Legacy::FormulaWithUnits' if defined $num && $num->classMatch('Formula'); Value::Error("A number with units must be a constant, not %s", lc(Value::showClass($num))) unless Value::isReal($num); bless $num, $options{class}; diff --git a/t/units/basic_parser.t b/t/units/basic_parser.t index 6598c29814..9b96e63617 100644 --- a/t/units/basic_parser.t +++ b/t/units/basic_parser.t @@ -95,12 +95,8 @@ subtest 'Test error handling' => sub { qr/Unrecognizable unit: \|$fake\|/, "No unit '$fake' defined in Units file" ); - like(dies { NumberWithUnits(1) }, qr/You must provide units for your number/, 'No unit given'); - like( - dies { NumberWithUnits('J') }, - qr/You must provide units for your number/, - 'No value given, wants 2 arguments' - ); + like(dies { NumberWithUnits(1) }, qr/You must provide units for your number/, 'No unit given'); + like(dies { NumberWithUnits('J') }, qr/A number with units must be a constant, not ''/, 'No value given'); }; subtest 'Check parsing of arguments' => sub { From 6a4dd455f029dbb110378852fe80eeed640c68f6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 6 Oct 2025 05:48:05 -0500 Subject: [PATCH 046/111] Fix invalid html for ans_array answers. The `format_matrix_HTML` method uses `span` tags to format the html output for matrices. This is a problem when that is used to format the output for an `ans_array` answer because the answer inputs inside now are wrapped in `div` tags (so that the feedback button works in a valid html way). So this just switches to using `div`s instead. This doesn't change the result at all since the containg array layout div already has `display:inline-block` set. There is one minor tweak to the style. I added `text-align:center;` to the cells. This just looks better in about all of the cases that I have observed. Note that this is also consistent with when these objects are displayed in math mode via MathJax or as an image with the image display mode. Note that method is used ans_array matrix, vector, and point answer rules, as well as the "Entered" feedback preview of the student answer for those answers. It also affects the "textual" correct answer, but that actually isn't used for anything anymore. --- lib/Value/AnswerChecker.pm | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index fb4dacb06c..31f4a52d07 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -555,57 +555,57 @@ sub format_matrix_HTML { my ($rows, $cols) = (scalar(@{$array}), scalar(@{ $array->[0] })); my $HTML = ""; my $class = 'class="ans_array_cell"'; - my $cell = "display:table-cell;vertical-align:middle;"; + my $cell = "display:table-cell;vertical-align:middle;text-align:center;"; my $pad = "padding:4px 0;"; - if ($sep) { $sep = '' . $sep . '' } - else { $sep = '' } - $sep = '' . $sep . ''; + if ($sep) { $sep = '
' . $sep . '
' } + else { $sep = '
' } + $sep = '
' . $sep . '
'; if ($options{top_labels}) { $HTML .= - '
' . join($sep, @{ $options{top_labels} }) - . ''; + . '
'; } foreach my $i (0 .. $rows - 1) { $HTML .= - '
' . join($sep, EVALUATE(@{ $array->[$i] })) - . ''; + . '
'; } - $HTML = '' . $HTML . ''; + $HTML = '
' . $HTML . '
'; $open = $self->format_delimiter($open, $rows, $options{tth_delims}); $close = $self->format_delimiter($close, $rows, $options{tth_delims}); if ($open ne '' || $close ne '') { my $delim = "display:inline-block; vertical-align:middle;"; $HTML = - '' . $open - . '' + . '
' . $HTML - . '' . $close - . ''; + . '
'; } - return '' . $HTML - . ''; + . '
'; } sub EVALUATE { From 5e8b76de371f85366011d4fcd2ef95cafe54da93 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 7 Oct 2025 14:42:57 -0500 Subject: [PATCH 047/111] Update the MathQuill version to pull in some fixes. https://github.com/openwebwork/mathquill/pull/39 and https://github.com/openwebwork/mathquill/pull/40 have been merged and a new version of @openwebwork/mathquill have been merged. This updates to the newly published version that includes those fixes. --- htdocs/package-lock.json | 14 +++++++------- htdocs/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index e513ce6e88..83ebb43adf 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -7,7 +7,7 @@ "name": "pg.javascript_package_manager", "license": "GPL-2.0+", "dependencies": { - "@openwebwork/mathquill": "^0.11.0", + "@openwebwork/mathquill": "^0.11.1", "jsxgraph": "^1.11.1", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", @@ -77,9 +77,9 @@ } }, "node_modules/@openwebwork/mathquill": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.0.tgz", - "integrity": "sha512-w5AlhsnreqCFYePP2V8Ve/hxA8KZvYmyHD5uehCD07PDp/HiL3DSo4mtAyLV7pWTXUoPVLFdnZK/WmrSYs+UdQ==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.1.tgz", + "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==", "license": "MPL-2.0" }, "node_modules/@parcel/watcher": { @@ -2114,9 +2114,9 @@ } }, "@openwebwork/mathquill": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.0.tgz", - "integrity": "sha512-w5AlhsnreqCFYePP2V8Ve/hxA8KZvYmyHD5uehCD07PDp/HiL3DSo4mtAyLV7pWTXUoPVLFdnZK/WmrSYs+UdQ==" + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.1.tgz", + "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==" }, "@parcel/watcher": { "version": "2.5.1", diff --git a/htdocs/package.json b/htdocs/package.json index 2410085950..f4cfd4ec1d 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -13,7 +13,7 @@ "prettier-check": "prettier --ignore-path=../.gitignore --check \"**/*.{js,css,scss,html}\" \"../**/*.dist.yml\"" }, "dependencies": { - "@openwebwork/mathquill": "^0.11.0", + "@openwebwork/mathquill": "^0.11.1", "jsxgraph": "^1.11.1", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", From 3c5132f359f6bbf7ff1058b6ce3c1f42869984bd Mon Sep 17 00:00:00 2001 From: John E Date: Thu, 9 Oct 2025 08:12:38 -0400 Subject: [PATCH 048/111] fix my spacing bug --- macros/core/PGbasicmacros.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 2332a760fb..94b8e5657c 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -3228,7 +3228,7 @@ sub tag { ' ', map { ($_ =~ s/_/-/gr) . (defined $attributes{$_} ? ('="' . encode_pg_and_html($attributes{$_})) . '"' : '') - } + } keys %attributes ); From a389e98002e6a85df882665553786fe226cdd82d Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 14 Oct 2025 06:11:07 -0500 Subject: [PATCH 049/111] Fix contextFraction.pl with reduceConstants disabled. This fixes issue #1325. The change is just what @dpvc suggested. There is a MWE posted in the issue. --- macros/contexts/contextFraction.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/contexts/contextFraction.pl b/macros/contexts/contextFraction.pl index af047f8687..539db4e3e9 100644 --- a/macros/contexts/contextFraction.pl +++ b/macros/contexts/contextFraction.pl @@ -709,7 +709,7 @@ sub _check { $self->{type} = $Value::Type{number}; } else { &{ $self->super('_check') }($self); - $self->setExtensionClass('MINUS') if $self->{op}->class eq 'Number'; + $self->setExtensionClass('MINUS') if $self->extensionClassMatch($self->{op}, 'MINUS', 'INTEGER'); } $self->mutate; } From 8080e3a8b61f1fdec472c76925e9e2338d948c4d Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Tue, 14 Oct 2025 15:16:47 -0400 Subject: [PATCH 050/111] Fix typos in POD documentation --- macros/contexts/contextReaction.pl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 60fb6ebf80..5d589e593e 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -117,9 +117,9 @@ =head1 DESCRIPTION correct answer in the form the correct answer is given (molecular or condensed structural form). If you want to allow either form, then you can set the flag C to C<1>, in which case, -the student's answwer is compared first to the original correct answer +the student's answer is compared first to the original correct answer and then to its molecular form. For example, if the correct answer is -`(NH_4)_3PO_4`, then a student answer of `N_3H_12PO_4` would also be +C<(NH_4)_3PO_4>, then a student answer of C would also be considered correct when this flag is set to C<1>. You can convert a compound (or every term of a complete reaction) to @@ -128,7 +128,7 @@ =head1 DESCRIPTION $R = Compute("CH_3CH_2CH_3")->molecularForm; -would make C<$R> be the quivalent of C. +would make C<$R> be the equivalent of C. Note that student answers aren't reduced to molecular form when C is true. If you want to allow students to From e4a64fb7cc651c5b4d4754e8068d072af8953252 Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Tue, 28 Oct 2025 15:03:52 -0400 Subject: [PATCH 051/111] Remove badges that are basically broken. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index d40515112f..5125bd5f59 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # Welcome to WeBWorK -![main workflow](https://github.com/pstaabp/pg/actions/workflows/coverage.yml/badge.svg) -[![codecov](https://codecov.io/gh/pstaabp/pg/branch/unit-test/graph/badge.svg?token=H7WYHBDB9S)](https://codecov.io/gh/pstaabp/pg) -![GitHub last commit](https://img.shields.io/github/last-commit/pstaabp/pg/unit-test) - WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](http://webwork.maa.org). ## Information for Users From ee9d9ce2d74f1fdf807a7cbfcacbf9def306d632 Mon Sep 17 00:00:00 2001 From: Danny Glin Date: Tue, 28 Oct 2025 13:34:14 -0600 Subject: [PATCH 052/111] Change all webwork.maa.org links to openwebwork.org, and remove code coverage badges from README file --- README | 4 ++-- README.md | 17 ++++++----------- conf/pg_config.dist.yml | 2 +- htdocs/helpFiles/Entering-Angles.html | 2 +- htdocs/helpFiles/Entering-Equations.html | 2 +- htdocs/helpFiles/Entering-Formulas.html | 2 +- htdocs/helpFiles/Entering-Formulas10.html | 2 +- htdocs/helpFiles/Entering-Logarithms.html | 2 +- htdocs/helpFiles/Entering-Logarithms10.html | 2 +- htdocs/helpFiles/Entering-Points.html | 2 +- htdocs/helpFiles/Entering-Syntax.html | 2 +- htdocs/helpFiles/Entering-Units.html | 2 +- htdocs/helpFiles/Entering-Vectors.html | 2 +- htdocs/helpFiles/Syntax.html | 2 +- htdocs/helpFiles/Units.html | 2 +- lib/Value/Matrix.pm | 10 +++++----- macros/answers/PGasu.pl | 4 ++-- macros/math/MatrixReduce.pl | 2 +- macros/math/algebraMacros.pl | 2 +- macros/math/draggableProof.pl | 4 ++-- macros/math/draggableSubsets.pl | 2 +- macros/math/tableau.pl | 8 ++++---- t/README.md | 2 +- .../sample-problems/DiffEq/HeavisideStep.pg | 2 +- tutorial/sample-problems/VectorCalc/Vectors.pg | 2 +- 25 files changed, 40 insertions(+), 45 deletions(-) diff --git a/README b/README index 9a8d30bb13..48bfabd55d 100644 --- a/README +++ b/README @@ -4,8 +4,8 @@ Branch: https://github.com/openwebwork - http://webwork.maa.org/wiki/Category:Release_Notes + https://wiki.openwebwork.org/wiki/Category:Release_Notes Copyright 2000-2025, The WeBWorK Project - http://webwork.maa.org + https://openwebwork.org All rights reserved. diff --git a/README.md b/README.md index d40515112f..ddfa947f8a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,20 @@ # Welcome to WeBWorK -![main workflow](https://github.com/pstaabp/pg/actions/workflows/coverage.yml/badge.svg) -[![codecov](https://codecov.io/gh/pstaabp/pg/branch/unit-test/graph/badge.svg?token=H7WYHBDB9S)](https://codecov.io/gh/pstaabp/pg) -![GitHub last commit](https://img.shields.io/github/last-commit/pstaabp/pg/unit-test) - -WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](http://webwork.maa.org). +WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](https://openwebwork.org). ## Information for Users New users interested in getting started with their own WeBWorK server, or instructors looking to learn more about how to use WeBWorK in their classes, should take a look at one of the following resources: -* [WeBWorK wiki](http://webwork.maa.org/wiki/Main_Page) - The main WeBWorK wiki - * [Instructors](http://webwork.maa.org/wiki/Instructors) - Information for Instructors - * [Problem Authors](http://webwork.maa.org/wiki/Authors) - Information for Problem Authors -* [WW_Install](http://github.com/aubreyja/ww_install) - Information for using the WW_install script -* [Forum](http://webwork.maa.org/moodle/mod/forum/index.php?id=3) - The WeBWorK Forum +* [WeBWorK wiki](https://wiki.openwebwork.org/wiki/WeBWorK_Main_Page) - The main WeBWorK wiki + * [Instructors](https://wiki.openwebwork.org/wiki/Instructors) - Information for Instructors + * [Problem Authors](https://wiki.openwebwork.org/wiki/Authors) - Information for Problem Authors +* [Forum](https://forums.openwebwork.org/mod/forum/index.php?id=3) - The WeBWorK Forum * [Frequently Asked Questions](https://github.com/openwebwork/webwork2/wiki/Frequently-Asked-Questions) - A list of frequently asked questions. ## Information For Developers -People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](http://webwork.maa.org/wiki/Authors). +People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](https://wiki.openwebwork.org/wiki/Authors). * [First Time Setup](https://github.com/openwebwork/webwork2/wiki/First-Time-Setup) - Setting up your clone of this github repo for the first time. * [Coding and Workflow](https://github.com/openwebwork/webwork2/wiki/Coding-and-Workflow) - Our suggested workflow processes. Following this will make it much easier to get code accepted into the repo. diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml index 3e4c9ab080..25295f05e4 100644 --- a/conf/pg_config.dist.yml +++ b/conf/pg_config.dist.yml @@ -141,7 +141,7 @@ specialPGEnvironmentVars: # To enable Rserve (the R statistical server), uncomment the following two # lines. The R server needs to be installed and running in order for this to - # work. See http://webwork.maa.org/wiki/R_in_WeBWorK for more info. + # work. See https://wiki.openwebwork.org/wiki/R_in_WeBWorK for more info. #Rserve: # host: localhost diff --git a/htdocs/helpFiles/Entering-Angles.html b/htdocs/helpFiles/Entering-Angles.html index bfa15541e0..b0837609f1 100644 --- a/htdocs/helpFiles/Entering-Angles.html +++ b/htdocs/helpFiles/Entering-Angles.html @@ -43,7 +43,7 @@

Entering Angles

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Equations.html b/htdocs/helpFiles/Entering-Equations.html index bd758fdda8..b05fd50386 100644 --- a/htdocs/helpFiles/Entering-Equations.html +++ b/htdocs/helpFiles/Entering-Equations.html @@ -45,7 +45,7 @@

    Entering Equations

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas.html b/htdocs/helpFiles/Entering-Formulas.html index 60e8dea8b6..21b40231db 100644 --- a/htdocs/helpFiles/Entering-Formulas.html +++ b/htdocs/helpFiles/Entering-Formulas.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas10.html b/htdocs/helpFiles/Entering-Formulas10.html index 7f60e91f56..8b00add2f9 100644 --- a/htdocs/helpFiles/Entering-Formulas10.html +++ b/htdocs/helpFiles/Entering-Formulas10.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms.html b/htdocs/helpFiles/Entering-Logarithms.html index ed055d68fc..b38374e0b0 100644 --- a/htdocs/helpFiles/Entering-Logarithms.html +++ b/htdocs/helpFiles/Entering-Logarithms.html @@ -54,7 +54,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms10.html b/htdocs/helpFiles/Entering-Logarithms10.html index 39b764f85e..de0bba372f 100644 --- a/htdocs/helpFiles/Entering-Logarithms10.html +++ b/htdocs/helpFiles/Entering-Logarithms10.html @@ -55,7 +55,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Points.html b/htdocs/helpFiles/Entering-Points.html index 077cf66fa0..a5b23d448c 100644 --- a/htdocs/helpFiles/Entering-Points.html +++ b/htdocs/helpFiles/Entering-Points.html @@ -37,7 +37,7 @@

    Entering Points

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Syntax.html b/htdocs/helpFiles/Entering-Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Entering-Syntax.html +++ b/htdocs/helpFiles/Entering-Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Entering-Units.html b/htdocs/helpFiles/Entering-Units.html index 9ac3fc9616..e8a4912770 100644 --- a/htdocs/helpFiles/Entering-Units.html +++ b/htdocs/helpFiles/Entering-Units.html @@ -219,5 +219,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/htdocs/helpFiles/Entering-Vectors.html b/htdocs/helpFiles/Entering-Vectors.html index 7a1523b13b..7d7d2339cc 100644 --- a/htdocs/helpFiles/Entering-Vectors.html +++ b/htdocs/helpFiles/Entering-Vectors.html @@ -64,7 +64,7 @@

    Entering Vectors

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Syntax.html b/htdocs/helpFiles/Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Syntax.html +++ b/htdocs/helpFiles/Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Units.html b/htdocs/helpFiles/Units.html index 456bc1ada4..02cea15387 100644 --- a/htdocs/helpFiles/Units.html +++ b/htdocs/helpFiles/Units.html @@ -199,5 +199,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 18b7498153..12a36bf495 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -12,11 +12,11 @@ This is the Math Object code for a Matrix. =over -=item L +=item L -=item L +=item L -=item L +=item L =back @@ -117,7 +117,7 @@ Examples: =head3 Update values (these need to be added) - see C in MatrixReduce and L + see C in MatrixReduce and L =head3 Advanced @@ -174,7 +174,7 @@ One can use fractions in Matrices by including C. For exam and operations will be done using rational arithmetic. Also helpful is the method C in the L macro. Some additional information can be -found at L. +found at L. =head2 methods diff --git a/macros/answers/PGasu.pl b/macros/answers/PGasu.pl index 781c346b57..64e855fcf0 100644 --- a/macros/answers/PGasu.pl +++ b/macros/answers/PGasu.pl @@ -116,7 +116,7 @@ =head2 no_trig_fun A similar effect can be accomplished with Contexts() by undefining the trig functions. -See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29#.282.29_Functions +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut @@ -202,7 +202,7 @@ =head2 must_have_filter A similar effect can be accomplished with Contexts() by undefining the trig functions. -See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29 +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut diff --git a/macros/math/MatrixReduce.pl b/macros/math/MatrixReduce.pl index a545655b45..920e4ee090 100644 --- a/macros/math/MatrixReduce.pl +++ b/macros/math/MatrixReduce.pl @@ -201,7 +201,7 @@ sub rref_perl_array { } # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 sub change_matrix_entry { my $self = shift; diff --git a/macros/math/algebraMacros.pl b/macros/math/algebraMacros.pl index bbe70005de..da5772496b 100644 --- a/macros/math/algebraMacros.pl +++ b/macros/math/algebraMacros.pl @@ -38,7 +38,7 @@ =head2 xgcd =cut -# by Dick Lane http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2286 +# by Dick Lane https://forums.openwebwork.org/mod/forum/discuss.php?d=2286 sub xgcd ($$) { my ($a, $b, $x, $y, $s, $t) = (@_, 1, 0, 0, 1); diff --git a/macros/math/draggableProof.pl b/macros/math/draggableProof.pl index 9a79a7fb33..1877a4d22f 100644 --- a/macros/math/draggableProof.pl +++ b/macros/math/draggableProof.pl @@ -160,8 +160,8 @@ =head1 CUSTOM CHECKERS Custom checkers can also be used by passing the C or C options to the C method. See -L, and -L for details on +L, and +L for details on how to use these. Note that if using a standard C the correct and student answers diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 53328cb414..0c0c7bc77e 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -188,7 +188,7 @@ =head1 SYNOPSIS =head1 CUSTOM CHECKERS A custom checkers can also be used by passing the C option to the -C method. See L +C method. See L for details on how to use a custom list checker. This follows the usual rules for the return value of the C method. diff --git a/macros/math/tableau.pl b/macros/math/tableau.pl index 91d33a0855..06fb4ef6b5 100644 --- a/macros/math/tableau.pl +++ b/macros/math/tableau.pl @@ -157,9 +157,9 @@ =head2 new =head1 REFERENCES -MathObject Matrix methods: L -MathObject Contexts: L -CPAN RealMatrix docs: L +MathObject Matrix methods: L +MathObject Contexts: L +CPAN RealMatrix docs: L More references: L @@ -1477,7 +1477,7 @@ sub submatrix { =cut # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 # taken from MatrixReduce.pl from Paul Pearson sub change_matrix_entry { diff --git a/t/README.md b/t/README.md index 2cf778d150..728135f5cb 100644 --- a/t/README.md +++ b/t/README.md @@ -16,7 +16,7 @@ and a working test can be better than documentation because it shows how the code currently works in practice. Old references can be found on the WebWork wiki page -[Unit Testing](https://webwork.maa.org/wiki/Unit_Testing) +[Unit Testing](https://wiki.openwebwork.org/wiki/Unit_Testing) ## Unit Tests diff --git a/tutorial/sample-problems/DiffEq/HeavisideStep.pg b/tutorial/sample-problems/DiffEq/HeavisideStep.pg index baa13bf846..fe69f522ea 100644 --- a/tutorial/sample-problems/DiffEq/HeavisideStep.pg +++ b/tutorial/sample-problems/DiffEq/HeavisideStep.pg @@ -30,7 +30,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserFunction.pl', 'PGcourse.pl'); #: #: For more details on adding the Heaviside function to the context, see the #: forum discussion on the -#: [Heaviside step function](https://webwork.maa.org/moodle/mod/forum/discuss.php?d=458). +#: [Heaviside step function](https://wiki.openwebwork.org/moodle/mod/forum/discuss.php?d=458). #: #: For the second question, since answers are checked numerically by comparing #: the student answer to the correct answer at several random points in the diff --git a/tutorial/sample-problems/VectorCalc/Vectors.pg b/tutorial/sample-problems/VectorCalc/Vectors.pg index b1f25d9d4f..498022c208 100644 --- a/tutorial/sample-problems/VectorCalc/Vectors.pg +++ b/tutorial/sample-problems/VectorCalc/Vectors.pg @@ -54,7 +54,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: example, to obtain the first component of `$v3` use `$v3->extract(1)`. Note #: that the index of `extract` starts at 1, not 0. #: -#: See the [vector](https://webwork.maa.org/wiki/Vector_(MathObject_Class)) +#: See the [vector](https://wiki.openwebwork.org/wiki/Vector_(MathObject_Class)) #: wiki page for more information about MathObject vectors. Context('Vector'); # Uncomment to display vectors in ijk format. From 57d5500b587a27a17e0c7479a941966287738cdb Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 8 Oct 2025 17:18:53 -0500 Subject: [PATCH 053/111] Fix feedback previews when the display mode is "images". Currently if the display mode is "images", then the student and correct answer previews don't work. The images are never actually rendered, thus the image files are never created, and so the `alt` value is shown which is the original TeX for the answer. The reason for this is that the image generator runs before the post content processor runs. So equation images in the problem are rendered, but not those in the feedback. That was done because of the hack of inserting the string `MaRkEr` followed by the image number hash when the image is initially inserted into the problem text, and then later replacing that with the alignment styel when the image is rendered. That results in invalid HTML which `Mojo::DOM` doesn't like. To fix the issue the `MaRkEr` hack is reworked. Instead of that string, a `data-imagegen-alignment-marker` attribute is used whose value is the image number hash. Since that is valid HTML, `Mojo::DOM` is fine with it and leaves it as it is. So the image generator can now be run after the post content processor runs, and that renders the images in feedback as well. Care is needed for the images in feedback when the `data-imagegen-alignment-marker` is replaced. Since the rendered `img` tag is inside the `data-bs-content` attribute of the feedback button, it is HTML encoded. So instead of double quotes, the HTML double quote escape character (") is used. --- lib/WeBWorK/PG.pm | 13 +++---------- lib/WeBWorK/PG/ImageGenerator.pm | 13 +++++++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index dee8594174..cef9335687 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -160,19 +160,12 @@ sub new_helper ($invocant, %options) { ); } - # HTML_dpng uses an ImageGenerator. We have to render the queued equations. This must be done before the post - # processing, since the image tags output by the image generator initially include markers which are invalid html. - # Mojo::DOM will change these markers into attributes with values and this will fail. - if ($image_generator) { - $image_generator->render( - refresh => $options{refreshMath2img} // 0, - body_text => $translator->r_text, - ); - } - $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; $translator->stringify_answers; + $image_generator->render(body_text => $translator->r_text, refresh => $options{refreshMath2img} // 0) + if $image_generator; + # Add the result summary set in post processing into the result. $result->{summary} = $translator->{rh_pgcore}{result_summary} if ref($translator->{rh_pgcore}) eq 'PGcore' diff --git a/lib/WeBWorK/PG/ImageGenerator.pm b/lib/WeBWorK/PG/ImageGenerator.pm index a5d09c86b3..d30424af26 100644 --- a/lib/WeBWorK/PG/ImageGenerator.pm +++ b/lib/WeBWorK/PG/ImageGenerator.pm @@ -250,8 +250,8 @@ sub add ($self, $string, $mode = 'inline') { # Determine what the image's "number" is. if ($useCache) { $imageNum = $self->{equationCache}->lookup($realString); - $aligntag = 'MaRkEr' . $imageNum if $self->{useMarkers}; - $depths->{$imageNum} = 'none' if $self->{store_depths}; + $aligntag = qq{data-imagegen-alignment-marker="$imageNum"} if $self->{useMarkers}; + $depths->{$imageNum} = 'none' if $self->{store_depths}; # Insert a slash after 2 characters. This effectively divides the images into 16^2 = 256 subdirectories. substr($imageNum, 2, 0) = '/'; } else { @@ -461,11 +461,16 @@ sub fix_markers ($self) { my %depths = %{ $self->{depths} }; for my $depthkey (keys %depths) { + # The data-imagegen-alignment-marker value may be quoted with double quotes or with " if the image is + # inside another HTML element attribute (such as for images in the feedback button). So both quote types need + # to be checked, and the replaced style attribute needs to use the same quoting that it comes in with. if ($depths{$depthkey} eq 'none') { - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:$self->{dvipng_align}"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:$self->{dvipng_align}$1/g; } else { my $ndepth = 0 - $depths{$depthkey}; - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:${ndepth}px"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:${ndepth}px$1/g; } } return; From 1b503384366f4120c21b02d4c9b081f846430bae Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 25 Aug 2025 06:25:31 -0500 Subject: [PATCH 054/111] Replace invalid "type" attribute on `ul` tag in PGML. The "type" attribute is also replaced for `ol` tags so that this can be done in a nice way for all of the list types. Note that although the "type" attribute is not deprecated for `ol` tags, it is recommended to use css instead in any case. --- macros/core/PGML.pl | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index cbeb92fabd..355c2c3de1 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1550,28 +1550,27 @@ sub Align { } our %bullet = ( - bullet => 'ul type="disc"', - numeric => 'ol type="1"', - alpha => 'ol type="a"', - Alpha => 'ol type="A"', - roman => 'ol type="i"', - Roman => 'ol type="I"', - disc => 'ul type="disc"', - circle => 'ul type="circle"', - square => 'ul 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 { my $self = shift; my $item = shift; my $list = $bullet{ $item->{bullet} }; - return - $self->nl . '<' - . $list - . ' style="margin:0; padding-left:2.25em">' . "\n" - . $self->string($item) - . $self->nl . "\n"; + return $self->nl + . main::tag( + $list->[0], + style => "margin:0; padding-left:2.25em; $list->[1]", + $self->string($item) . $self->nl + ); } sub Bullet { From 2465f46e560abfafa25df212a61a9a19b0faebb7 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Thu, 6 Nov 2025 09:30:00 -0700 Subject: [PATCH 055/111] Use same context when creating a new context::Fraction::Parser::Number When creating a context::Fraction::Parser::Number, use the same context as the original object instead of the current context. This fixes issue #1337 with checking if an object created in the fraction context is equal to a number after the context has changed. This fix is by @dpvc. --- macros/contexts/contextFraction.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/contexts/contextFraction.pl b/macros/contexts/contextFraction.pl index 539db4e3e9..4c559b5232 100644 --- a/macros/contexts/contextFraction.pl +++ b/macros/contexts/contextFraction.pl @@ -781,7 +781,7 @@ package context::Fraction::Parser::Number; sub new { my $self = shift; - my $num = &{ $self->super('new') }($self, @_); + my $num = &{ $self->super('new', $_[0]->context) }($self, @_); $num->setExtensionClass('INTEGER') if $num->{value_string} =~ m/^[-+]?[0-9]+$/; return $num->mutate; } From fdf168f93f1ddcf294f247ed0c2d3c54dc7d3fa7 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 6 Nov 2025 13:32:03 -0600 Subject: [PATCH 056/111] Fix an issue with implied multiplication in the contextFraction.pl macro. The issue occurs with the following MWE: ``` DOCUMENT(); loadMacros(qw(PGstandard.pl MathObjects.pl contextUnits.pl contextFraction.pl)); Context(context::Units::extending('Fraction')->withUnitsFor('length')); $a = Compute('2*3 cm'); ENDDOCUMENT(); ``` Attempting to open the problem with the develop or main branch will consume all of your server's resources, and eventually the oomkiller will kill the process. The changes in this pull request were suggested by @dpvc in an email communication between @Alex-Jordan, @dpvc, and myself. --- macros/contexts/contextFraction.pl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/macros/contexts/contextFraction.pl b/macros/contexts/contextFraction.pl index 539db4e3e9..8389c006f1 100644 --- a/macros/contexts/contextFraction.pl +++ b/macros/contexts/contextFraction.pl @@ -646,7 +646,15 @@ sub _check { # operator we didn't otherwise subclass. # package context::Fraction::BOP::Space; -our @ISA = ('context::Fraction::BOP::space'); +our @ISA = ('context::Fraction::Class', 'Parser::BOP'); + +sub _check { + my $self = shift; + my $context = $self->context; + $self->{bop} = $self->{def}{string}; + $self->{def} = $context->{operators}{ $self->{bop} }; + return $self->mutate->_check; +} ################################################################################################# ################################################################################################# From 3c05a412b743a8c702ecdb9bbbc31f4993e1bae2 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 6 Nov 2025 17:27:34 -0600 Subject: [PATCH 057/111] Possible fix for the eval issue. This is a possible fix for #1340. This is now @dpvc's suggested code from his comment in #1340. --- macros/contexts/contextFraction.pl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/macros/contexts/contextFraction.pl b/macros/contexts/contextFraction.pl index 539db4e3e9..c55294e9b6 100644 --- a/macros/contexts/contextFraction.pl +++ b/macros/contexts/contextFraction.pl @@ -813,7 +813,7 @@ sub make { my $context = (Value::isContext($_[0]) ? shift : $self->context); my $x = shift; $x = $context->Package("Formula")->new($context, $x)->eval if !ref($x) && $x =~ m!/!; - $x = $x->eval if @_ == 0 && Value::classMatch($x, 'Fraction'); + return $x->eval if @_ == 0 && Value::classMatch($x, 'Fraction'); return $self->mutate($context)->make($context, $x, @_); } @@ -852,10 +852,10 @@ sub new { return $x->[0] if Value::classMatch($x->[0], 'Fraction') && @_ == 0; $x = $context->toFraction($x->[0]->value) if Value::isReal($x->[0]) && @_ == 0; return $self->formula($x) if Value::isFormula($x->[0]) || Value::isFormula($x->[1]); - Value::Error("Fraction numerators must be integers") unless isInteger($x->[0]); - Value::Error("Fraction denominators must be integers") unless isInteger($x->[1]); my ($a, $b) = ($x->[0]->value, $x->[1]->value); ($a, $b) = (-$a, -$b) if $b < 0; + Value::Error("Fraction numerators must be integers") unless isInteger($a); + Value::Error("Fraction denominators must be integers") unless isInteger($b); Value::Error("Denominator can't be zero") if $b == 0; ($a, $b) = context::Fraction::reduce($a, $b) if $context->flag("reduceFractions"); bless { data => [ $a, $b ], context => $context }, $class; @@ -872,6 +872,8 @@ sub make { push(@_, 0) if @_ == 0; push(@_, 1) if @_ == 1; my ($a, $b) = @_; + $a = $a->value if Value::isReal($a); + $b = $b->value if Value::isReal($b); ($a, $b) = (-$a, -$b) if $b < 0; return $context->Package("Real")->make($context, $a / $b) unless isInteger($a) && isInteger($b); ($a, $b) = context::Fraction::reduce($a, $b) if $context->flag("reduceFractions"); From f5907157a949eaa0fcd6d51f8fd1b349a42bdaed Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Sun, 9 Nov 2025 15:56:50 -0700 Subject: [PATCH 058/111] Allow plotting Fy = 0 in plots. Since 0 returns false, the logic to ensure Fy was defined was incorrect, and needs to check Fy is not the empty string. --- lib/Plots/Data.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Plots/Data.pm b/lib/Plots/Data.pm index 2f6a07c383..35c10f7f24 100644 --- a/lib/Plots/Data.pm +++ b/lib/Plots/Data.pm @@ -225,7 +225,7 @@ sub set_function { $f->{"x$key"} = $options{$key}; delete $options{$key}; } - return unless $f->{Fy}; + return unless $f->{Fy} ne ''; $f->{Fx} = $self->get_math_object($f->{Fx}, $f->{xvar}, $f->{yvar}); $f->{Fy} = $self->get_math_object($f->{Fy}, $f->{xvar}, $f->{yvar}); From f6975f6adcaeba30343d2f45f2f6f2354c492d80 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 6 Nov 2025 13:45:21 -0600 Subject: [PATCH 059/111] Fix the legacyFraction.pl init method. This causes the macro to fail to load. --- macros/contexts/legacyFraction.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/contexts/legacyFraction.pl b/macros/contexts/legacyFraction.pl index dbdcbfe64f..6972174ec8 100644 --- a/macros/contexts/legacyFraction.pl +++ b/macros/contexts/legacyFraction.pl @@ -223,7 +223,7 @@ =head1 DESCRIPTION =cut -sub _contextFraction_init { context::Fraction::Init() } +sub _legacyFraction_init { context::Fraction::Init() } ########################################################################### From 76cb3e23d5744097e3df959ebe0a056997a474d3 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 27 Nov 2025 15:43:56 -0700 Subject: [PATCH 060/111] Fix numeric values for CheckboxLists. For the following MWE the correct answer is not accepted as correct: ```perl DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserCheckboxList.pl', 'PGcourse.pl'); $checkList = CheckboxList( [ 50, 51, 52, 53 ], [0], labels => 'ABC', values => [ 50, 51, 52, 53 ] ); BEGIN_PGML Select [`50`]. [_]{$checkList} END_PGML ENDDOCUMENT(); ``` The problem is that the only correct answer 50 is turned into a `Value::Real` instead of a `Value::String`. However, the student answer is parsed into a `Value::String`, and then the types don't match and it is marked incorrect. To fix this explicitly construct the correct answers as strings. --- macros/parsers/parserCheckboxList.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index d2e80c6d36..9a6c86c4ea 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -255,7 +255,7 @@ sub new { $self->getCheckedChoices($self->{checked}); $context->strings->add(map { ($self->{values}[$_] => {}) } (0 .. ($self->{n} - 1))); - $_ = Value::makeValue($_, context => $context) for @{ $self->data }; + $_ = $context->Package('String')->make($context, $_) for @{ $self->data }; return $self; } From b87e1c2d7b70d60aa276d8f366b03e8e3308c635 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 30 Oct 2025 17:47:16 -0500 Subject: [PATCH 061/111] Fix a bug with MathQuill options in the context flag before it is really an issue. The current code passes on the option from the context as a hard coded zero instead of the actual option value from the context flag. This works at this point because this is only used by the parserLogb.pl macro to make MathQuill not use the base change formula and the option value it is passing is zero. However, in the future if a context ever wanted to enable a MathQuill feature then it wouldn't work. --- macros/PG.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/macros/PG.pl b/macros/PG.pl index ef398570db..aea6e6a3ba 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -994,7 +994,8 @@ sub ENDDOCUMENT { if ($ansHash->{correct_value}) { for (keys %{ $ansHash->{correct_value}->context->flag('mathQuillOpts') }) { - $mq_part_opts->{$_} = 0 unless defined $mq_part_opts->{$_}; + $mq_part_opts->{$_} = $ansHash->{correct_value}->context->flag('mathQuillOpts')->{$_} + unless defined $mq_part_opts->{$_}; } } From 8e09356f78b6655a01162fa38cd59025853f20d2 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 14 Oct 2025 18:59:04 -0500 Subject: [PATCH 062/111] Restructure the warning handling for the LaTeXImage.pm module. This moves the error warnings up to immediately after latex or pdflatex/xelatex (whichever is the latex2pdf program) runs. This way these errors are always warned. When the `TikZ.pm` module was converted the the `LaTeXImage.pm` module and restructured for PTX output, it resulted in the case that a `pdf` file is the end result (such as for hardcopy) not having these warnings issued. This is to address issue https://github.com/openwebwork/webwork2/issues/2834. --- lib/LaTeXImage.pm | 64 +++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/lib/LaTeXImage.pm b/lib/LaTeXImage.pm index 8c818cd3a3..b04fd368df 100644 --- a/lib/LaTeXImage.pm +++ b/lib/LaTeXImage.pm @@ -183,8 +183,17 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex') . " --interaction=nonstopmode image-dvisvgm.tex > latex.stdout 2> /dev/null"; - move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); - chmod(oct(664), "$working_dir/image.dvi"); + + if (-r "$working_dir/image-dvisvgm.dvi") { + move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); + chmod(oct(664), "$working_dir/image.dvi"); + } else { + warn 'The dvi file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image-dvisvgm.tex for writing."; return ''; @@ -200,7 +209,16 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex2pdf') . " --interaction=nonstopmode image.tex > latex.stdout 2> /dev/null"; - chmod(oct(664), "$working_dir/image.pdf"); + + if (-r "$working_dir/image.pdf") { + chmod(oct(664), "$working_dir/image.pdf"); + } else { + warn 'The pdf file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image.tex for writing."; return ''; @@ -208,39 +226,19 @@ sub draw { } # Make derivatives of the dvi - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm') { - if (-r "$working_dir/image.dvi") { - $self->use_svgMethod($working_dir); - } else { - warn "The dvi file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } - } + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm' && -r "$working_dir/image.dvi") { + $self->use_svgMethod($working_dir); } # Make derivatives of the pdf - if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf') { - if (-r "$working_dir/image.pdf") { - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { - $self->use_svgMethod($working_dir); - } - if ($ext eq 'tgz') { - $self->use_convert($working_dir, "png"); - } elsif ($ext ne 'svg' && $ext ne 'pdf') { - $self->use_convert($working_dir, $ext); - } - } else { - warn "The pdf file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } + if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf' && -r "$working_dir/image.pdf") { + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { + $self->use_svgMethod($working_dir); + } + if ($ext eq 'tgz') { + $self->use_convert($working_dir, 'png'); + } elsif ($ext ne 'svg' && $ext ne 'pdf') { + $self->use_convert($working_dir, $ext); } } From 1e33c7cf71142f34fd7fc4b67fa3b719b9a0b5ea Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 3 Dec 2025 10:09:25 -0600 Subject: [PATCH 063/111] Update the browserslist-db to eliminate the warning to do so. This is simply the result of executing `npx update-browserslist-db@latest`. --- htdocs/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 83ebb43adf..8e070015f2 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -544,9 +544,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "dev": true, "funding": [ { @@ -2314,9 +2314,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "dev": true }, "chokidar": { From f21a8820bd4d9c89cff465b17978749681c4251c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Fri, 12 Dec 2025 21:36:17 -0700 Subject: [PATCH 064/111] Use strong and em instead of b and i tags for bold and italic. PGML should use strong and emphasis for bold and italic. This also updates `$BITALIC` and `$EITALIC` in PGbasicmacros.pl to use emphasis. The `$BBOLD` and `$EBOLD` variables were already using strong, so this also makes things consistent. --- macros/core/PGML.pl | 4 ++-- macros/core/PGbasicmacros.pl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index 355c2c3de1..34cba7fc06 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1613,13 +1613,13 @@ sub Par { sub Bold { my $self = shift; my $item = shift; - return '' . $self->string($item) . ''; + return '' . $self->string($item) . ''; } sub Italic { my $self = shift; my $item = shift; - return '' . $self->string($item) . ''; + return '' . $self->string($item) . ''; } our %openQuote = ('"' => "“", "'" => "‘"); diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 94b8e5657c..c730e67272 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -1319,8 +1319,8 @@ sub SPACE { sub EBOLD { MODES(TeX => '}', HTML => '', PTX => ''); } sub BLABEL { MODES(TeX => '', HTML => '', PTX => ''); } -sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } -sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } +sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } +sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } sub BUL { MODES(TeX => '\\underline{', HTML => '', PTX => ''); } sub EUL { MODES(TeX => '}', HTML => '', PTX => ''); } From f6692fae0f8e9257533686b5dd498c1b8ab9862b Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 8 Oct 2025 07:03:04 -0500 Subject: [PATCH 065/111] Fix some issues with plots.pl and the graph tool. First, fix the alt attribute for TikZ and GD image types for plots.pl. The `image` method of `PGbasicmacros.pl` currently sets the `aria_description` with the value of the `alt` option if it is passed to the method, and it does so regardless of the plots image type. However, that is only used for `html` output with JSXGraph. Thus for TikZ or GD image types the alt attribute is lost. This makes it so that the `aria_description` is only set for `html` output, and for all other outputs the given alt tag is left in the `@alt_list` so that the later code that inserts the `` tag can get it and add it as an attribute to the `` tag. You can test this with the following MWE: ```perl DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); $plot = Plot(); $plot->add_function('x^2', 'x', -10, 10, color => 'blue'); $plot->image_type('tikz'); BEGIN_PGML [!graph of x^2!]{$plot}{300} END_PGML ENDDOCUMENT(); ``` With that example and the current code, the image will not have an alt attribute, but will with this pull request. If you remove the line that sets the image type to 'tikz', then the JSXGraph image will get the aria description (with the second fix below). Second, fix the aria description for both JSXGraph output of plots.pl and the graph tool. This is caused by the removal of the `description` option for the `JXG.Board` object in the JSXGraph library. I must have missed this when this happened three years ago. Although, it seems to have been done rather quietly, as this is not listed in the change log for JSXGraph. To fix this, I just do the same thing that the `description` option used to do, and add a visually hidden span that the graph is `aria-describedby`. Note that there is a new `aria-description` attribute that could be used in the future for this, but it is in a future aria specification, and I don't know how well supported it is at this point. Finally, fix some issues with GD output of the plots.pl macro. This is caused when an Plots::Plot object does not have the height explicitly set. For TikZ and JSXGraph output, the `size` method is called which determines the height if it is not set explicitly. So GD output should do the same. --- htdocs/js/GraphTool/graphtool.js | 9 ++++++++- lib/Plots/GD.pm | 14 +++++++------- lib/Plots/JSXGraph.pm | 7 ++++++- lib/Plots/Tikz.pm | 2 +- macros/core/PGbasicmacros.pl | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index 8edb82c3c7..c674837746 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -69,7 +69,6 @@ window.graphTool = (containerId, options) => { if ('htmlInputId' in options) gt.html_input = document.getElementById(options.htmlInputId); const cfgOptions = { title: 'WeBWorK Graph Tool', - description: options.ariaDescription ?? 'Interactively graph objects', showCopyright: false, pan: { enabled: false }, zoom: { enabled: false }, @@ -116,6 +115,14 @@ window.graphTool = (containerId, options) => { const setupBoard = () => { gt.board = JXG.JSXGraph.initBoard(`${containerId}_graph`, cfgOptions); + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${containerId}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Interactively graph objects'; + gt.board.containerObj.after(descriptionSpan); + gt.board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + gt.board.suspendUpdate(); // Move the axes defining points to the end so that the arrows go to the board edges. diff --git a/lib/Plots/GD.pm b/lib/Plots/GD.pm index 5b75c34640..800374a002 100644 --- a/lib/Plots/GD.pm +++ b/lib/Plots/GD.pm @@ -63,7 +63,8 @@ sub im_y { return unless defined($y); my $plots = $self->plots; my ($ymin, $ymax) = ($plots->axes->yaxis('min'), $plots->axes->yaxis('max')); - return int(($ymax - $y) * $plots->{height} / ($ymax - $ymin)); + (undef, my $height) = $plots->size; + return int(($ymax - $y) * $height / ($ymax - $ymin)); } sub moveTo { @@ -217,12 +218,11 @@ sub draw_circle_stamp { } sub draw { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $grid = $axes->grid; - my $width = $plots->{width}; - my $height = $plots->{height}; + my $self = shift; + my $plots = $self->plots; + my $axes = $plots->axes; + my $grid = $axes->grid; + my ($width, $height) = $plots->size; # Initialize image $self->im->interlaced('true'); diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index f4d386360f..6b531d8612 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -404,7 +404,6 @@ sub init_graph { my $JSXOptions = Mojo::JSON::encode_json({ title => $axes->style('aria_label'), - description => $axes->style('aria_description'), boundingBox => [ $xmin, $ymax, $xmax, $ymin ], axis => 0, showNavigation => $allow_navigation, @@ -497,6 +496,12 @@ sub init_graph { $self->{JSend} = ''; $self->{JS} = <<~ "END_JS"; const board = JXG.JSXGraph.initBoard(id, $JSXOptions); + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `\${id}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = '${\($axes->style('aria_description'))}'; + board.containerObj.after(descriptionSpan); + board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); board.suspendUpdate(); board.create('axis', [[$xmin, $xaxis_pos], [$xmax, $xaxis_pos]], $XAxisOptions); board.create('axis', [[$yaxis_pos, $ymin], [$yaxis_pos, $ymax]], $YAxisOptions); diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 37cbcd6b78..08d18eacbf 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -221,7 +221,7 @@ sub get_plot_opts { } my $end_markers = ($start || $end) ? ", $start-$end" : ''; $marks = $self->get_mark($marks); - $marks = $marks ? $mark_size ? ", mark=$marks, mark size=${mark_size}px" : ", mark=$marks" : ''; + $marks = $marks ? $mark_size ? ", mark=$marks, mark size=${mark_size}pt" : ", mark=$marks" : ''; $linestyle =~ s/ /_/g; $linestyle = { diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index c730e67272..6437d71e92 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -2928,9 +2928,9 @@ sub image { $image_item->{width} = $width if $out_options{width}; $image_item->{height} = $height if $out_options{height}; $image_item->{tex_size} = $tex_size if $out_options{tex_size}; - $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; if ($image_item->ext eq 'html') { + $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; $image_item->{description_details} = $description_details; push(@output_list, $image_item->draw); next; From 08d205776d1ce6c9bef0177353a96dd652cf4d81 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Fri, 26 Dec 2025 21:20:58 -0800 Subject: [PATCH 066/111] PTX stuff for parserRadioMultiAnswer --- macros/parsers/parserRadioMultiAnswer.pl | 49 +++++++++++++++++++++--- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/macros/parsers/parserRadioMultiAnswer.pl b/macros/parsers/parserRadioMultiAnswer.pl index 3cee48a56f..57163dc1bf 100644 --- a/macros/parsers/parserRadioMultiAnswer.pl +++ b/macros/parsers/parserRadioMultiAnswer.pl @@ -16,7 +16,7 @@ =head1 DESCRIPTION $rma = RadioMultiAnswer([ ['The unique solution is \(x=\) %s and \(y=\) %s.', 5, 6], ['There are an infinite number of solutions parameterized by ' - . '\(x=\) %s and \(y=\) %s.', '23-3t', 't'] + . '\(x=\) %s and \(y=\) %s.', '23-3x', 'x'], ['There are no solutions.'] ], 0); @@ -233,6 +233,14 @@ =head1 OPTIONS =back +=item showInStatic (Default: showInStatic => 1) + +In static output, such as PDF or PTX, this controls whether or not the list of answer options is +displayed. (The text preceding the list of answer options might make printing the answer option +list unnecessary in a static output format.) + +=back + =cut BEGIN { strict->import } @@ -288,6 +296,7 @@ sub new { size => undef, checked => undef, uncheckable => 0, + showInStatic => 1, @inputs ); @@ -593,6 +602,7 @@ sub ANS_NAME { # Produce the label for a part of the radio answer. sub label { my ($self, $i) = @_; + $self->{originalLabels} //= $self->{labels}; return $self->{labels}[$i] if ref($self->{labels}) eq 'ARRAY' && $#{ $self->{labels} } >= $i; $self->{labels} = [ @main::ALPHABET[ 0 .. $#{ $self->{data} } ] ] if uc($self->{labels}) eq 'ABC'; @@ -638,6 +648,7 @@ sub generate_aria_label { # Produce the answer rule. sub ans_rule { my ($self, $size, @options) = @_; + $size ||= 20; my @data = @{ $self->{data} }; my @rules; @@ -705,18 +716,44 @@ sub ans_rule { push(@rules, $rule); } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + + my $ptx_list_type = 'ul'; + my $ptx_sub_type = ' form="buttons"'; + if ($main::displayMode eq 'PTX') { + # Do we want an ol, ul, or dl? + if ($self->{displayLabels}) { + my $originalLabels = $self->{originalLabels}; + if (ref $originalLabels eq 'ARRAY') { + $ptx_list_type = 'dl'; + $ptx_sub_type = ' width = "narrow"'; + } elsif ($originalLabels =~ m/^(123|abc)$/i) { + my $marker = ''; + $marker = '1' if $originalLabels eq '123'; + $marker = 'a' if $originalLabels eq 'abc'; + $marker = 'A' if uc($originalLabels) eq 'ABC' && $originalLabels ne 'abc'; + $ptx_list_type = 'ol'; + $ptx_sub_type = qq( marker="$marker"); + } + } + } + return main::MODES( TeX => '\\begin{itemize}', - HTML => '
    ' + HTML => '
    ', + PTX => qq(<${ptx_list_type}${ptx_sub_type} name="$radio_name">) ) - . join(main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem')), @rules) - . main::MODES(TeX => '\\end{itemize}', HTML => '
    '); + . join( + main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem'), PTX => ''), + @rules + ) . main::MODES(TeX => '\\end{itemize}', HTML => '
    ', PTX => ""); } # Format a label. sub label_format { my ($self, $label) = @_; return '' unless $self->{displayLabels} && defined $label && $label ne ''; + return '' . $self->quoteXML($label) . '' if $main::displayMode eq 'PTX'; return sprintf($self->{labelFormat}, main::MODES(TeX => $self->quoteTeX($label), HTML => $self->quoteHTML($label))); } @@ -734,6 +771,8 @@ sub begin_radio { if ($extend) { main::EXTEND_RESPONSE($name, $name, $value, $checked) } else { $name = main::RECORD_ANS_NAME($name, { $value => $checked }) } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + my $idSuffix = $extend ? "_$value" : ''; return main::MODES( @@ -754,7 +793,7 @@ sub begin_radio { $checked ? (checked => undef) : () ) . main::tag('label', for => "$name$idSuffix", $tag), - PTX => "
  • $tag", + PTX => ref $self->{originalLabels} eq 'ARRAY' ? "
  • $tag" : '
  • ', ); } From 0920b1611f441ad8102f11a52b49fe321ef486ce Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Fri, 20 Jun 2025 12:08:31 -0700 Subject: [PATCH 067/111] PG critic script. --- bin/pg-critic.pl | 153 ++++++++++++++ lib/WeBWorK/PG/PGProblemCritic.pm | 319 ++++++++++++++++++++++++++++++ 2 files changed, 472 insertions(+) create mode 100755 bin/pg-critic.pl create mode 100644 lib/WeBWorK/PG/PGProblemCritic.pm diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl new file mode 100755 index 0000000000..2fc6b855f2 --- /dev/null +++ b/bin/pg-critic.pl @@ -0,0 +1,153 @@ +#!/usr/bin/env perl + +=head1 NAME + +pg-critic.pl -- Analyze a pg file for use of old and current methods. + +=head1 SYNOPSIS + + pg-critic.pl [options] file1 file2 ... + +Options: + + -s|--score Give a score for each file. + -f|--format Format of the output. Default ('text') is a plain text listing of the filename + and the score. 'JSON' will make a JSON file. + For output format 'JSON', the filename output must also be assigned, + however for 'text', the output is optional. + -o|--output-file Filename for the output. Note: this is required if JSON is the output format. + -d|--details Include the details in the output. (Only used if the format is JSON). + -v|--verbose Increase the verbosity of the output. + -h|--help Show the help message. + +=head1 DESCRIPTION + +This script analyzes the input files for old/deprecated functions and macros as well +as features for current best practices features. + +See L for details on what features are determined presence. + +=head1 OPTIONS + +The option C<-v> or C<--verbose> gives more information (on STDOUT) as the +script is run. + +The option C<-s> or C<--score> will return a score for each given PG problem. + +=cut + +use strict; +use warnings; +use experimental 'signatures'; +use feature 'say'; + +use Mojo::File qw(curfile); +use Mojo::Util qw(dumper); +use Mojo::JSON qw(encode_json); +use Getopt::Long; +use Pod::Usage; + +use lib curfile->dirname->dirname . '/lib'; + +use WeBWorK::PG::PGProblemCritic qw(analyzePGfile); + +my ($verbose, $show_score, $details, $show_help) = (0, 1, 0, 0); +my ($format, $filename) = ('text', ''); +GetOptions( + 's|score' => \$show_score, + 'f|format=s' => \$format, + 'o|output-file=s' => \$filename, + 'd|details' => \$details, + "v|verbose" => \$verbose, + 'h|help' => \$show_help +); +pod2usage(2) if $show_help || !$show_score; + +die 'arguments must have a list of pg files' unless @ARGV > 0; +die "The output format must be 'text' or 'JSON'" if (scalar(grep { $_ eq $format } qw(text JSON)) == 0); + +my $output_file; +unless ($format eq 'text' && $filename eq '') { + die "The output-file is required if using the format: $format" if $filename eq ''; + $output_file = Mojo::File->new($filename); + my $dir = $output_file->dirname->realpath; + die "The output directory $dir does not exist or is not a directory" unless -d $dir->to_string; +} + +# Give a problem an assessment score: + +my $rubric = { + metadata => -5, # score for each missing required metadta + good => { + PGML => 20, + solution => 30, + hint => 10, + scaffold => 50, + custom_checker => 50, + multianswer => 30, + answer_hints => 20, + nicetable => 10, + contexts => { base_n => 10, units => 10, boolean => 10, reaction => 10 }, + parsers => { radio_buttons => 10, checkbox_list => 10, radio_multianswer => 10, graph_tool => 10 }, + macros => { + random_person => 10, + plots => 10, + tikz => 10, + plotly3D => 10, + latex_image => 10, + scaffold => 10, + answer_hints => 10, + } + }, + bad => { + BEGIN_TEXT => -10, + beginproblem => -5, + oldtable => -25, + num_cmp => -75, + str_cmp => -75, + fun_cmp => -75, + context_texstrings => -5, + multiple_loadmacros => -20, + showPartialCorrect => -5, + lines_below_enddocument => -5, + macros => { ww_plot => -20, PGchoicemacros => -20 } + }, + deprecated_macros => -10 +}; + +sub scoreProblem ($prob) { + my $score = 0; + $score += (1 - $prob->{metadata}{$_}) * $rubric->{metadata} for (keys %{ $prob->{metadata} }); + $score += $prob->{good}{$_} * $rubric->{good}{$_} for (keys %{ $prob->{good} }); + $score += $prob->{bad}{$_} * $rubric->{bad}{$_} for (keys %{ $prob->{bad} }); + $score += $rubric->{deprecated_macros} for (@{ $prob->{deprecated_macros} }); + return $score; +} + +my @scores; + +for (grep { $_ =~ /\.pg$/ } @ARGV) { + say $_ if $verbose; + my $features = analyzePGfile($_); + my $file_info = { file => $_, score => scoreProblem($features) }; + $file_info->{details} = $features if $details; + push(@scores, $file_info); +} + +if ($format eq 'text') { + my $output_str = ''; + for my $score (@scores) { + $output_str .= "filename: $score->{file}; score: $score->{score}\n"; + } + if ($filename eq '') { + say $output_str; + } else { + $output_file->spew($output_str); + say "Results written in text format to $output_file" if $verbose; + } +} elsif ($format eq 'JSON') { + $output_file->spew(encode_json(\@scores)); + say "Results written in JSON format to $output_file" if $verbose; +} + +1; diff --git a/lib/WeBWorK/PG/PGProblemCritic.pm b/lib/WeBWorK/PG/PGProblemCritic.pm new file mode 100644 index 0000000000..f637d36f60 --- /dev/null +++ b/lib/WeBWorK/PG/PGProblemCritic.pm @@ -0,0 +1,319 @@ + +=head1 NAME + +PGProblemCritic - Parse a PG program and analyze the contents for positive and negative features. + +=head1 DESCRIPTION + +Analyze a pg file for use of old and current methods. + +=over + +=item * C: a list of the macros that the problem uses that is in the C +folder. + +=item * Positive features: + +=over + +=item * Uses PGML + +=item * Provides a solution + +=item * Provides a hint + +=item * Uses Scaffolds + +=item * Uses a custom checker + +=item * Uses a multianswer + +=item * Uses answer hints + +=item * Uses nicetables + +=item * Uses randomness + +=back + +=item Old and deprecated features + +=over + +=item * Use of BEGIN_TEXT/END_TEXT + +=item * Include the C + +=item * Include old tables (for example from C) + +=item * The use of C, C and C in lieu of using MathObjects + +=item * Including C<< Context()->TeXStrings >> + +=item * Calling C more than once. + +=item * Using the line C< $showPartialCorrectAnswers = 1 > which is the default behavior and thus unnecessary. + +=item * Using methods from C + +=item * Inlcuding code or other text below the C line indicating the end of the problem. + +=back + +=back + + +=cut + +package WeBWorK::PG::PGProblemCritic; +use parent qw(Exporter); + +use strict; +use warnings; +use experimental 'signatures'; +use feature 'say'; + +use Mojo::File qw(curfile); +use Mojo::Util qw(dumper); + +our @EXPORT_OK = qw(analyzePGfile analyzePGcode getDeprecatedMacros); + +sub analyzePGcode ($code) { + # default flags for presence of features in a PG problem + my $features = { + metadata => { DBsubject => 0, DBchapter => 0, DBsection => 0, KEYWORDS => 0 }, + positive => { + PGML => 0, + solution => 0, + hint => 0, + custom_checker => 0, + multianswer => 0, + nicetables => 0, + randomness => 0, + contexts => { BaseN => 0, Units => 0, Boolean => 0, Reaction => 0 }, + parsers => + { dropdown => 0, RadioButtons => 0, CheckboxList => 0, RadioMultianswer => 0, GraphTool => 0 }, + macros => { + randomPerson => 0, + Plots => 0, + PGtikz => 0, + Plotly3D => 0, + PGlateximage => 0, + Scaffold => 0, + AnswerHints => 0, + } + }, + negative => { + BEGIN_TEXT => 0, + beginproblem => 0, + oldtable => 0, + num_cmp => 0, + str_cmp => 0, + fun_cmp => 0, + context_texstrings => 0, + multiple_loadmacros => 0, + showPartialCorrect => 0, + lines_below_enddocument => 0, + macros => { PGgraphmacros => 0, PGchoicemacros => 0 } + }, + deprecated_macros => [], + macros => [] + }; + + # Get a list of all deprecated macros. + my $all_deprecated_macros = getDeprecatedMacros(curfile->dirname->dirname->dirname->dirname); + + # determine if the loadMacros has been parsed. + my $loadmacros_parsed = 0; + + my @pglines = split /\n/, $code; + my $line = ''; + while (1) { + $line = shift @pglines; + # print Dumper $line; + last unless defined($line); # end of the file. + next if $line =~ /^\s*$/; # skip any blank lines. + + # Determine if some of the metadata tags are present. + for (qw(DBsubject DBchapter DBsection KEYWORDS)) { + $features->{metadata}{$_} = 1 if $line =~ /$_\(/i; + } + + # Skip any full-line comments. + next if $line =~ /^\s*#/; + + $features->{positive}{solution} = 1 if $line =~ /BEGIN_(PGML_)?SOLUTION/; + $features->{positive}{hint} = 1 if $line =~ /BEGIN_(PGML_)?HINT/; + + # Analyze the loadMacros info. + if ($line =~ /loadMacros\(/) { + $features->{negative}{multiple_loadmacros} = 1 if $loadmacros_parsed == 1; + $loadmacros_parsed = 1; + # Parse the macros, which may be on multiple rows. + my $macros = $line; + while ($line && $line !~ /\);\s*$/) { + $line = shift @pglines; + + # Strip any comments at the end of lines. + $line =~ s/(.*)#.*/$1/; + $macros .= $line; + } + + $macros =~ s/^\s*loadMacros\(\s*(.*)\s*\);\s*$/$1/; + my @macros; + # if the arguments of loadMacros is q[qw] form, handle this. + if ($macros =~ /^q[qw]?[\(\[\{\/](.*)[\)\]\/\}]$/) { + $macros =~ s/^q[qw]?[\(\[\{\/](.*)[\)\]\/\}]$/$1/; + @macros = grep { $_ ne '' } split(/\s+/, $macros); + } else { # arguments are strings separated by commas. + @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + } + + $features->{macros} = \@macros; + for my $macro (@macros) { + push(@{ $features->{deprecated_macros} }, $macro) if (grep { $macro eq $_ } @$all_deprecated_macros); + } + } elsif ($line =~ /BEGIN_PGML(_SOLUTION|_HINT)?/) { + $features->{positive}{PGML} = 1; + my @pgml_lines; + while (1) { + $line = shift @pglines; + last if $line =~ /END_PGML(_SOLUTON|_HINT)?/; + push(@pgml_lines, $line); + } + + my $pgml_features = analyzePGMLBlock(@pgml_lines); + $features->{negative}{missing_alt_tag} = 1 if $pgml_features->{missing_alt_tag}; + } + + if ($line =~ /ENDDOCUMENT/) { # scan if there are any lines below the ENDDOCUMENT + + do { + $line = shift @pglines; + last unless defined($line); + $features->{negative}{lines_below_enddocument} = 1 if $line !~ /^\s*$/; + } while (defined($line)); + } + + # Check for negative features. + $features->{negative}{beginproblem} = 1 if $line =~ /beginproblem\(\)/; + $features->{negative}{BEGIN_TEXT} = 1 if $line =~ /(BEGIN_(TEXT|HINT|SOLUTION))|EV[23]/; + $features->{negative}{context_texstrings} = 1 if $line =~ /->(texStrings|normalStrings)/; + for (qw(num str fun)) { + $features->{negative}{ $_ . '_cmp' } = 1 if $line =~ /${_}_cmp\(/; + } + $features->{negative}{oldtable} = 1 if $line =~ /BeginTable/i; + $features->{negative}{showPartialCorrect} = 1 if $line =~ /\$showPartialCorrectAnswers\s=\s1/; + $features->{negative}{macros}{PGgraphmacros} = 1 if $line =~ /init_graph\(/; + $features->{negative}{macros}{PGchoicemacros} = 1 + if $line =~ /new_checkbox_multiple_choice/ + || $line =~ /new_match_list/ + || $line =~ /new_select_list/ + || $line =~ /new_multiple_choice/ + || $line =~ /qa\s\(/; + + # check for positive features + # macros: + $features->{positive}{macros}{Scaffold} = 1 if $line =~ /Scaffold::Begin/; + $features->{positive}{macros}{Plots} = 1 if $line =~ /Plot\(/; + $features->{positive}{macros}{Plotly3D} = 1 if $line =~ /Graph3D\(/; + $features->{positive}{macros}{PGtikz} = 1 if $line =~ /createTikZImage\(/; + $features->{positive}{macros}{AnswerHints} = 1 if $line =~ /AnswerHints/; + $features->{positive}{macros}{randomPerson} = 1 if $line =~ /randomPerson\(/ || $line =~ /randomLastName\(/; + $features->{positive}{macros}{PGlateximage} = 1 if $line =~ /createLaTeXImage\(/; + + # contexts: + + $features->{positive}{contexts}{Units} = 1 if $line =~ /Context\(['"]Units['"]\)/; + $features->{positive}{contexts}{BaseN} = 1 if $line =~ /Context\(['"](Limited)?BaseN['"]\)/; + $features->{positive}{contexts}{Boolean} = 1 if $line =~ /Context\(['"]Boolean['"]\)/; + $features->{positive}{contexts}{Reaction} = 1 if $line =~ /Context\(['"]Reaction['"]\)/; + + # parsers: + $features->{positive}{parsers}{PopUp} = 1 if $line =~ /DropDown\(/; + $features->{positive}{parsers}{RadioButtons} = 1 if $line =~ /RadioButtons\(/; + $features->{positive}{parsers}{CheckboxList} = 1 if $line =~ /CheckboxList\(/; + $features->{positive}{parsers}{GraphTool} = 1 if $line =~ /GraphTool\(/; + + # other: + $features->{positive}{multianswer} = 1 if $line =~ /MultiAnswer/; + $features->{positive}{custom_checker} = 1 if $line =~ /checker\s*=>/; + $features->{positive}{nicetables} = 1 if $line =~ /DataTable|LayoutTable/; + $features->{positive}{randomness} = 1 if $line =~ /random\(|random_(\w+)\(|list_random\(/; + + } + return $features; +} + +# Return a list of the macro filenames in the 'macros/deprecated' directory. +sub getDeprecatedMacros ($pgroot) { + return Mojo::File->new($pgroot)->child('macros/deprecated')->list->map(sub { $_->basename })->to_array; +} + +sub analyzePGfile ($file) { + my $path = Mojo::File->new($file); + die "The file: $file does not exist or is not readable" unless -r $path; + + return analyzePGcode($path->slurp); +} + +# Parse a string that is a function in the form of "funct($arg1, $arg2, ..., param1 => val1, param2 => val2 , ...)" +# A hashref of the form {_args = [$arg1, $arg2, ...], param1 => val1, param2 => val2} is returned. + +sub parseFunctionString($string) { + + my ($funct, $args); + if ($string =~ /(\w+)\(\s*(.*)\)/) { + ($funct, $args) = ($1, $2); + } else { + return (); + } + + my @args = split(/,\s/, $args); + + my %params = (_name => $funct, _args => []); + for (@args) { + if ($_ !~ /=>/) { + push(@{ $params{_args} }, $_); + } else { + if ($_ =~ /(\w+)\s*=>\s*["']?([^"]*)["']?/) { + my ($key, $value) = ($1, $2); + $params{$key} = $value; + } + } + } + return %params; +} + +# Perform some analysis of a PGML block. + +sub analyzePGMLBlock(@lines) { + my $pgml_features = {}; + + while (1) { + my $line = shift @lines; + last unless defined($line); + + # If there is a perl block analyze [@ @] + if ($line =~ /\[@/) { + my $perl_line = $line; + while ($perl_line !~ /@\]/) { + $line = shift @lines; + $perl_line .= $line; + } + my ($perlcode) = $perl_line =~ /\[@\s*(.*)\s*@\]/; + + my %funct_info = parseFunctionString($perlcode); + if (%funct_info && $funct_info{_name} =~ /image/) { + if (defined($funct_info{extra_html_tags}) && $funct_info{extra_html_tags} !~ /alt/) { + $pgml_features->{missing_alt_tag} = 1; + } + } + + } elsif (my ($alt_text) = $line =~ /\[!(.*)!\]/) { + $pgml_features->{missing_alt_tag} = 1 if $alt_text =~ /^\s$/; + } + + } + return $pgml_features; +} From cf4d73fb2a7bf1ff534d4a39d191a376c0d1724e Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 9 Jul 2025 09:16:03 -0500 Subject: [PATCH 068/111] Add a PG critic for problem code. This uses `Perl::Critic` and custom PG policies for `Perl::Critic` to analyze the code. The custom PG policies must be under the `Perl::Critic::Policy` to be loaded by `Perl::Critic` (they give no alternative for that). That means they are in the `lib/Perl/Critic/Policy` directory. Policies corresponding to everything that was attempted to be detected in #1254 have been implemented except for `randomness`. `randomness` of a problem is far more complicated than just checking if `random`, `list_random`, etc. are called. Basically, the code of a problem is first translated (via the `default_preprocess_code` method of the `WeBWorK::PG::Translator` package), then converted to a `PPI::Document` (the underlying library that `Perl::Critic` uses), and that is passed to `Perl::Critic`. There are some utility methods provided in the `WeBWorK::PG::Critic::Utils` package that can be used by the PG policies. At this point those are `getDeprecatedMacros`, `parsePGMLBlock`, and `parseTextBlock`. The `getDeprecatedMacros` method just lists the macros in the `macros/deprecated` directory. The `parsePGMLBlock` method parses PGML contents, and actually uses PGML::Parse for the parsing, and returns `PPI::Document` representations of the content. At this point only command blocks are returned (perl content of `[@ ... @]` blocks), but more can be added as needed by the policies that are created. The `parseTextBlock` method is similar but parses `BEGIN_TEXT`/`END_TEXT` blocks (and the ilk) using a simplified `ev_substring` approach. At this point only the contents of `\{ ... \}` blocks are returned, and other elements can be added later if needed. Unfortunately, the `parsePGMLBlock` and `parseTextBlock` methods do not give proper positioning within the code, so the line and column numbers of the things in the return value will not be reliable. The only policy that uses these at this point is the `Perl::Critic::Policy::PG::RequireImageAltAttribute` policy and that just reports the violations as being inside the PGML or text block the violations are found in. Also, the original untranslated code is passed to the policies and can be used if needed. The `Perl::Critic::Policy::PG::ProhibitEnddocumentMatter` is the only policy that uses this at this point. Note that since this is just `Perl::Critic` this also reports violations of the core `Perl::Critic` policies (at severity level 4). However, there are policies that clearly don't apply to PG problem code, and so those are disabled. For instance, obviously `use strict` and `use warnings` can't be called in a problem, so the `Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict` and `Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings` policies are disabled. The disabled policies start at line 57 of the `WeBWorK::PG::Critic` package. This may need tweaking as there may be other policies that need to be disabled as well, but those are the common violations that I have seen over the years using this for problems that should not apply to problems (I have used a form of this PG critic without the custom PG policies for some time now -- see https://github.com/drgrice1/pg-language-server). Also note that since this is just `Perl::Critic`, you can also use `## no critic` annotations in the code to disable policy violations for a specific line, the entire file, a specific policy on a specific line, etc. See https://metacpan.org/pod/Perl::Critic#BENDING-THE-RULES. For example, if you have a problem that is in the works and are not ready to add metadata, then add `## no critic (PG::RequireMetadata)` to the beginning of the file, and you won't see the violations for having missing metadata. Note that the `bin/pg-critic.pl` script has a `-s` or `--strict` option that ignores all `## no critic` annotations, and forces all policies to be enforced. The result is a reliable, versatile, and extendable approach for critiquing problem code. Since there was a desire to have a "problem score" and to reward good behavior that has been implemented. That means that not all "violations" are bad. Some of them are good. The score is implemented by setting the "explanation" of each violation as a hash which will have the keys `score` and `explanation`. The score will be positive if the "violation" is good, and negative otherwise. The `explanation` is of course a string that would be the usual explanation. This is a bit of a hack since `Perl::Critic` expects the violation to be either a string or a reference to an array of numbers (page numbers in the PBP book), but the `explanation` method of the `Perl::Critic::Violation` object returns the hash as is so this works to get the score from the policy. Although, I am wondering if this "problem score" is really a good idea. If we do start using this and make these scores public, will a low score on a problem deter usage of the problem? It seems like this might happen, and there are basic but quite good problems that are going to get low scores simply because they don't need complicated macros and code for there implementation. Will a high score really mean that a problem is good anyway? What do we really want these scores for? Some sort of validation when our problems get high scores because they utilize the things that happen to be encouraged at the time? I am thinking that this "problem score" idea really was NOT a good idea, and should be removed. If the score is removed, then there is also no point in the "positive violations". Those simply become a "pat on the back" for doing something right which is really not needed (in fact that is all they really are even with the score in my opinion). So my proposal is to actually make this a proper critic that just shows the things in a problem that need improvement, and remove the score and the "positive violations". That is in my opinion what is really important here. --- bin/pg-critic.pl | 213 ++++++------ cpanfile | 6 + .../Policy/PG/EncourageCustomCheckers.pm | 43 +++ .../Policy/PG/EncourageModernContextUsage.pm | 65 ++++ .../Critic/Policy/PG/EncouragePGMLUsage.pm | 57 ++++ .../Policy/PG/EncourageQualityMacroUsage.pm | 100 ++++++ .../Policy/PG/EncourageSolutionsAndHints.pm | 61 ++++ .../Critic/Policy/PG/ProhibitBeginproblem.pm | 36 ++ .../Policy/PG/ProhibitContextStrings.pm | 40 +++ .../Critic/Policy/PG/ProhibitDeprecatedCmp.pm | 85 +++++ .../Policy/PG/ProhibitDeprecatedMacros.pm | 45 +++ .../PG/ProhibitDeprecatedMultipleChoice.pm | 55 +++ .../Policy/PG/ProhibitEnddocumentMatter.pm | 41 +++ .../Critic/Policy/PG/ProhibitGraphMacros.pm | 42 +++ .../PG/ProhibitMultipleLoadMacrosCalls.pm | 38 +++ lib/Perl/Critic/Policy/PG/ProhibitOldText.pm | 53 +++ ...ssarilySettingShowPartialCorrectAnswers.pm | 42 +++ .../Policy/PG/RequireImageAltAttribute.pm | 96 ++++++ lib/Perl/Critic/Policy/PG/RequireMetadata.pm | 51 +++ lib/Perl/Critic/Policy/PG/RequireSolution.pm | 61 ++++ lib/WeBWorK/PG/Critic.pm | 94 ++++++ lib/WeBWorK/PG/Critic/Utils.pm | 159 +++++++++ lib/WeBWorK/PG/PGProblemCritic.pm | 319 ------------------ 23 files changed, 1371 insertions(+), 431 deletions(-) create mode 100644 lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm create mode 100644 lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm create mode 100644 lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm create mode 100644 lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm create mode 100644 lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitOldText.pm create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm create mode 100644 lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm create mode 100644 lib/Perl/Critic/Policy/PG/RequireMetadata.pm create mode 100644 lib/Perl/Critic/Policy/PG/RequireSolution.pm create mode 100644 lib/WeBWorK/PG/Critic.pm create mode 100644 lib/WeBWorK/PG/Critic/Utils.pm delete mode 100644 lib/WeBWorK/PG/PGProblemCritic.pm diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl index 2fc6b855f2..bc4f094225 100755 --- a/bin/pg-critic.pl +++ b/bin/pg-critic.pl @@ -2,7 +2,7 @@ =head1 NAME -pg-critic.pl -- Analyze a pg file for use of old and current methods. +pg-critic.pl - Command line interface to critque PG problem code. =head1 SYNOPSIS @@ -10,144 +10,133 @@ =head1 SYNOPSIS Options: - -s|--score Give a score for each file. - -f|--format Format of the output. Default ('text') is a plain text listing of the filename - and the score. 'JSON' will make a JSON file. - For output format 'JSON', the filename output must also be assigned, - however for 'text', the output is optional. - -o|--output-file Filename for the output. Note: this is required if JSON is the output format. - -d|--details Include the details in the output. (Only used if the format is JSON). - -v|--verbose Increase the verbosity of the output. - -h|--help Show the help message. + -f|--format Format of the output, either 'text' or 'json'. + 'text' is the default and will output a plain text + listing of the results. 'json' will output results in + JavaScript Object Notation. + -o|--output-file Filename to write output to. If not provided output will + be printed to STDOUT. + -n|--no-details Only show the filename and score and do not include the + details in the output for each file. + -s|--strict Disable "## no critic" annotations and force all + policies to be enforced. + -h|--help Show the help message. =head1 DESCRIPTION -This script analyzes the input files for old/deprecated functions and macros as well -as features for current best practices features. - -See L for details on what features are determined presence. - -=head1 OPTIONS - -The option C<-v> or C<--verbose> gives more information (on STDOUT) as the -script is run. - -The option C<-s> or C<--score> will return a score for each given PG problem. +C is a PG problem source code analyzer. It is the executable +front-end to the L module, which attempts to identify +usage of old or deprecated PG features, as well as usage of newer features and +current best practices in coding a problem. =cut -use strict; -use warnings; -use experimental 'signatures'; -use feature 'say'; +use Mojo::Base -signatures; -use Mojo::File qw(curfile); -use Mojo::Util qw(dumper); +use Mojo::File qw(curfile path); use Mojo::JSON qw(encode_json); use Getopt::Long; use Pod::Usage; use lib curfile->dirname->dirname . '/lib'; -use WeBWorK::PG::PGProblemCritic qw(analyzePGfile); +use WeBWorK::PG::Critic qw(critiquePGFile); -my ($verbose, $show_score, $details, $show_help) = (0, 1, 0, 0); -my ($format, $filename) = ('text', ''); GetOptions( - 's|score' => \$show_score, - 'f|format=s' => \$format, - 'o|output-file=s' => \$filename, - 'd|details' => \$details, - "v|verbose" => \$verbose, - 'h|help' => \$show_help + 'f|format=s' => \my $format, + 'o|output-file=s' => \my $filename, + 'n|no-details' => \my $noDetails, + 's|strict' => \my $force, + 'h|help' => \my $show_help ); -pod2usage(2) if $show_help || !$show_score; +pod2usage(2) if $show_help; -die 'arguments must have a list of pg files' unless @ARGV > 0; -die "The output format must be 'text' or 'JSON'" if (scalar(grep { $_ eq $format } qw(text JSON)) == 0); +$format //= 'text'; -my $output_file; -unless ($format eq 'text' && $filename eq '') { - die "The output-file is required if using the format: $format" if $filename eq ''; - $output_file = Mojo::File->new($filename); - my $dir = $output_file->dirname->realpath; - die "The output directory $dir does not exist or is not a directory" unless -d $dir->to_string; -} +$format = lc($format); -# Give a problem an assessment score: - -my $rubric = { - metadata => -5, # score for each missing required metadta - good => { - PGML => 20, - solution => 30, - hint => 10, - scaffold => 50, - custom_checker => 50, - multianswer => 30, - answer_hints => 20, - nicetable => 10, - contexts => { base_n => 10, units => 10, boolean => 10, reaction => 10 }, - parsers => { radio_buttons => 10, checkbox_list => 10, radio_multianswer => 10, graph_tool => 10 }, - macros => { - random_person => 10, - plots => 10, - tikz => 10, - plotly3D => 10, - latex_image => 10, - scaffold => 10, - answer_hints => 10, - } - }, - bad => { - BEGIN_TEXT => -10, - beginproblem => -5, - oldtable => -25, - num_cmp => -75, - str_cmp => -75, - fun_cmp => -75, - context_texstrings => -5, - multiple_loadmacros => -20, - showPartialCorrect => -5, - lines_below_enddocument => -5, - macros => { ww_plot => -20, PGchoicemacros => -20 } - }, - deprecated_macros => -10 -}; +unless (@ARGV) { + say 'A list of pg problem files must be provided.'; + pod2usage(2); +} +unless ($format eq 'text' || $format eq 'json') { + say 'The output format must be "text" or "json"'; + pod2usage(2); +} -sub scoreProblem ($prob) { +sub scoreProblem (@violations) { my $score = 0; - $score += (1 - $prob->{metadata}{$_}) * $rubric->{metadata} for (keys %{ $prob->{metadata} }); - $score += $prob->{good}{$_} * $rubric->{good}{$_} for (keys %{ $prob->{good} }); - $score += $prob->{bad}{$_} * $rubric->{bad}{$_} for (keys %{ $prob->{bad} }); - $score += $rubric->{deprecated_macros} for (@{ $prob->{deprecated_macros} }); + for (@violations) { + if ($_->policy =~ /^Perl::Critic::Policy::PG::/) { + $score += $_->explanation->{score} // 0; + } else { + # Deduct 5 points for any of the default Perl::Critic::Policy violations. + # These will not have a score in the explanation. + $score -= 5; + } + } return $score; } -my @scores; +my @results; -for (grep { $_ =~ /\.pg$/ } @ARGV) { - say $_ if $verbose; - my $features = analyzePGfile($_); - my $file_info = { file => $_, score => scoreProblem($features) }; - $file_info->{details} = $features if $details; - push(@scores, $file_info); -} +for (@ARGV) { + my @violations = critiquePGFile($_, $force); -if ($format eq 'text') { - my $output_str = ''; - for my $score (@scores) { - $output_str .= "filename: $score->{file}; score: $score->{score}\n"; - } - if ($filename eq '') { - say $output_str; - } else { - $output_file->spew($output_str); - say "Results written in text format to $output_file" if $verbose; + my (@positivePGResults, @negativePGResults, @perlCriticResults); + if (!$noDetails) { + @positivePGResults = + grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ && $_->explanation->{score} > 0 } @violations; + @negativePGResults = + grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ && $_->explanation->{score} < 0 } @violations; + @perlCriticResults = grep { $_->policy !~ /^Perl::Critic::Policy::PG::/ } @violations; } -} elsif ($format eq 'JSON') { - $output_file->spew(encode_json(\@scores)); - say "Results written in JSON format to $output_file" if $verbose; + + push( + @results, + { + file => $_, + score => scoreProblem(@violations), + $noDetails + ? () + : ( + positivePGResults => \@positivePGResults, + negativePGResults => \@negativePGResults, + perlCriticResults => \@perlCriticResults + ) + } + ); +} + +Perl::Critic::Violation::set_format('%m at line %l, column %c. (%p)'); + +my $outputMethod = $format eq 'json' ? \&encode_json : sub { + my $results = shift; + + return join( + "\n", + map { ( + "filename: $_->{file}", + "score: $_->{score}", + @{ $_->{positivePGResults} // [] } + ? ('positive pg critic results:', map { "\t" . $_->to_string } @{ $_->{positivePGResults} }) + : (), + @{ $_->{negativePGResults} // [] } + ? ('negative pg critic results:', map { "\t" . $_->to_string } @{ $_->{negativePGResults} }) + : (), + @{ $_->{perlCriticResults} // [] } + ? ('perl critic results:', map { "\t" . $_->to_string } @{ $_->{perlCriticResults} }) + : () + ) } @$results + ); +}; + +if ($filename) { + eval { path($filename)->spew($outputMethod->(\@results), 'UTF-8') }; + if ($@) { say "Unable to write results to $filename: $@"; } + else { say "Results written in $format format to $filename"; } +} else { + say $outputMethod->(\@results); } 1; diff --git a/cpanfile b/cpanfile index 12c7e39b75..00b469c470 100644 --- a/cpanfile +++ b/cpanfile @@ -24,6 +24,12 @@ on runtime => sub { # Needed for Rserve recommends 'IO::Handle'; + + # Needed for WeBWorK::PG::Tidy + recommends 'Perl::Tidy'; + + # Needed for WeBWorK::PG::PGProblemCritic + recommends 'Perl::Critic'; }; on test => sub { diff --git a/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm b/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm new file mode 100644 index 0000000000..45e50f43fc --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm @@ -0,0 +1,43 @@ +package Perl::Critic::Policy::PG::EncourageCustomCheckers; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A custom checker is utilized'; +use constant EXPLANATION => 'Custom checkers demonstrate a high level of sophistication in problem coding.'; +use constant SCORE => 50; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +use Mojo::Util qw(dumper); + +# FIXME: This misses some important cases. For example, answer checking can also be performed in a post filter. In +# fact that demonstrates an even higher level of sophistication than using a checker in some senses. It is more +# complicated to use correctly, and can work around type limitations imposed on MathObject checkers. However, there is +# no reliable way to determine what a post filter is in a problem for, as there are other reasons to add a post filter. +sub violates ($self, $element, $document) { + return unless $element eq 'checker' || $element eq 'list_checker'; + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::EncourageCustomCheckers - Custom checkers demonstrate +a high level of sophistication in problem coding. + +=head1 DESCRIPTION + +Utilization of a custom checker in a problem demonstrates a high level of +sophistication in coding a problem. Custom checkers can be used to supplement +default MathObject checkers in several ways. For example, to award partial +credit and display more meaningful messages for answers that are not entirely +correct + +=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm b/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm new file mode 100644 index 0000000000..f9010cfdd1 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm @@ -0,0 +1,65 @@ +package Perl::Critic::Policy::PG::EncourageModernContextUsage; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +# FIXME: Is this policy really a good idea? Why are these contexts so special? Just because they are newer? Many of the +# contexts that have been around for a long time are actually better than some of these, and some of them are more +# complicated to use and demonstrate a higher level of sophistication than these. + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The context %s is used from the macro %s'; +use constant EXPLANATION => '%s is a modern context whose usage demonstrates currency in problem authoring.'; + +use constant CONTEXTS => { + BaseN => { macro => 'contextBaseN.pl', score => 10 }, + Boolean => { macro => 'contextBoolean.pl', score => 10 }, + Reaction => { macro => 'contextReaction.pl', score => 10 }, + Units => { macro => 'contextUnits.pl', score => 10 } +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $document) { + return unless $element eq 'Context' && is_function_call($element); + my $context = first_arg($element); + return $self->violation( + sprintf(DESCRIPTION, $context->string, CONTEXTS->{ $context->string }{macro}), + { + score => CONTEXTS->{ $context->string }{score}, + explanation => sprintf(EXPLANATION, CONTEXTS->{ $context->string }{macro}) + }, + $context + ) if $context && CONTEXTS->{ $context->string }; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::EncourageModernContextUsage - Usage of recently +created contexts demonstrates currency in problem authoring. + +=head1 DESCRIPTION + +Usage of recently created contexts demonstrates currency in problem authoring. +Currently this policy encourages the use of the following contexts: + +=over + +=item * L + +=item * L + +=item * L + +=item * L + +=back + +=cut diff --git a/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm b/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm new file mode 100644 index 0000000000..fd67cc9de8 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm @@ -0,0 +1,57 @@ +package Perl::Critic::Policy::PG::EncouragePGMLUsage; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'PGML is used for problem text'; +use constant EXPLANATION => 'PGML should be used for problem text.'; +use constant SCORE => 20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc) } + +# Only report this once even if there are multiple PGML blocks in the problem. +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + return $self->violation( + DESCRIPTION, + { score => SCORE, explanation => EXPLANATION }, + $element->parent->parent->parent->parent->parent + ) + if $element->terminator =~ /^END_PGML(_SOLUTION|_HINT)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && $element->parent->parent->parent->first_element eq 'PGML::Format2' + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element); + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::EncouragePGMLUsages - All problems should use PGML to +insert problem text. + +=head1 DESCRIPTION + +All problems should use PGML via C/C, +C/C, or +C/C blocks to insert problem text, +instead of the older C/C, C/C, or +C/C blocks. The PGML syntax is much easier to read +for other problem authors looking at the code, and PGML helps to ensure that +many text elements (for example images and tables) are inserted correctly for +recent requirements for accessibility. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm b/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm new file mode 100644 index 0000000000..79b1cb878f --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm @@ -0,0 +1,100 @@ +package Perl::Critic::Policy::PG::EncourageQualityMacroUsage; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => '%s is used from the macro %s'; +use constant EXPLANATION => '%s is a high quality macro whose usage is encouraged.'; + +# FIXME: A better explanation is needed. Perhaps instead of a single explanation for all macros, add an explanation key +# to each of the methods below and give an explanation specific to the method and macro used. + +use constant METHODS => { + AnswerHints => { macro => 'answerHints.pl', score => 10 }, + CheckboxList => { macro => 'parserCheckboxList.pl', score => 10 }, + createLaTeXImage => { macro => 'PGlateximage.pl', score => 10 }, + createTikZImage => { macro => 'PGtikz.pl', score => 10 }, + DataTable => { macro => 'niceTables.pl', score => 10 }, + DraggableProof => { macro => 'draggableProof.pl', score => 10 }, + DraggableSubset => { macro => 'draggableSubset.pl', score => 10 }, + DropDown => { macro => 'parserPopUp.pl', score => 10 }, + Graph3D => { macro => 'plotly3D.pl', score => 10 }, + GraphTool => { marco => 'parserGraphTool.pl', score => 10 }, + LayoutTable => { macro => 'niceTables.pl', score => 10 }, + MultiAnswer => { macro => 'parserMultiAnswer.pl', score => 30 }, + Plots => { macro => 'plots.pl', score => 10 }, + RadioButtons => { macro => 'parserRadioButtons.pl', score => 10 }, + RadioMultiAnswer => { macro => 'parserRadioMultiAnswer.pl', score => 30 }, + randomLastName => { macro => 'randomPerson.pl', score => 10 }, + randomPerson => { macro => 'randomPerson.pl', score => 10 }, + 'Scaffold::Begin' => { macro => 'scaffold.pl', score => 20 } +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $document) { + return unless METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element, METHODS->{$element}{macro}), + { score => METHODS->{$element}{score}, explanation => sprintf(EXPLANATION, METHODS->{$element}{macro}) }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::EncourageQualityMacroUsage - Usage of macros that are +well maintained and provide advanced MathObject answers is encouraged. + +=head1 DESCRIPTION + +Usage of macros that are well maintained and provide advanced MathObject answers +is encouraged. This policy currently recognizes the usage of the following +macros: + +=over + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=back + +=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm b/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm new file mode 100644 index 0000000000..abe6944869 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm @@ -0,0 +1,61 @@ +package Perl::Critic::Policy::PG::EncourageSolutionsAndHints; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A %s is included'; +use constant EXPLANATION => { + solution => 'A solution should be added to all problems.', + hint => 'A hint is helpful for students.' +}; +use constant SCORE => { solution => 15, hint => 10 }; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc) } + +sub violates ($self, $element, $) { + if ( + $element->terminator =~ /^END_(PGML_)?(SOLUTION|HINT)/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && ($element->parent->parent->parent->first_element eq 'PGML::Format2' + || $element->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element) + ) + { + my $type = lc($1); + return $self->violation( + sprintf(DESCRIPTION, $type), + { score => SCORE->{$type}, explanation => EXPLANATION->{$type} }, + $element->parent->parent->parent->parent->parent + ); + } + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::EncourageSolutionsAndHints - Solutions should be +provided in all problems, and hints are helpful for students. + +=head1 DESCRIPTION + +All problems should provide solutions that demonstrate how to work the problem, +and which do not just give the answers to the problem. + +Hints are helpful for students that are struggling with the concepts presented +in the problem, and it is recommended that hints be added particularly for more +difficult problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm new file mode 100644 index 0000000000..2176f84227 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm @@ -0,0 +1,36 @@ +package Perl::Critic::Policy::PG::ProhibitBeginproblem; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The beingproblem function is called'; +use constant EXPLANATION => 'The beingproblem function no longer does anything and should be removed.'; +use constant SCORE => -5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'beginproblem' && is_function_call($element); + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitBeginproblem - The C function is +deprecated, no longer does anything, and should be removed from all problems. + +=head1 DESCRIPTION + +The C function is deprecated, no longer does anything, and should +be removed from all problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm new file mode 100644 index 0000000000..4487c616e4 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm @@ -0,0 +1,40 @@ +package Perl::Critic::Policy::PG::ProhibitContextStrings; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'Context()->%s is called'; +use constant EXPLANATION => 'Context()->%s no longer necessary and should be removed.'; +use constant SCORE => -5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return + unless ($element eq 'texStrings' || $element eq 'normalStrings') + && is_method_call($element) + && $element->parent =~ /^Context/; + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element) }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireContextStrings - C<< Context()->texStrings >> +and C<< Context->normalStrings >> calls are not necessary and should be removed. + +=head1 DESCRIPTION + +Calling C<< Context()->texStrings >> and C<< Context->normalStrings >> is no +longer necessary and should be removed from problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm new file mode 100644 index 0000000000..21ec89d42f --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm @@ -0,0 +1,85 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedCmp; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s method is called'; +use constant EXPLANATION => 'Convert the answer into a MathObject and use the cmp method of the object.'; +use constant SCORE => -55; + +use constant CMP_METHODS => { + str_cmp => 1, + std_str_cmp => 1, + std_str_cmp_list => 1, + std_cs_str_cmp => 1, + std_cs_str_cmp_list => 1, + strict_str_cmp => 1, + strict_str_cmp_list => 1, + unordered_str_cmp => 1, + unordered_str_cmp_list => 1, + unordered_cs_str_cmp => 1, + unordered_cs_str_cmp_list => 1, + ordered_str_cmp => 1, + ordered_str_cmp_list => 1, + ordered_cs_str_cmp => 1, + ordered_cs_str_cmp_list => 1, + num_cmp => 1, + num_rel_cmp => 1, + std_num_cmp => 1, + std_num_cmp_list => 1, + std_num_cmp_abs => 1, + std_num_cmp_abs_list => 1, + frac_num_cmp => 1, + frac_num_cmp_list => 1, + frac_num_cmp_abs => 1, + frac_num_cmp_abs_list => 1, + arith_num_cmp => 1, + arith_num_cmp_list => 1, + arith_num_cmp_abs => 1, + arith_num_cmp_abs_list => 1, + strict_num_cmp => 1, + strict_num_cmp_list => 1, + strict_num_cmp_abs => 1, + strict_num_cmp_abs_list => 1, + std_num_str_cmp => 1, + fun_cmp => 1, + function_cmp => 1, + function_cmp_up_to_constant => 1, + function_cmp_abs => 1, + function_cmp_up_to_constant_abs => 1, + adaptive_function_cmp => 1, + multivar_function_cmp => 1, + cplx_cmp => 1, + multi_cmp => 1, + radio_cmp => 1, + checkbox_cmp => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $document) { + return unless CMP_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedCmp - Use C instead of +the deprecated C methods. + +=head1 DESCRIPTION + +Convert answers into a C and use the C method of the object +instead of using any of the deprecated C methods such as C from +the L macro, C from the +L macro, or C from the +L macro. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm new file mode 100644 index 0000000000..38e051bb30 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm @@ -0,0 +1,45 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated macro %s is loaded'; +use constant EXPLANATION => 'Remove this macro and replace methods used from this macro with modern alternatives.'; +use constant SCORE => -10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'loadMacros' && is_function_call($element); + my $deprecatedMacros = getDeprecatedMacros; + return unless $deprecatedMacros; + return map { + $self->violation( + sprintf(DESCRIPTION, $_->[0]->string), + { score => SCORE, explanation => EXPLANATION }, + $_->[0] + ) + } + grep { $deprecatedMacros->{ $_->[0]->string } } parse_arg_list($element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMacros - Replace deprecated macro +usage with modern alternatives. + +=head1 DESCRIPTION + +All problems that use a deprecated macro (those in the C directory) +should be rewritten to use modern alternatives. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm new file mode 100644 index 0000000000..d7667be8e9 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => -20; +use constant SAMPLE_PROBLEMS => [ + [ 'Multiple Choice with Checkbox' => 'Misc/MultipleChoiceCheckbox' ], + [ 'Multiple Choice with Popup' => 'Misc/MultipleChoicePopup' ], + [ 'Multiple Choice with Radio Buttons' => 'Misc/MultipleChoiceRadio' ] +]; + +# Note that new_match_list is not in this list because there is not a modern alternative yet. +# The qa method is also not listed because it is needed with new_match_list. +use constant MULTIPLE_CHOICE_METHODS => { + new_checkbox_multiple_choice => 1, + new_multiple_choice => 1, + new_pop_up_select_list => 1, + new_select_list => 1 +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless MULTIPLE_CHOICE_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice - Replace usage of +L multiple choice methods with the appropriate MathObject +multiple choice +macro. + +=head1 DESCRIPTION + +Replace usage of L multiple choice methods with the +appropriate modern multiple choice macro. For example, consider using +L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm new file mode 100644 index 0000000000..9f240628f7 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm @@ -0,0 +1,41 @@ +package Perl::Critic::Policy::PG::ProhibitEnddocumentMatter; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'There is content after the ENDDOCUMENT call'; +use constant EXPLANATION => 'Remove this content. The ENDDOCUMENT call should be at the end of the problem.'; +use constant SCORE => -5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element) + if $element eq 'ENDDOCUMENT' + && is_function_call($element) + && ($document->{_doc}{untranslatedCode} // '') =~ /ENDDOCUMENT[^\n]*\n(.*)/s + && $1 =~ /\S/; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitEnddocumentMatter - There should not be any +content after the C call in a problem. + +=head1 DESCRIPTION + +The C call is intended to signify the end of the problem code. +Although all content after the C call is ignored, there should not +be any content (text or code) in this area. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm new file mode 100644 index 0000000000..734fd74eff --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitGraphMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The init_graph function from PGgraphmacros.pl is called'; +use constant EXPLANATION => 'PGgraphmacros.pl generates poor quality graphics. Consider using a modern alternative.'; +use constant SCORE => -20; +use constant SAMPLE_PROBLEMS => [ + [ 'TikZ Graph Images' => 'ProblemTechniques/TikZImages' ], + [ 'Inserting Images in PGML' => 'ProblemTechniques/Images' ], + [ 'Function Plot' => 'Algebra/FunctionPlot' ] +]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'init_graph' && is_function_call($element); + return $self->violation(DESCRIPTION, + { score => SCORE, explanation => EXPLANATION, sampleProblems => SAMPLE_PROBLEMS }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitGraphMacros - L generates +poor quality graphics. Modern alternatives should be used instead. + +=head1 DESCRIPTION + +L generates poor quality graphics. Replace its usage with a +modern alternative such as L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm new file mode 100644 index 0000000000..b680bbf551 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm @@ -0,0 +1,38 @@ +package Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'loadMacros is called multiple times'; +use constant EXPLANATION => 'Consolidate multiple loadMacros calls into a single call.'; +use constant SCORE => -20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $tokens = $document->find('PPI::Token'); + return unless $tokens; + my @loadMacrosCalls = grep { $_ eq 'loadMacros' && is_function_call($_) } @$tokens; + shift @loadMacrosCalls; + return map { $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $_) } @loadMacrosCalls; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls - The C +function should only be called once in each problem. + +=head1 DESCRIPTION + +The C function should only be called once in each problem. +Consolidate multiple C calls into a single call and make sure that +all macros that are loaded are actually used in the problem. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm new file mode 100644 index 0000000000..ca37c30988 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm @@ -0,0 +1,53 @@ +package Perl::Critic::Policy::PG::ProhibitOldText; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A BEGIN_%1$s/END_%1$s block is used for problem text'; +use constant EXPLANATION => 'Load the macro PGML.pl and replace the BEGIN_%1$s/END_%1$s ' + . 'block with a BEGIN_PGML%2$s/END_PGML%2$s block.'; +use constant SCORE => -10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc) } + +sub violates ($self, $element, $document) { + if ($element->terminator =~ /^END_(TEXT|SOLUTION|HINT)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && $element->parent->parent->parent->first_element eq 'EV3P' + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element)) + { + my $oldType = $1 eq 'STATEMENT' ? 'TEXT' : $1; + return $self->violation( + sprintf(DESCRIPTION, $oldType), + { score => SCORE, explanation => sprintf(EXPLANATION, $oldType, $1 eq 'STATEMENT' ? '' : "_$1") }, + $element->parent->parent->parent->parent->parent + ); + } + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitOldText - Replace old PG text usage with PGML. + +=head1 DESCRIPTION + +Load the C macro and replace all C/C, +C/C, and C/C blocks with +C/C, C/C, and +C/C blocks. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm new file mode 100644 index 0000000000..51252dc6fb --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => '$showPartialCorrectAnswers is set to 1'; +use constant EXPLANATION => 'The value of $showPartialCorrectAnswers is 1 by default, ' + . 'so it should only ever be set to 0 to change the value.'; +use constant SCORE => -5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Operator) } + +sub violates ($self, $element, $) { + return unless is_assignment_operator($element); + my $left = $element->sprevious_sibling; + my $right = $element->snext_sibling; + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $left) + if $left && $left eq '$showPartialCorrectAnswers' && $element eq '=' && $right && $right eq '1'; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers +- There is no need to set C<$showPartialCorrectAnswers> to 1 since that is the +default value. + +=head1 DESCRIPTION + +The value of C<$showPartialCorrectAnswers> is 1 by default, so it should only +ever be set to 0 to change the value. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm new file mode 100644 index 0000000000..e538b409a4 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm @@ -0,0 +1,96 @@ +package Perl::Critic::Policy::PG::RequireImageAltAttribute; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(parsePGMLBlock parseTextBlock); + +use constant DESCRIPTION => 'An image is missing the alt attribute'; +use constant EXPLANATION => 'Add an alt attribute that describes the image content.'; +use constant SCORE => -10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc PPI::Token::Word) } + +sub hasAltTag ($self, $element) { + my @args = + map { $_->[0]->isa('PPI::Token::Quote') ? $_->[0]->string : $_->[0]->content } parse_arg_list($element); + shift @args; # Remove the image argument. + my %args = @args % 2 ? () : @args; + return $args{alt} || ($args{extra_html_tags} && $args{extra_html_tags} =~ /alt/); +} +use Mojo::Util qw(dumper); + +sub violates ($self, $element, $) { + my @violations; + if ($element->isa('PPI::Token::Word') && $element eq 'image' && is_function_call($element)) { + push(@violations, $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element)) + unless $self->hasAltTag($element); + } elsif ( + $element->isa('PPI::Token::HereDoc') + && $element->terminator =~ /^END_(PGML|PGML_SOLUTION|PGML_HINT|TEXT|HINT|SOLUTION)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && ($element->parent->parent->parent->first_element eq 'PGML::Format2' + || $element->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element) + ) + { + for my $command ( + @{ + ( + $element->terminator =~ /PGML/ + ? parsePGMLBlock($element->heredoc)->{commands} + : parseTextBlock($element->heredoc)->{commands} + ) // [] + } + ) + { + for (grep { $_ eq 'image' && is_function_call($_) } @{ $command->find('PPI::Token::Word') || [] }) { + next if $self->hasAltTag($_); + push( + @violations, + $self->violation( + DESCRIPTION + . ' inside the ' + . ($element->terminator =~ s/END/BEGIN/r) . '/' + . ($element->terminator) + . ' block', + { score => SCORE, explanation => EXPLANATION }, + $element->parent->parent->parent->parent->parent + ) + ); + } + } + } + + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireImageAltAttribute - Images created with the +C method should have the C attribute set. + +=head1 DESCRIPTION + +The C attribute is crucial for accessibility, especially for visually +impaired users who rely on screen readers to understand the content of the +problem. So all images added to a problem should have the C attribute set. +Note that it can be set to the empty string to indicate that the image is not +essential to the meaning of the problem content. Generally it is better to use +the PGML syntax for images C<[!alternate text!]{$image}> rather than using the +C method. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireMetadata.pm b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm new file mode 100644 index 0000000000..799f859e6c --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm @@ -0,0 +1,51 @@ +package Perl::Critic::Policy::PG::RequireMetadata; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The %s metadata tag is required'; +use constant EXPLANATION => 'Include the required metadata tags at the beginning of the problem file.'; +use constant SCORE => -5; +use constant REQUIRED_METADATA => [ 'DBsubject', 'DBchapter', 'DBsection', 'KEYWORDS' ]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $comments = $document->find('PPI::Token::Comment'); + my %foundMetadata; + if ($comments) { + for my $comment (@$comments) { + my ($metadataType) = grep { $comment =~ /#\s*$_\(/i } @{ REQUIRED_METADATA() }; + $foundMetadata{$metadataType} = 1 if $metadataType; + } + } + + my @violations; + for (@{ REQUIRED_METADATA() }) { + push(@violations, + $self->violation(sprintf(DESCRIPTION, $_), { score => SCORE, explanation => EXPLANATION }, $document)) + unless $foundMetadata{$_}; + } + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireMetadata - All problems should have the +appropriate OPL metadata tags set. + +=head1 DESCRIPTION + +All problems should have the appropriate OPL metadata tags set. The required +metadata attributes should be set at the beginning of the problem file before +the C call. The metadata tags that are required for all problems are +C, C, C, and C. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireSolution.pm b/lib/Perl/Critic/Policy/PG/RequireSolution.pm new file mode 100644 index 0000000000..937be8bf10 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireSolution.pm @@ -0,0 +1,61 @@ +package Perl::Critic::Policy::PG::RequireSolution; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A solution is not included in this problem'; +use constant EXPLANATION => 'A solution should be included in all problems.'; +use constant SCORE => -15; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + my $solutionFound = 0; + if (my $heredocs = $document->find('PPI::Token::HereDoc')) { + for (@$heredocs) { + if ( + $_->terminator =~ /^END_(PGML_)?SOLUTION$/ + && $_->parent + && $_->parent->parent + && $_->parent->parent->parent + && ($_->parent->parent->parent->first_element eq 'PGML::Format2' + || $_->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($_->parent->parent->parent->first_element) + && $_->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent->first_element eq 'SOLUTION' + && is_function_call($_->parent->parent->parent->first_element) + ) + { + $solutionFound = 1; + last; + } + } + } + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $document) + unless $solutionFound; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireSolution - All problems should provide a +solution. + +=head1 DESCRIPTION + +A solution should be included in all problems. Note that a solution should +demonstrate all steps to solve a problem, and should certainly not just give the +answers to questions in the problem. This is one of the most challenging parts +of PG problem authoring, but the solution should not be omitted. + +=cut diff --git a/lib/WeBWorK/PG/Critic.pm b/lib/WeBWorK/PG/Critic.pm new file mode 100644 index 0000000000..a235fc5412 --- /dev/null +++ b/lib/WeBWorK/PG/Critic.pm @@ -0,0 +1,94 @@ + +=head1 NAME + +WeBWorK::PG::Critic - Critique PG problem source code for best-practices. + +=head1 DESCRIPTION + +Analyze a pg file for use of old and current methods. + +=head1 FUNCTIONS + +=head2 critiquePGCode + + my $results = critiquePGCode($code, $force = 0); + +Parses and critiques the given PG problem source provided in C<$code>. An array +of "violations" that are found is returned. Note that the elements of this +return array are L objects. However, not all of these +"violations" are bad. Some are actually noting good things that are used in the +source code for the problem. The C method can be called for each +element, and that will either return a string or a reference to a hash. The +string return type will occur for a violation of a default L +policy. The last return type will occur with a C +policy, and the hash will contain a C key and an C key +containing the actual explanation. If the C is positive, then it is not +actually a violation, but something good. In some cases the C +return hash will also contain the key C which will be a +reference to an array each of whose entries will be a reference to a two element +array whose first element is the title of a sample problem and whose second +element is the path for that sample problem where the sample problem +demonstrates a way to fix the policy violation. + +Note that C<## no critic> annotations can be used in the code to disable a +violation for a line or the entire file. See L<"BENDING THE +RULES"|https://metacpan.org/pod/Perl::Critic#BENDING-THE-RULES>. However, if +C<$force> is true, then C<## no critic> annotations are ignored, and all +policies are enforced regardless. + +=head2 critiquePGFile + + my $results = critiquePGFile($file, $force); + +This just executes C on the contents of C<$file> and returns +the result. +=cut + +package WeBWorK::PG::Critic; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(path); +use PPI; +use Perl::Critic; + +our @EXPORT_OK = qw(critiquePGFile critiquePGCode); + +sub critiquePGCode ($code, $force = 0) { + my $critic = Perl::Critic->new( + -severity => 4, + -exclude => [ + 'Perl::Critic::Policy::Modules::ProhibitMultiplePackages', + 'Perl::Critic::Policy::Modules::RequireEndWithOne', + 'Perl::Critic::Policy::Modules::RequireExplicitPackage', + 'Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage', + 'Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms', + 'Perl::Critic::Policy::Subroutines::RequireArgUnpacking', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings', + 'Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars', + + # Make sure that Community and Freenode policies are not used if installed on the system. + '.*::Community::.*', '.*::Freenode::.*' + ], + -force => $force + ); + + my $translatedCode = WeBWorK::PG::Translator::default_preprocess_code($code); + + my $document = PPI::Document->new(\$translatedCode); + + # Provide the untranslated code so that policies can access it. It will be in the _doc key of the $document that is + # passed as the third argument to the violates method. See Perl::Critic::Policy::PG::ProhibitEnddocumentMatter which + # uses this for example. + $document->{untranslatedCode} = $code; + + return $critic->critique($document); +} + +sub critiquePGFile ($file, $force = 0) { + my $code = eval { path($file)->slurp('UTF-8') }; + die qq{Unable to read contents of "$file": $@} if $@; + return critiquePGCode($code, $force); +} + +1; diff --git a/lib/WeBWorK/PG/Critic/Utils.pm b/lib/WeBWorK/PG/Critic/Utils.pm new file mode 100644 index 0000000000..a0e1f005d6 --- /dev/null +++ b/lib/WeBWorK/PG/Critic/Utils.pm @@ -0,0 +1,159 @@ + +=head1 NAME + +WeBWorK::PG::Critic::Utils - Utility methods for PG Critic policies. + +=head1 DESCRIPTION + +Utility methods for PG Critic policies. + +=head1 FUNCTIONS + +=head2 getDeprecatedMacros + + my @deprecatedMacros = getDeprecatedMacros(); + +Returns a list of deprecated macros. These are the macros found in the +C directory. + +=head2 parsePGMLBlock + + my $pgmlElements = parsePGMLBlock(@lines); + +Parses the given C<@lines> of code from a PGML block and returns a reference to +a hash containing details of the PGML blocks found. + +If any C<[@ ... @]> blocks are found in the PGML code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<[@ ... @]> +blocks found. + +If the PGML content or a block within fails to parse, then the return hash will +contain the key C with a reference to an array of errors that occurred. + +=head2 parseTextBlock + + my $textElements = parseTextBlock(@lines); + +Parses the given C<@lines> of code from a C/C, +C/C, or C/C block and +returns a reference to a hash containing details of the elements found. + +If any C<\{ ... \}> blocks are found in the code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<\{ ... \}> +blocks found. + +If a block within fails to parse, then the return hash will contain the key +C which is a reference to an array of errors that occurred. + +=cut + +package WeBWorK::PG::Critic::Utils; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(curfile path); +use PPI; +use Perl::Critic::Utils qw(:classification :ppi); +use Scalar::Util qw(blessed); +use Mojo::Util qw(md5_sum encode); +use Env qw(PG_ROOT); + +use lib curfile->dirname->dirname->dirname->dirname->dirname->child('lib'); + +require WeBWorK::PG::Translator; + +our @EXPORT_OK = qw(getDeprecatedMacros parsePGMLBlock parseTextBlock); + +$PG_ROOT = curfile->dirname->dirname->dirname->dirname->dirname; + +sub getDeprecatedMacros () { + state $deprecatedMacros; + return $deprecatedMacros if $deprecatedMacros; + return $deprecatedMacros = + { map { $_->basename => 1 } @{ path($PG_ROOT)->child('macros', 'deprecated')->list } }; +} + +sub main::PG_restricted_eval ($code) { return $code; } + +sub walkPGMLTree ($block, $results //= {}) { + for my $item (@{ $block->{stack} }) { + next unless blessed $item && $item->isa('PGML::Block'); + if ($item->{type} eq 'command') { + my $command = PPI::Document->new(\($item->{text})); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } + walkPGMLTree($item, $results); + } + return $results; +} + +# For now, only command blocks are returned. Add other PGML elements as needed. +sub parsePGMLBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular PGML blocks so that if multiple policies + # use the same PGML block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + package main; ## no critic (Modules::ProhibitMultiplePackages) + + require WeBWorK::PG::Environment; + require WeBWorK::PG; + require PGcore; + require Parser; + + $WeBWorK::PG::IO::pg_envir = WeBWorK::PG::Environment->new; + %main::envir = %{ WeBWorK::PG::defineProblemEnvironment($WeBWorK::PG::IO::pg_envir) }; + + do "$ENV{PG_ROOT}/macros/PG.pl"; + + $main::PG = $main::PG = PGcore->new(\%main::envir); + loadMacros('PGML.pl'); + + $PGML::warningsFatal = $PGML::warningsFatal = 1; + my $parser = eval { PGML::Parse->new($source =~ s/\\\\/\\/gr) }; + return { errors => [$@] } if $@; + + return $processedBlocks{$sourceHash} = WeBWorK::PG::Critic::Utils::walkPGMLTree($parser->{root}); +} + +# For now, only contents of \{ .. \} blocks are returned. Add other text elements as needed. +sub parseTextBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular text blocks so that if multiple policies + # use the same text block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + my $results = {}; + + while ($source ne '') { + if ($source =~ /\Q\\{\E/s) { + $source =~ s/^(.*?)\Q\\{\E//s; + $source =~ s/^(.*?)\Q\\}\E//s; + my $command = PPI::Document->new(\($1)); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } else { + last; + } + + } + + return $processedBlocks{$sourceHash} = $results; +} +1; diff --git a/lib/WeBWorK/PG/PGProblemCritic.pm b/lib/WeBWorK/PG/PGProblemCritic.pm deleted file mode 100644 index f637d36f60..0000000000 --- a/lib/WeBWorK/PG/PGProblemCritic.pm +++ /dev/null @@ -1,319 +0,0 @@ - -=head1 NAME - -PGProblemCritic - Parse a PG program and analyze the contents for positive and negative features. - -=head1 DESCRIPTION - -Analyze a pg file for use of old and current methods. - -=over - -=item * C: a list of the macros that the problem uses that is in the C -folder. - -=item * Positive features: - -=over - -=item * Uses PGML - -=item * Provides a solution - -=item * Provides a hint - -=item * Uses Scaffolds - -=item * Uses a custom checker - -=item * Uses a multianswer - -=item * Uses answer hints - -=item * Uses nicetables - -=item * Uses randomness - -=back - -=item Old and deprecated features - -=over - -=item * Use of BEGIN_TEXT/END_TEXT - -=item * Include the C - -=item * Include old tables (for example from C) - -=item * The use of C, C and C in lieu of using MathObjects - -=item * Including C<< Context()->TeXStrings >> - -=item * Calling C more than once. - -=item * Using the line C< $showPartialCorrectAnswers = 1 > which is the default behavior and thus unnecessary. - -=item * Using methods from C - -=item * Inlcuding code or other text below the C line indicating the end of the problem. - -=back - -=back - - -=cut - -package WeBWorK::PG::PGProblemCritic; -use parent qw(Exporter); - -use strict; -use warnings; -use experimental 'signatures'; -use feature 'say'; - -use Mojo::File qw(curfile); -use Mojo::Util qw(dumper); - -our @EXPORT_OK = qw(analyzePGfile analyzePGcode getDeprecatedMacros); - -sub analyzePGcode ($code) { - # default flags for presence of features in a PG problem - my $features = { - metadata => { DBsubject => 0, DBchapter => 0, DBsection => 0, KEYWORDS => 0 }, - positive => { - PGML => 0, - solution => 0, - hint => 0, - custom_checker => 0, - multianswer => 0, - nicetables => 0, - randomness => 0, - contexts => { BaseN => 0, Units => 0, Boolean => 0, Reaction => 0 }, - parsers => - { dropdown => 0, RadioButtons => 0, CheckboxList => 0, RadioMultianswer => 0, GraphTool => 0 }, - macros => { - randomPerson => 0, - Plots => 0, - PGtikz => 0, - Plotly3D => 0, - PGlateximage => 0, - Scaffold => 0, - AnswerHints => 0, - } - }, - negative => { - BEGIN_TEXT => 0, - beginproblem => 0, - oldtable => 0, - num_cmp => 0, - str_cmp => 0, - fun_cmp => 0, - context_texstrings => 0, - multiple_loadmacros => 0, - showPartialCorrect => 0, - lines_below_enddocument => 0, - macros => { PGgraphmacros => 0, PGchoicemacros => 0 } - }, - deprecated_macros => [], - macros => [] - }; - - # Get a list of all deprecated macros. - my $all_deprecated_macros = getDeprecatedMacros(curfile->dirname->dirname->dirname->dirname); - - # determine if the loadMacros has been parsed. - my $loadmacros_parsed = 0; - - my @pglines = split /\n/, $code; - my $line = ''; - while (1) { - $line = shift @pglines; - # print Dumper $line; - last unless defined($line); # end of the file. - next if $line =~ /^\s*$/; # skip any blank lines. - - # Determine if some of the metadata tags are present. - for (qw(DBsubject DBchapter DBsection KEYWORDS)) { - $features->{metadata}{$_} = 1 if $line =~ /$_\(/i; - } - - # Skip any full-line comments. - next if $line =~ /^\s*#/; - - $features->{positive}{solution} = 1 if $line =~ /BEGIN_(PGML_)?SOLUTION/; - $features->{positive}{hint} = 1 if $line =~ /BEGIN_(PGML_)?HINT/; - - # Analyze the loadMacros info. - if ($line =~ /loadMacros\(/) { - $features->{negative}{multiple_loadmacros} = 1 if $loadmacros_parsed == 1; - $loadmacros_parsed = 1; - # Parse the macros, which may be on multiple rows. - my $macros = $line; - while ($line && $line !~ /\);\s*$/) { - $line = shift @pglines; - - # Strip any comments at the end of lines. - $line =~ s/(.*)#.*/$1/; - $macros .= $line; - } - - $macros =~ s/^\s*loadMacros\(\s*(.*)\s*\);\s*$/$1/; - my @macros; - # if the arguments of loadMacros is q[qw] form, handle this. - if ($macros =~ /^q[qw]?[\(\[\{\/](.*)[\)\]\/\}]$/) { - $macros =~ s/^q[qw]?[\(\[\{\/](.*)[\)\]\/\}]$/$1/; - @macros = grep { $_ ne '' } split(/\s+/, $macros); - } else { # arguments are strings separated by commas. - @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); - } - - $features->{macros} = \@macros; - for my $macro (@macros) { - push(@{ $features->{deprecated_macros} }, $macro) if (grep { $macro eq $_ } @$all_deprecated_macros); - } - } elsif ($line =~ /BEGIN_PGML(_SOLUTION|_HINT)?/) { - $features->{positive}{PGML} = 1; - my @pgml_lines; - while (1) { - $line = shift @pglines; - last if $line =~ /END_PGML(_SOLUTON|_HINT)?/; - push(@pgml_lines, $line); - } - - my $pgml_features = analyzePGMLBlock(@pgml_lines); - $features->{negative}{missing_alt_tag} = 1 if $pgml_features->{missing_alt_tag}; - } - - if ($line =~ /ENDDOCUMENT/) { # scan if there are any lines below the ENDDOCUMENT - - do { - $line = shift @pglines; - last unless defined($line); - $features->{negative}{lines_below_enddocument} = 1 if $line !~ /^\s*$/; - } while (defined($line)); - } - - # Check for negative features. - $features->{negative}{beginproblem} = 1 if $line =~ /beginproblem\(\)/; - $features->{negative}{BEGIN_TEXT} = 1 if $line =~ /(BEGIN_(TEXT|HINT|SOLUTION))|EV[23]/; - $features->{negative}{context_texstrings} = 1 if $line =~ /->(texStrings|normalStrings)/; - for (qw(num str fun)) { - $features->{negative}{ $_ . '_cmp' } = 1 if $line =~ /${_}_cmp\(/; - } - $features->{negative}{oldtable} = 1 if $line =~ /BeginTable/i; - $features->{negative}{showPartialCorrect} = 1 if $line =~ /\$showPartialCorrectAnswers\s=\s1/; - $features->{negative}{macros}{PGgraphmacros} = 1 if $line =~ /init_graph\(/; - $features->{negative}{macros}{PGchoicemacros} = 1 - if $line =~ /new_checkbox_multiple_choice/ - || $line =~ /new_match_list/ - || $line =~ /new_select_list/ - || $line =~ /new_multiple_choice/ - || $line =~ /qa\s\(/; - - # check for positive features - # macros: - $features->{positive}{macros}{Scaffold} = 1 if $line =~ /Scaffold::Begin/; - $features->{positive}{macros}{Plots} = 1 if $line =~ /Plot\(/; - $features->{positive}{macros}{Plotly3D} = 1 if $line =~ /Graph3D\(/; - $features->{positive}{macros}{PGtikz} = 1 if $line =~ /createTikZImage\(/; - $features->{positive}{macros}{AnswerHints} = 1 if $line =~ /AnswerHints/; - $features->{positive}{macros}{randomPerson} = 1 if $line =~ /randomPerson\(/ || $line =~ /randomLastName\(/; - $features->{positive}{macros}{PGlateximage} = 1 if $line =~ /createLaTeXImage\(/; - - # contexts: - - $features->{positive}{contexts}{Units} = 1 if $line =~ /Context\(['"]Units['"]\)/; - $features->{positive}{contexts}{BaseN} = 1 if $line =~ /Context\(['"](Limited)?BaseN['"]\)/; - $features->{positive}{contexts}{Boolean} = 1 if $line =~ /Context\(['"]Boolean['"]\)/; - $features->{positive}{contexts}{Reaction} = 1 if $line =~ /Context\(['"]Reaction['"]\)/; - - # parsers: - $features->{positive}{parsers}{PopUp} = 1 if $line =~ /DropDown\(/; - $features->{positive}{parsers}{RadioButtons} = 1 if $line =~ /RadioButtons\(/; - $features->{positive}{parsers}{CheckboxList} = 1 if $line =~ /CheckboxList\(/; - $features->{positive}{parsers}{GraphTool} = 1 if $line =~ /GraphTool\(/; - - # other: - $features->{positive}{multianswer} = 1 if $line =~ /MultiAnswer/; - $features->{positive}{custom_checker} = 1 if $line =~ /checker\s*=>/; - $features->{positive}{nicetables} = 1 if $line =~ /DataTable|LayoutTable/; - $features->{positive}{randomness} = 1 if $line =~ /random\(|random_(\w+)\(|list_random\(/; - - } - return $features; -} - -# Return a list of the macro filenames in the 'macros/deprecated' directory. -sub getDeprecatedMacros ($pgroot) { - return Mojo::File->new($pgroot)->child('macros/deprecated')->list->map(sub { $_->basename })->to_array; -} - -sub analyzePGfile ($file) { - my $path = Mojo::File->new($file); - die "The file: $file does not exist or is not readable" unless -r $path; - - return analyzePGcode($path->slurp); -} - -# Parse a string that is a function in the form of "funct($arg1, $arg2, ..., param1 => val1, param2 => val2 , ...)" -# A hashref of the form {_args = [$arg1, $arg2, ...], param1 => val1, param2 => val2} is returned. - -sub parseFunctionString($string) { - - my ($funct, $args); - if ($string =~ /(\w+)\(\s*(.*)\)/) { - ($funct, $args) = ($1, $2); - } else { - return (); - } - - my @args = split(/,\s/, $args); - - my %params = (_name => $funct, _args => []); - for (@args) { - if ($_ !~ /=>/) { - push(@{ $params{_args} }, $_); - } else { - if ($_ =~ /(\w+)\s*=>\s*["']?([^"]*)["']?/) { - my ($key, $value) = ($1, $2); - $params{$key} = $value; - } - } - } - return %params; -} - -# Perform some analysis of a PGML block. - -sub analyzePGMLBlock(@lines) { - my $pgml_features = {}; - - while (1) { - my $line = shift @lines; - last unless defined($line); - - # If there is a perl block analyze [@ @] - if ($line =~ /\[@/) { - my $perl_line = $line; - while ($perl_line !~ /@\]/) { - $line = shift @lines; - $perl_line .= $line; - } - my ($perlcode) = $perl_line =~ /\[@\s*(.*)\s*@\]/; - - my %funct_info = parseFunctionString($perlcode); - if (%funct_info && $funct_info{_name} =~ /image/) { - if (defined($funct_info{extra_html_tags}) && $funct_info{extra_html_tags} !~ /alt/) { - $pgml_features->{missing_alt_tag} = 1; - } - } - - } elsif (my ($alt_text) = $line =~ /\[!(.*)!\]/) { - $pgml_features->{missing_alt_tag} = 1 if $alt_text =~ /^\s$/; - } - - } - return $pgml_features; -} From 675b24e949f9248f8200071f0a6b56fd7ab74f04 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 16 Jul 2025 06:29:12 -0500 Subject: [PATCH 069/111] Remove "positive violations" and invert score. Now all violations are really violations. There is no need for the "positive violations". The score is now purely a badness score. The higher the score, the more work is needed to fix the problem. A score of 0 means the problem has no issues. The "positive violations" were really going to be a problem if the intent of the score is to obtain a measure of how much work is needed to fix a problem and make it conform to current best-practices in problem authoring. For example, a problem could do quite a bit wrong but have a custom checker. The custom checker score was really high, and so that would offset the things done wrong and it might end up with a score that is the same as for a problem that only does a few things wrong, but doesn't have a custom checker. So that first problem with the custom checker really needs a lot of work, but the second problem without the checker only needs trivial fixes. By only having a badness score you get a much clearer measure of how much is needed to update a problem to conform with current best-practices. --- bin/pg-critic.pl | 44 +++----- .../Policy/PG/EncourageCustomCheckers.pm | 43 -------- .../Policy/PG/EncourageModernContextUsage.pm | 65 ------------ .../Critic/Policy/PG/EncouragePGMLUsage.pm | 57 ---------- .../Policy/PG/EncourageQualityMacroUsage.pm | 100 ------------------ .../Policy/PG/EncourageSolutionsAndHints.pm | 61 ----------- .../Critic/Policy/PG/ProhibitBeginproblem.pm | 2 +- .../Policy/PG/ProhibitContextStrings.pm | 2 +- .../Critic/Policy/PG/ProhibitDeprecatedCmp.pm | 2 +- .../Policy/PG/ProhibitDeprecatedMacros.pm | 2 +- .../PG/ProhibitDeprecatedMultipleChoice.pm | 2 +- .../Policy/PG/ProhibitEnddocumentMatter.pm | 2 +- .../Critic/Policy/PG/ProhibitGraphMacros.pm | 2 +- .../PG/ProhibitMultipleLoadMacrosCalls.pm | 2 +- lib/Perl/Critic/Policy/PG/ProhibitOldText.pm | 2 +- ...ssarilySettingShowPartialCorrectAnswers.pm | 2 +- .../Policy/PG/RequireImageAltAttribute.pm | 2 +- lib/Perl/Critic/Policy/PG/RequireMetadata.pm | 2 +- lib/Perl/Critic/Policy/PG/RequireSolution.pm | 2 +- lib/WeBWorK/PG/Critic.pm | 34 +++--- 20 files changed, 46 insertions(+), 384 deletions(-) delete mode 100644 lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm delete mode 100644 lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm delete mode 100644 lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm delete mode 100644 lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm delete mode 100644 lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl index bc4f094225..6e8ab7c4cd 100755 --- a/bin/pg-critic.pl +++ b/bin/pg-critic.pl @@ -16,8 +16,8 @@ =head1 SYNOPSIS JavaScript Object Notation. -o|--output-file Filename to write output to. If not provided output will be printed to STDOUT. - -n|--no-details Only show the filename and score and do not include the - details in the output for each file. + -n|--no-details Only show the filename and badness score and do not + include the details in the output for each file. -s|--strict Disable "## no critic" annotations and force all policies to be enforced. -h|--help Show the help message. @@ -25,9 +25,9 @@ =head1 SYNOPSIS =head1 DESCRIPTION C is a PG problem source code analyzer. It is the executable -front-end to the L module, which attempts to identify -usage of old or deprecated PG features, as well as usage of newer features and -current best practices in coding a problem. +front-end to the L module, which attempts to identify usage +of old or deprecated PG features and code that does not conform to current +best-practices. =cut @@ -70,9 +70,9 @@ (@violations) if ($_->policy =~ /^Perl::Critic::Policy::PG::/) { $score += $_->explanation->{score} // 0; } else { - # Deduct 5 points for any of the default Perl::Critic::Policy violations. + # Add 5 points for any of the default Perl::Critic::Policy violations. # These will not have a score in the explanation. - $score -= 5; + $score += 5; } } return $score; @@ -83,13 +83,10 @@ (@violations) for (@ARGV) { my @violations = critiquePGFile($_, $force); - my (@positivePGResults, @negativePGResults, @perlCriticResults); + my (@pgCriticViolations, @perlCriticViolations); if (!$noDetails) { - @positivePGResults = - grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ && $_->explanation->{score} > 0 } @violations; - @negativePGResults = - grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ && $_->explanation->{score} < 0 } @violations; - @perlCriticResults = grep { $_->policy !~ /^Perl::Critic::Policy::PG::/ } @violations; + @pgCriticViolations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations; + @perlCriticViolations = grep { $_->policy !~ /^Perl::Critic::Policy::PG::/ } @violations; } push( @@ -99,11 +96,7 @@ (@violations) score => scoreProblem(@violations), $noDetails ? () - : ( - positivePGResults => \@positivePGResults, - negativePGResults => \@negativePGResults, - perlCriticResults => \@perlCriticResults - ) + : (pgCriticViolations => \@pgCriticViolations, perlCriticViolations => \@perlCriticViolations) } ); } @@ -116,16 +109,13 @@ (@violations) return join( "\n", map { ( - "filename: $_->{file}", - "score: $_->{score}", - @{ $_->{positivePGResults} // [] } - ? ('positive pg critic results:', map { "\t" . $_->to_string } @{ $_->{positivePGResults} }) + "Filename: $_->{file}", + "Score: $_->{score}", + @{ $_->{pgCriticViolations} // [] } + ? ('PG critic violations:', map { "\t" . $_->to_string } @{ $_->{pgCriticViolations} }) : (), - @{ $_->{negativePGResults} // [] } - ? ('negative pg critic results:', map { "\t" . $_->to_string } @{ $_->{negativePGResults} }) - : (), - @{ $_->{perlCriticResults} // [] } - ? ('perl critic results:', map { "\t" . $_->to_string } @{ $_->{perlCriticResults} }) + @{ $_->{perlCriticViolations} // [] } + ? ('Perl critic violations:', map { "\t" . $_->to_string } @{ $_->{perlCriticViolations} }) : () ) } @$results ); diff --git a/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm b/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm deleted file mode 100644 index 45e50f43fc..0000000000 --- a/lib/Perl/Critic/Policy/PG/EncourageCustomCheckers.pm +++ /dev/null @@ -1,43 +0,0 @@ -package Perl::Critic::Policy::PG::EncourageCustomCheckers; -use Mojo::Base 'Perl::Critic::Policy', -signatures; - -use Perl::Critic::Utils qw(:severities :classification :ppi); - -use constant DESCRIPTION => 'A custom checker is utilized'; -use constant EXPLANATION => 'Custom checkers demonstrate a high level of sophistication in problem coding.'; -use constant SCORE => 50; - -sub supported_parameters ($) {return} -sub default_severity ($) { return $SEVERITY_HIGHEST } -sub default_themes ($) { return qw(pg) } -sub applies_to ($) { return qw(PPI::Token::Word) } - -use Mojo::Util qw(dumper); - -# FIXME: This misses some important cases. For example, answer checking can also be performed in a post filter. In -# fact that demonstrates an even higher level of sophistication than using a checker in some senses. It is more -# complicated to use correctly, and can work around type limitations imposed on MathObject checkers. However, there is -# no reliable way to determine what a post filter is in a problem for, as there are other reasons to add a post filter. -sub violates ($self, $element, $document) { - return unless $element eq 'checker' || $element eq 'list_checker'; - return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element); -} - -1; - -__END__ - -=head1 NAME - -Perl::Critic::Policy::PG::EncourageCustomCheckers - Custom checkers demonstrate -a high level of sophistication in problem coding. - -=head1 DESCRIPTION - -Utilization of a custom checker in a problem demonstrates a high level of -sophistication in coding a problem. Custom checkers can be used to supplement -default MathObject checkers in several ways. For example, to award partial -credit and display more meaningful messages for answers that are not entirely -correct - -=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm b/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm deleted file mode 100644 index f9010cfdd1..0000000000 --- a/lib/Perl/Critic/Policy/PG/EncourageModernContextUsage.pm +++ /dev/null @@ -1,65 +0,0 @@ -package Perl::Critic::Policy::PG::EncourageModernContextUsage; -use Mojo::Base 'Perl::Critic::Policy', -signatures; - -# FIXME: Is this policy really a good idea? Why are these contexts so special? Just because they are newer? Many of the -# contexts that have been around for a long time are actually better than some of these, and some of them are more -# complicated to use and demonstrate a higher level of sophistication than these. - -use Perl::Critic::Utils qw(:severities :classification :ppi); - -use constant DESCRIPTION => 'The context %s is used from the macro %s'; -use constant EXPLANATION => '%s is a modern context whose usage demonstrates currency in problem authoring.'; - -use constant CONTEXTS => { - BaseN => { macro => 'contextBaseN.pl', score => 10 }, - Boolean => { macro => 'contextBoolean.pl', score => 10 }, - Reaction => { macro => 'contextReaction.pl', score => 10 }, - Units => { macro => 'contextUnits.pl', score => 10 } -}; - -sub supported_parameters ($) {return} -sub default_severity ($) { return $SEVERITY_HIGHEST } -sub default_themes ($) { return qw(pg) } -sub applies_to ($) { return qw(PPI::Token::Word) } - -sub violates ($self, $element, $document) { - return unless $element eq 'Context' && is_function_call($element); - my $context = first_arg($element); - return $self->violation( - sprintf(DESCRIPTION, $context->string, CONTEXTS->{ $context->string }{macro}), - { - score => CONTEXTS->{ $context->string }{score}, - explanation => sprintf(EXPLANATION, CONTEXTS->{ $context->string }{macro}) - }, - $context - ) if $context && CONTEXTS->{ $context->string }; - return; -} - -1; - -__END__ - -=head1 NAME - -Perl::Critic::Policy::PG::EncourageModernContextUsage - Usage of recently -created contexts demonstrates currency in problem authoring. - -=head1 DESCRIPTION - -Usage of recently created contexts demonstrates currency in problem authoring. -Currently this policy encourages the use of the following contexts: - -=over - -=item * L - -=item * L - -=item * L - -=item * L - -=back - -=cut diff --git a/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm b/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm deleted file mode 100644 index fd67cc9de8..0000000000 --- a/lib/Perl/Critic/Policy/PG/EncouragePGMLUsage.pm +++ /dev/null @@ -1,57 +0,0 @@ -package Perl::Critic::Policy::PG::EncouragePGMLUsage; -use Mojo::Base 'Perl::Critic::Policy', -signatures; - -use Perl::Critic::Utils qw(:severities :classification :ppi); - -use constant DESCRIPTION => 'PGML is used for problem text'; -use constant EXPLANATION => 'PGML should be used for problem text.'; -use constant SCORE => 20; - -sub supported_parameters ($) {return} -sub default_severity ($) { return $SEVERITY_HIGHEST } -sub default_themes ($) { return qw(pg) } -sub applies_to ($) { return qw(PPI::Token::HereDoc) } - -# Only report this once even if there are multiple PGML blocks in the problem. -sub default_maximum_violations_per_document ($) { return 1; } - -sub violates ($self, $element, $document) { - return $self->violation( - DESCRIPTION, - { score => SCORE, explanation => EXPLANATION }, - $element->parent->parent->parent->parent->parent - ) - if $element->terminator =~ /^END_PGML(_SOLUTION|_HINT)?$/ - && $element->parent - && $element->parent->parent - && $element->parent->parent->parent - && $element->parent->parent->parent->first_element eq 'PGML::Format2' - && is_function_call($element->parent->parent->parent->first_element) - && $element->parent->parent->parent->parent - && $element->parent->parent->parent->parent->parent - && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ - && is_function_call($element->parent->parent->parent->parent->parent->first_element); - return; -} - -1; - -__END__ - -=head1 NAME - -Perl::Critic::Policy::PG::EncouragePGMLUsages - All problems should use PGML to -insert problem text. - -=head1 DESCRIPTION - -All problems should use PGML via C/C, -C/C, or -C/C blocks to insert problem text, -instead of the older C/C, C/C, or -C/C blocks. The PGML syntax is much easier to read -for other problem authors looking at the code, and PGML helps to ensure that -many text elements (for example images and tables) are inserted correctly for -recent requirements for accessibility. - -=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm b/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm deleted file mode 100644 index 79b1cb878f..0000000000 --- a/lib/Perl/Critic/Policy/PG/EncourageQualityMacroUsage.pm +++ /dev/null @@ -1,100 +0,0 @@ -package Perl::Critic::Policy::PG::EncourageQualityMacroUsage; -use Mojo::Base 'Perl::Critic::Policy', -signatures; - -use Perl::Critic::Utils qw(:severities :classification :ppi); - -use constant DESCRIPTION => '%s is used from the macro %s'; -use constant EXPLANATION => '%s is a high quality macro whose usage is encouraged.'; - -# FIXME: A better explanation is needed. Perhaps instead of a single explanation for all macros, add an explanation key -# to each of the methods below and give an explanation specific to the method and macro used. - -use constant METHODS => { - AnswerHints => { macro => 'answerHints.pl', score => 10 }, - CheckboxList => { macro => 'parserCheckboxList.pl', score => 10 }, - createLaTeXImage => { macro => 'PGlateximage.pl', score => 10 }, - createTikZImage => { macro => 'PGtikz.pl', score => 10 }, - DataTable => { macro => 'niceTables.pl', score => 10 }, - DraggableProof => { macro => 'draggableProof.pl', score => 10 }, - DraggableSubset => { macro => 'draggableSubset.pl', score => 10 }, - DropDown => { macro => 'parserPopUp.pl', score => 10 }, - Graph3D => { macro => 'plotly3D.pl', score => 10 }, - GraphTool => { marco => 'parserGraphTool.pl', score => 10 }, - LayoutTable => { macro => 'niceTables.pl', score => 10 }, - MultiAnswer => { macro => 'parserMultiAnswer.pl', score => 30 }, - Plots => { macro => 'plots.pl', score => 10 }, - RadioButtons => { macro => 'parserRadioButtons.pl', score => 10 }, - RadioMultiAnswer => { macro => 'parserRadioMultiAnswer.pl', score => 30 }, - randomLastName => { macro => 'randomPerson.pl', score => 10 }, - randomPerson => { macro => 'randomPerson.pl', score => 10 }, - 'Scaffold::Begin' => { macro => 'scaffold.pl', score => 20 } -}; - -sub supported_parameters ($) {return} -sub default_severity ($) { return $SEVERITY_HIGHEST } -sub default_themes ($) { return qw(pg) } -sub applies_to ($) { return qw(PPI::Token::Word) } - -sub violates ($self, $element, $document) { - return unless METHODS->{$element} && is_function_call($element); - return $self->violation(sprintf(DESCRIPTION, $element, METHODS->{$element}{macro}), - { score => METHODS->{$element}{score}, explanation => sprintf(EXPLANATION, METHODS->{$element}{macro}) }, - $element); -} - -1; - -__END__ - -=head1 NAME - -Perl::Critic::Policy::PG::EncourageQualityMacroUsage - Usage of macros that are -well maintained and provide advanced MathObject answers is encouraged. - -=head1 DESCRIPTION - -Usage of macros that are well maintained and provide advanced MathObject answers -is encouraged. This policy currently recognizes the usage of the following -macros: - -=over - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=item * L - -=back - -=cut diff --git a/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm b/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm deleted file mode 100644 index abe6944869..0000000000 --- a/lib/Perl/Critic/Policy/PG/EncourageSolutionsAndHints.pm +++ /dev/null @@ -1,61 +0,0 @@ -package Perl::Critic::Policy::PG::EncourageSolutionsAndHints; -use Mojo::Base 'Perl::Critic::Policy', -signatures; - -use Perl::Critic::Utils qw(:severities :classification :ppi); - -use constant DESCRIPTION => 'A %s is included'; -use constant EXPLANATION => { - solution => 'A solution should be added to all problems.', - hint => 'A hint is helpful for students.' -}; -use constant SCORE => { solution => 15, hint => 10 }; - -sub supported_parameters ($) {return} -sub default_severity ($) { return $SEVERITY_HIGHEST } -sub default_themes ($) { return qw(pg) } -sub applies_to ($) { return qw(PPI::Token::HereDoc) } - -sub violates ($self, $element, $) { - if ( - $element->terminator =~ /^END_(PGML_)?(SOLUTION|HINT)/ - && $element->parent - && $element->parent->parent - && $element->parent->parent->parent - && ($element->parent->parent->parent->first_element eq 'PGML::Format2' - || $element->parent->parent->parent->first_element eq 'EV3P') - && is_function_call($element->parent->parent->parent->first_element) - && $element->parent->parent->parent->parent - && $element->parent->parent->parent->parent->parent - && $element->parent->parent->parent->parent->parent->first_element =~ /^(HINT|SOLUTION)$/ - && is_function_call($element->parent->parent->parent->parent->parent->first_element) - ) - { - my $type = lc($1); - return $self->violation( - sprintf(DESCRIPTION, $type), - { score => SCORE->{$type}, explanation => EXPLANATION->{$type} }, - $element->parent->parent->parent->parent->parent - ); - } - return; -} - -1; - -__END__ - -=head1 NAME - -Perl::Critic::Policy::PG::EncourageSolutionsAndHints - Solutions should be -provided in all problems, and hints are helpful for students. - -=head1 DESCRIPTION - -All problems should provide solutions that demonstrate how to work the problem, -and which do not just give the answers to the problem. - -Hints are helpful for students that are struggling with the concepts presented -in the problem, and it is recommended that hints be added particularly for more -difficult problems. - -=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm index 2176f84227..ee2e300b96 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => 'The beingproblem function is called'; use constant EXPLANATION => 'The beingproblem function no longer does anything and should be removed.'; -use constant SCORE => -5; +use constant SCORE => 5; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm index 4487c616e4..309ee1a832 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => 'Context()->%s is called'; use constant EXPLANATION => 'Context()->%s no longer necessary and should be removed.'; -use constant SCORE => -5; +use constant SCORE => 5; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm index 21ec89d42f..4af90b468b 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm @@ -5,7 +5,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'The deprecated %s method is called'; use constant EXPLANATION => 'Convert the answer into a MathObject and use the cmp method of the object.'; -use constant SCORE => -55; +use constant SCORE => 55; use constant CMP_METHODS => { str_cmp => 1, diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm index 38e051bb30..27bbd26512 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => 'The deprecated macro %s is loaded'; use constant EXPLANATION => 'Remove this macro and replace methods used from this macro with modern alternatives.'; -use constant SCORE => -10; +use constant SCORE => 10; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm index d7667be8e9..bc38344461 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => 'The deprecated %s function is called'; use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; -use constant SCORE => -20; +use constant SCORE => 20; use constant SAMPLE_PROBLEMS => [ [ 'Multiple Choice with Checkbox' => 'Misc/MultipleChoiceCheckbox' ], [ 'Multiple Choice with Popup' => 'Misc/MultipleChoicePopup' ], diff --git a/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm index 9f240628f7..0267989b31 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm @@ -5,7 +5,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'There is content after the ENDDOCUMENT call'; use constant EXPLANATION => 'Remove this content. The ENDDOCUMENT call should be at the end of the problem.'; -use constant SCORE => -5; +use constant SCORE => 5; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm index 734fd74eff..437951b0d0 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => 'The init_graph function from PGgraphmacros.pl is called'; use constant EXPLANATION => 'PGgraphmacros.pl generates poor quality graphics. Consider using a modern alternative.'; -use constant SCORE => -20; +use constant SCORE => 20; use constant SAMPLE_PROBLEMS => [ [ 'TikZ Graph Images' => 'ProblemTechniques/TikZImages' ], [ 'Inserting Images in PGML' => 'ProblemTechniques/Images' ], diff --git a/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm index b680bbf551..6dd18ca2e2 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm @@ -5,7 +5,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'loadMacros is called multiple times'; use constant EXPLANATION => 'Consolidate multiple loadMacros calls into a single call.'; -use constant SCORE => -20; +use constant SCORE => 20; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm index ca37c30988..b46ccbe8e1 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm @@ -6,7 +6,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'A BEGIN_%1$s/END_%1$s block is used for problem text'; use constant EXPLANATION => 'Load the macro PGML.pl and replace the BEGIN_%1$s/END_%1$s ' . 'block with a BEGIN_PGML%2$s/END_PGML%2$s block.'; -use constant SCORE => -10; +use constant SCORE => 20; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm index 51252dc6fb..2faac1d9ff 100644 --- a/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm +++ b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm @@ -8,7 +8,7 @@ use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); use constant DESCRIPTION => '$showPartialCorrectAnswers is set to 1'; use constant EXPLANATION => 'The value of $showPartialCorrectAnswers is 1 by default, ' . 'so it should only ever be set to 0 to change the value.'; -use constant SCORE => -5; +use constant SCORE => 5; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm index e538b409a4..7fbc026577 100644 --- a/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm +++ b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm @@ -7,7 +7,7 @@ use WeBWorK::PG::Critic::Utils qw(parsePGMLBlock parseTextBlock); use constant DESCRIPTION => 'An image is missing the alt attribute'; use constant EXPLANATION => 'Add an alt attribute that describes the image content.'; -use constant SCORE => -10; +use constant SCORE => 10; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/Perl/Critic/Policy/PG/RequireMetadata.pm b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm index 799f859e6c..ab081f9dfa 100644 --- a/lib/Perl/Critic/Policy/PG/RequireMetadata.pm +++ b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm @@ -5,7 +5,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'The %s metadata tag is required'; use constant EXPLANATION => 'Include the required metadata tags at the beginning of the problem file.'; -use constant SCORE => -5; +use constant SCORE => 5; use constant REQUIRED_METADATA => [ 'DBsubject', 'DBchapter', 'DBsection', 'KEYWORDS' ]; sub supported_parameters ($) {return} diff --git a/lib/Perl/Critic/Policy/PG/RequireSolution.pm b/lib/Perl/Critic/Policy/PG/RequireSolution.pm index 937be8bf10..eb14e1a647 100644 --- a/lib/Perl/Critic/Policy/PG/RequireSolution.pm +++ b/lib/Perl/Critic/Policy/PG/RequireSolution.pm @@ -5,7 +5,7 @@ use Perl::Critic::Utils qw(:severities :classification :ppi); use constant DESCRIPTION => 'A solution is not included in this problem'; use constant EXPLANATION => 'A solution should be included in all problems.'; -use constant SCORE => -15; +use constant SCORE => 25; sub supported_parameters ($) {return} sub default_severity ($) { return $SEVERITY_HIGHEST } diff --git a/lib/WeBWorK/PG/Critic.pm b/lib/WeBWorK/PG/Critic.pm index a235fc5412..35742f7d7e 100644 --- a/lib/WeBWorK/PG/Critic.pm +++ b/lib/WeBWorK/PG/Critic.pm @@ -11,24 +11,22 @@ Analyze a pg file for use of old and current methods. =head2 critiquePGCode - my $results = critiquePGCode($code, $force = 0); + my @violations = critiquePGCode($code, $force = 0); Parses and critiques the given PG problem source provided in C<$code>. An array -of "violations" that are found is returned. Note that the elements of this -return array are L objects. However, not all of these -"violations" are bad. Some are actually noting good things that are used in the -source code for the problem. The C method can be called for each -element, and that will either return a string or a reference to a hash. The -string return type will occur for a violation of a default L -policy. The last return type will occur with a C -policy, and the hash will contain a C key and an C key -containing the actual explanation. If the C is positive, then it is not -actually a violation, but something good. In some cases the C -return hash will also contain the key C which will be a -reference to an array each of whose entries will be a reference to a two element -array whose first element is the title of a sample problem and whose second -element is the path for that sample problem where the sample problem -demonstrates a way to fix the policy violation. +of violations that are found is returned. Note that the elements of this return +array are L objects. The C method can be +called for each element, and that will either return a string or a reference to +a hash. The string return type will occur for a violation of a default +L policy. The last return type will occur with a +C policy, and the hash will contain a C key and +an C key containing the actual explanation. Note that the greater +the score, the worse the violation is. In some cases the C return +hash will also contain the key C which will be a reference to an +array each of whose entries will be a reference to a two element array whose +first element is the title of a sample problem and whose second element is the +path for that sample problem where the sample problem demonstrates a way to fix +the policy violation. Note that C<## no critic> annotations can be used in the code to disable a violation for a line or the entire file. See L<"BENDING THE @@ -38,10 +36,10 @@ policies are enforced regardless. =head2 critiquePGFile - my $results = critiquePGFile($file, $force); + my @violations = critiquePGFile($file, $force); This just executes C on the contents of C<$file> and returns -the result. +the violations found. =cut package WeBWorK::PG::Critic; From 184b4679664308805162144007dc0096d92cc2c0 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 16 Jul 2025 07:16:52 -0500 Subject: [PATCH 070/111] Add a "--pg-only" ("-p" for short) option to the `bin/pg-critic.pl` script. If this option is set, only PG critic policy violations (those in the `Perl::Critic::Policy::PG` namespace) are shown and scored, and general Perl critic policies are ignored. --- bin/pg-critic.pl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl index 6e8ab7c4cd..2c4431d952 100755 --- a/bin/pg-critic.pl +++ b/bin/pg-critic.pl @@ -20,6 +20,9 @@ =head1 SYNOPSIS include the details in the output for each file. -s|--strict Disable "## no critic" annotations and force all policies to be enforced. + -p|--pg-only Only include PG critic policy violations and ignore + general Perl critic policy violations (both for the + score and display). -h|--help Show the help message. =head1 DESCRIPTION @@ -47,6 +50,7 @@ =head1 DESCRIPTION 'o|output-file=s' => \my $filename, 'n|no-details' => \my $noDetails, 's|strict' => \my $force, + 'p|pg-only' => \my $pgOnly, 'h|help' => \my $show_help ); pod2usage(2) if $show_help; @@ -82,6 +86,7 @@ (@violations) for (@ARGV) { my @violations = critiquePGFile($_, $force); + @violations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations if $pgOnly; my (@pgCriticViolations, @perlCriticViolations); if (!$noDetails) { @@ -96,7 +101,10 @@ (@violations) score => scoreProblem(@violations), $noDetails ? () - : (pgCriticViolations => \@pgCriticViolations, perlCriticViolations => \@perlCriticViolations) + : ( + @pgCriticViolations ? (pgCriticViolations => \@pgCriticViolations) : (), + @perlCriticViolations ? (perlCriticViolations => \@perlCriticViolations) : () + ) } ); } From 1d6366cb9e4592f63fd66ee739f7fcf3a0770710 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 16 Jul 2025 07:49:14 -0500 Subject: [PATCH 071/111] Switch to a better approach to using `PGML::Parse`. This is much more minimal. --- lib/WeBWorK/PG/Critic.pm | 2 ++ lib/WeBWorK/PG/Critic/Utils.pm | 24 +++++++----------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/WeBWorK/PG/Critic.pm b/lib/WeBWorK/PG/Critic.pm index 35742f7d7e..0a656d494c 100644 --- a/lib/WeBWorK/PG/Critic.pm +++ b/lib/WeBWorK/PG/Critic.pm @@ -49,6 +49,8 @@ use Mojo::File qw(path); use PPI; use Perl::Critic; +require WeBWorK::PG::Translator; + our @EXPORT_OK = qw(critiquePGFile critiquePGCode); sub critiquePGCode ($code, $force = 0) { diff --git a/lib/WeBWorK/PG/Critic/Utils.pm b/lib/WeBWorK/PG/Critic/Utils.pm index a0e1f005d6..a36e322753 100644 --- a/lib/WeBWorK/PG/Critic/Utils.pm +++ b/lib/WeBWorK/PG/Critic/Utils.pm @@ -61,7 +61,7 @@ use Env qw(PG_ROOT); use lib curfile->dirname->dirname->dirname->dirname->dirname->child('lib'); -require WeBWorK::PG::Translator; +require Value; our @EXPORT_OK = qw(getDeprecatedMacros parsePGMLBlock parseTextBlock); @@ -74,7 +74,12 @@ sub getDeprecatedMacros () { { map { $_->basename => 1 } @{ path($PG_ROOT)->child('macros', 'deprecated')->list } }; } +# Mock methods used by PGML. sub main::PG_restricted_eval ($code) { return $code; } +sub main::loadMacros(@macros) { return; } +sub main::Context() { return; } + +do "$PG_ROOT/macros/core/PGML.pl"; sub walkPGMLTree ($block, $results //= {}) { for my $item (@{ $block->{stack} }) { @@ -103,22 +108,7 @@ sub parsePGMLBlock (@lines) { my $sourceHash = md5_sum(encode('UTF-8', $source)); return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; - package main; ## no critic (Modules::ProhibitMultiplePackages) - - require WeBWorK::PG::Environment; - require WeBWorK::PG; - require PGcore; - require Parser; - - $WeBWorK::PG::IO::pg_envir = WeBWorK::PG::Environment->new; - %main::envir = %{ WeBWorK::PG::defineProblemEnvironment($WeBWorK::PG::IO::pg_envir) }; - - do "$ENV{PG_ROOT}/macros/PG.pl"; - - $main::PG = $main::PG = PGcore->new(\%main::envir); - loadMacros('PGML.pl'); - - $PGML::warningsFatal = $PGML::warningsFatal = 1; + PGML::ClearWarnings(); my $parser = eval { PGML::Parse->new($source =~ s/\\\\/\\/gr) }; return { errors => [$@] } if $@; From 8c044073a46194a1e699130960faec0840621975 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 16 Jul 2025 08:59:16 -0500 Subject: [PATCH 072/111] Add warnings to the return data of the `parsePGMLBlock` method. --- lib/WeBWorK/PG/Critic/Utils.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/WeBWorK/PG/Critic/Utils.pm b/lib/WeBWorK/PG/Critic/Utils.pm index a36e322753..425233aeaf 100644 --- a/lib/WeBWorK/PG/Critic/Utils.pm +++ b/lib/WeBWorK/PG/Critic/Utils.pm @@ -31,6 +31,9 @@ blocks found. If the PGML content or a block within fails to parse, then the return hash will contain the key C with a reference to an array of errors that occurred. +Also if there are any warnings that occur in the parsing, those will be in the +C key of the return hash. + =head2 parseTextBlock my $textElements = parseTextBlock(@lines); @@ -110,9 +113,10 @@ sub parsePGMLBlock (@lines) { PGML::ClearWarnings(); my $parser = eval { PGML::Parse->new($source =~ s/\\\\/\\/gr) }; - return { errors => [$@] } if $@; + return { errors => [$@], warnings => \@PGML::warnings } if $@; - return $processedBlocks{$sourceHash} = WeBWorK::PG::Critic::Utils::walkPGMLTree($parser->{root}); + return $processedBlocks{$sourceHash} = + WeBWorK::PG::Critic::Utils::walkPGMLTree($parser->{root}, { warnings => \@PGML::warnings }); } # For now, only contents of \{ .. \} blocks are returned. Add other text elements as needed. From 59a5e7722c9f80ac5236afd55cde0bc2d6919f7e Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 15 Sep 2025 10:57:32 -0500 Subject: [PATCH 073/111] Add a SimpleGraph.pl macro for working with simple graphs from graph theory. This is intended to replace the PGnauGraphtheory.pl macro. That macro has lots of problems, and I don't see that macro as something worth repairing. One problem is that it has "NAU" (i.e., a institution name or acronym) in its name which is a practice that we want to get away from. Another problem is that is is implemented with a rather horrid function naming scheme which makes its usage rather tedious. It is almost hopeless to remember the method names, and so documentation must be constantly consulted. In addition there is no real documentation. The graph images that are produced by the macro use WWPlot.pm and PGgraphmacros.pl (via the PGnauGraphics.pl macro which also needs to be eliminated). Another problem is that the macro represents a graph by a matrix that is stored as a single string. That leads to rather ugly and inefficient code. At the heart of this macro is the `GraphTheory::SimpleGraph` object. Such an object can be created with the SimpleGraph function, several random graph generation functions, or a few other functions that produce various special types of graphs. Internally, the `GraphTheory::SimpleGraph` represents a graph by a Perl matrix (i.e., an array of arrays -- the natural form of a matrix in computer programming). However, one rarely needs to access that matrix directly since there are numerous convenience methods provided that can be used to get entries or modify the matrix. If you want to know about various properties of the graph there are also convenient methods for that. For example, if `$graph` is a `GraphTheory::SimpleGraph` object, and you want to know if the graph it represents is bipartite, just call `$graph->isBipartite`. To get a picture of the graph for display purposes use the object `image` method. For example, in PGML you can do `[!alt text!]{$graph->image}`. The default image layout arranges the vertices around the perimeter of a circle, but a grid layout, bipartite layout, and wheel layout (similar to the default layout but with a vertex at the center) are also available. These images are created as `Plots::Plot` objects from the `plots.pl` macro. There is one special MathObject Context provided. The `EdgeSet` context. This can be used for student answers to ask for the edge set of a graph. Note that given a `$graph` instance of the `GraphTheory::SimpleGraph` package, you can call `$edgeSet = $graph->edgeSet` to obtain the edge set for the graph already in this context. That can be used directly for an answer. There are two MathObjects that are provided for use in this context that work together. An `EdgeSet` and an `Edge`. Both are sets delimited by braces. An `EdgeSet` may only contain `Edge`s, and `Edge`s must contain exactly two vertices. Note that the vertices must be declared as `String`s in the context and have the `isVertex` flag set. For example, `Context()->strings->add('A' => { isVetex => 1, caseSensitive => 1 })` adds "A" to the context as a vertex. You can remove the `caseSensitive` flag if you don't want to allow "a" to also be entered for the vertex. Note that if you obtain an `EdgeSet` via `$graph->edgeSet`, then the vertices are automatically added to the context of the returned answer for you. The format a student would use to enter an edgeset is, for example, `{{A, B}, {C, E}}`. Although, by setting the cmp option `requireParenMatch => 0`, you can make it so the outer braces do not need to be entered. There is extensive POD documentation for all of this. Read that for more details. There is also a `SimpleGraphCatalog.pl` macro added. This is to replace the `PGnauGraphCatalog.pl` macro, and contains the same graphs formatted to work with the `SimpleGraph.pl` macro. --- macros/math/SimpleGraph.pl | 2670 +++++++ macros/math/SimpleGraphCatalog.pl | 10735 ++++++++++++++++++++++++++++ 2 files changed, 13405 insertions(+) create mode 100644 macros/math/SimpleGraph.pl create mode 100644 macros/math/SimpleGraphCatalog.pl diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl new file mode 100644 index 0000000000..957e6863fc --- /dev/null +++ b/macros/math/SimpleGraph.pl @@ -0,0 +1,2670 @@ +BEGIN { strict->import; } + +loadMacros('PGauxiliaryFunctions.pl', 'PGbasicmacros.pl', 'MathObjects.pl', 'plots.pl'); + +sub _SimpleGraph_init { + my $context = $main::context{EdgeSet} = Parser::Context->getCopy('Numeric'); + $context->{name} = 'EdgeSet'; + $context->{value}{EdgeSet} = 'GraphTheory::SimpleGraph::Value::EdgeSet'; + $context->{value}{Edge} = 'GraphTheory::SimpleGraph::Value::Edge'; + $context->lists->set( + # The "List" type list is set to use the GraphTheory::SimpleGraph::Parser::List class so that implicit lists can + # be turned into edges or edge sets, and empty lists can be interpreted as edge sets for a graph with no edges. + # All explicit lists that open with something other than a brace are untouched. + List => { %{ $context->lists->get('List') }, class => 'GraphTheory::SimpleGraph::Parser::List' }, + EdgeSet => + { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + Edge => { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + ); + $context->parens->set( + '{' => { close => '}', type => 'Edge', formList => 1, formMatrix => 0, removable => 0, emptyOK => 1 }); +} + +sub SimpleGraph { GraphTheory::SimpleGraph->new(@_) } +sub EdgeSet { GraphTheory::SimpleGraph::Value::EdgeSet->new(@_) } +sub Edge { GraphTheory::SimpleGraph::Value::Edge->new(@_) } + +sub randomSimpleGraph { + my ($size, %options) = @_; + + my $graph; + my $edgeCount = 0; + my $edgeProbability = $options{edgeProbability} // 0.5; + + if (ref $size eq 'ARRAY') { + $graph = + GraphTheory::SimpleGraph->new($size->[0] * $size->[1], %options, gridLayout => [ $size->[0], $size->[1] ]); + + for my $i (0 .. $size->[0] - 1) { + for my $j (0 .. $size->[1] - 1) { + my $location = $j * $size->[0] + $i; + + if ($j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0]); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + 1); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] + 1); + ++$edgeCount; + } + if ($i >= 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] - 1); + ++$edgeCount; + } + } + } + } else { + $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j (0 .. $i - 1) { + if (main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub randomGraphWithEulerCircuit { + my ($size, %options) = @_; + + die 'A graph with an Euler circuit must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 2) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->numComponents > 1; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomGraphWithEulerTrail { + my ($size, %options) = @_; + + die 'A graph with an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomGraphWithEulerCircuit($size, %options); + + my ($vertex1, $vertex2); + + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +sub randomGraphWithoutEulerTrail { + my ($size, %options) = @_; + + die 'A graph without an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 1) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->hasEulerTrail; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomBipartiteGraph { + my ($size, %options) = @_; + + my ($s1, $s2); + + if (ref $size eq 'ARRAY' && @$size == 2) { + ($s1, $s2) = @$size; + die 'A bipartite graph must have at least 1 vertex in each partition.' unless $s1 > 0 && $s2 > 0; + } else { + die 'A bipartite graph must have at least 2 vertices.' if $size < 2; + $s1 = main::random(1, $size - 1); + $s2 = $size - $s1; + } + + my $graph = GraphTheory::SimpleGraph->new($s1 + $s2, %options); + + my $edgeProbability = $options{edgeProbability} // 0.5; + my $edgeCount = 0; + + for my $i (0 .. $s1 - 1) { + for my $j ($s1 .. $s1 + $s2 - 1) { + next unless main::random(0, 100) <= 100 * $edgeProbability; + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomTreeGraph { + my ($size, %options) = @_; + + die 'A tree graph must have at least 2 vertices.' if $size < 2; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my @available = main::random_subset($size, 0 .. $size - 1); + + my @used; + push @used, pop @available; + do { + my $j = pop @available; + my $i = main::random(0, $#used); + $graph->addEdge($used[$i], $j); + push @used, $j; + } while @available > 0; + + return $graph->setRandomWeights(%options, edgeCount => @used - 1); +} + +sub randomForestGraph { + my ($size, %options) = @_; + + die 'A forest graph must have at least 2 vertices.' if $size < 2; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomTreeGraph($size, %options); + + my ($vertex1, $vertex2); + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +# Returns a Hamiltonian graph of the given $size. Note that $size must be 5 or greater. +sub randomHamiltonianGraph { + my ($size, %options) = @_; + + die 'A Hamiltonian graph must have at least 5 vertices.' if $size < 5; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my $comp = $size * ($size - 1) / 2; + + my ($low, $high) = $size <= 5 ? ($size + 1, $comp - 1) : (int($comp / 3) + 1, int($comp / 2) + 1); + + for my $i (0 .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + + my $edges = int main::random($low, $high); + + my $edgeCount = $size; + while ($edgeCount < $edges) { + my ($i, $j) = main::random_subset(2, 0 .. $size - 1); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomNonHamiltonianGraph { + my ($size, $type, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size); + my $edgeCount = 0; + + if ($type % 2 == 0) { + die 'A non Hamiltonian graph with a degree 1 vertex must have at least 5 vertices.' if $size < 5; + + my $numEdges = main::random($size + 1, ($size - 1) * ($size - 2) / 3 + 1); + + for my $i (0 .. $size - 2) { + $graph->addEdge($i, $i + 1); + } + $graph->addEdge($size - 2, 0); + + $edgeCount = $size; + do { + my ($i, $j) = main::random_subset(2, 0 .. $size - 2); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } while $edgeCount < $numEdges; + } else { + die 'A non Hamiltonian graph with two cycles must have at least 6 vertices.' if $size < 6; + + my $split = int($size / 2); + + for my $i (0 .. $split - 1) { + $graph->addEdge($i, ($i + 1) % $split); + } + for my $i ($split .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + $graph->addEdge($size - 1, $split); + + my $numEdges = 2 * $size - 5; + $edgeCount = $size + 1; + + my $maxtry = 4; # Protection against a possibly infinite loop. + while ($edgeCount < $numEdges && $maxtry > 0) { + --$maxtry; + my $v1 = main::random(0, $split - 1); + my $v2 = ($v1 + 2) % $split; + unless ($graph->hasEdge($v1, $v2)) { + $graph->addEdge($v1, $v2); + ++$edgeCount; + } + unless ($graph->hasEdge($v1 + $split, $v2 + $split)) { + $graph->addEdge($v1 + $split, $v2 + $split); + ++$edgeCount; + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub simpleGraphWithDegreeSequence { + my ($degrees, %options) = @_; + + my @degrees = reverse num_sort(@$degrees); + + return if $degrees[0] >= @degrees; + + my $graph = GraphTheory::SimpleGraph->new(scalar @degrees, %options); + + my $value = 0; + my $vertex = 0; + my $edgeCount = 0; + + while ($vertex < @degrees && $value == 0) { + $value = $degrees[$vertex] - $graph->vertexDegree($vertex); + my $otherVertex = $vertex + 1; + while ($value > 0 && $otherVertex < @degrees) { + if ($graph->vertexDegree($otherVertex) < $degrees[$otherVertex]) { + $graph->addEdge($vertex, $otherVertex); + ++$edgeCount; + --$value; + } + ++$otherVertex; + } + ++$vertex; + } + + return if $value != 0; + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub cycleGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for (0 .. $graph->lastVertexIndex) { + $graph->addEdge($_, ($_ + 1) % $graph->numVertices); + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices); +} + +sub completeGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j ($i + 1 .. $size - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices * ($graph->numVertices - 1) / 2); +} + +sub wheelGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options, wheelLayout => 0); + + for my $i (1 .. $size - 2) { + $graph->addEdge($i, $i + 1); + $graph->addEdge(0, $i); + } + $graph->addEdge($size - 1, 1); + $graph->addEdge(0, $size - 1); + + return $graph->setRandomWeights(%options, edgeCount => ($graph->numVertices - 1) * 2); +} + +sub completeBipartiteGraph { + my ($m, $n, %options) = @_; + + my $graph = + GraphTheory::SimpleGraph->new($m + $n, %options, bipartiteLayout => [ [ 0 .. $m - 1 ], [ $m .. $m + $n - 1 ] ]); + + for my $i (0 .. $m - 1) { + for my $j ($m .. $m + $n - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $m * $n); +} + +package GraphTheory::SimpleGraph; + +sub new { + my ($class, $definition, %options) = @_; + my $self = bless {}, ref($class) || $class; + + die 'A graph definition in the form of a numeric size, adjacency matrix, ' + . 'edge set, or another simple graph object is required.' + unless defined $definition; + + if (ref $definition eq 'GraphTheory::SimpleGraph') { + $self->{adjacencyMatrix} = [ map { [@$_] } @{ $definition->adjacencyMatrix } ]; + $self->{labels} = [ @{ $definition->{labels} } ]; + $options{gridLayout} //= $definition->{gridLayout}; + $options{bipartiteLayout} //= $definition->{bipartiteLayout}; + $options{wheelLayout} //= $definition->{wheelLayout}; + } elsif (Value::classMatch($definition, 'Matrix') + || Value::classMatch($definition, 'EdgeSet') + || ref $definition eq 'ARRAY') + { + $definition = [ map { [ $_->value ] } @{ $definition->data } ] if Value::classMatch($definition, 'Matrix'); + $definition = $definition->data if Value::classMatch($definition, 'EdgeSet'); + + my $haveLabels = ref $options{labels} eq 'ARRAY' && @{ $options{labels} }; + die 'Graphs with no vertices are not supported.' unless @$definition || $haveLabels; + + my @edgeSet; + for (@$definition) { + if (ref $_ ne 'GraphTheory::SimpleGraph::Value::Edge') { @edgeSet = (); last; } + push(@edgeSet, $_->data); + } + if (!@edgeSet) { + for (@$definition) { + if ( + ref $_ ne 'ARRAY' + || @$_ < 2 + || @$_ > 3 + || (!Value::classMatch($_->[0], 'String') + && (Value::isReal($_->[0]) || ($_->[0] ^ $_->[0]) eq '0')) + || (!Value::classMatch($_->[1], 'String') + && (Value::isReal($_->[1]) || ($_->[1] ^ $_->[1]) eq '0')) + ) + { + @edgeSet = (); + last; + } + push(@edgeSet, $_); + } + } + + if (@edgeSet || (!@$definition && $haveLabels)) { + die 'Labels must be provided when using an edgeset definition.' unless $haveLabels; + + $definition = [ map { $_->{data} } @$definition ] + if ref $definition->[0] eq 'GraphTheory::SimpleGraph::Value::Edge'; + + $self->{labels} = [ @{ $options{labels} } ]; + $self->{adjacencyMatrix} = [ map { [ (0) x @{ $self->{labels} } ] } @{ $self->{labels} } ]; + + my %labelIndices = map { $self->{labels}[$_] => $_ } 0 .. $#{ $self->{labels} }; + + for my $i (0 .. $#$definition) { + die 'Invalid edge set format.' unless ref $definition->[$i] eq 'ARRAY'; + my @edge = @{ $definition->[$i] }; + die 'Invalid edge format.' unless @edge >= 2; + die "Invalid vertex $edge[0] in edge set." unless defined $labelIndices{ $edge[0] }; + die "Invalid vertex $edge[1] in edge set." unless defined $labelIndices{ $edge[1] }; + $self->edgeWeight($labelIndices{ $edge[0] }, $labelIndices{ $edge[1] }, $edge[2] // 1); + } + } else { + $self->{adjacencyMatrix} = []; + for my $i (0 .. $#$definition) { + die 'Invalid adjacency matrix format.' unless ref $definition->[$i] eq 'ARRAY'; + die 'The adjacency matrix for a graph must be a square matrix.' + unless @{ $definition->[$i] } == @$definition; + die 'The diagonal entries of the adjacency matrix must be zero.' if $definition->[$i][$i]; + for my $j ($i + 1 .. $#{ $definition->[$i] }) { + die 'The adjacency matrix for a graph must be symmetric.' + unless $definition->[$i][$j] == $definition->[$j][$i]; + } + push(@{ $self->{adjacencyMatrix} }, [ @{ $definition->[$i] } ]); + } + } + } else { + die 'Graphs with no vertices are not supported.' unless $definition > 0; + $self->{adjacencyMatrix} = [ map { [ (0) x $definition ] } 0 .. ($definition - 1) ]; + } + + if (ref $options{labels} eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @{ $options{labels} } < $self->numVertices; + for (0 .. $self->lastVertexIndex) { + die 'Labels cannot be undefined.' unless defined $options{labels}[$_]; + } + $self->{labels} = [ @{ $options{labels} }[ 0 .. $self->lastVertexIndex ] ]; + } + + unless (defined $self->{labels}) { + my $alphaOffset = main::random(0, 25 - $self->lastVertexIndex); + $self->{labels} = [ ('A' .. 'Z')[ $alphaOffset .. $alphaOffset + $self->lastVertexIndex ] ]; + } + + $self->{gridLayout} = [ @{ $options{gridLayout} } ] + if ref $options{gridLayout} eq 'ARRAY' && @{ $options{gridLayout} } == 2; + + if (ref $options{bipartiteLayout} eq 'ARRAY' + && @{ $options{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $options{bipartiteLayout} }) + { + $self->{bipartiteLayout} = [ map { [@$_] } @{ $options{bipartiteLayout} } ]; + } elsif ($options{bipartiteLayout}) { + $self->{bipartiteLayout} = 1; + } + + $self->{wheelLayout} = $options{wheelLayout} if defined $options{wheelLayout}; + + return $self; +} + +sub adjacencyMatrix { + my $self = shift; + return $self->{adjacencyMatrix}; +} + +sub edgeSet { + my ($self, %options) = @_; + + my $context = Value::isContext($options{context}) ? $options{context} : Parser::Context->getCopy('EdgeSet'); + $self->addVerticesToContext($options{caseSensitive} // 1, $context); + + my @edgeSet; + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push( + @edgeSet, + GraphTheory::SimpleGraph::Value::Edge->new( + $context, $self->vertexLabel($i), $self->vertexLabel($j) + ) + ); + $edgeSet[-1]->{open} = '{'; + $edgeSet[-1]->{close} = '}'; + } + } + + my $edgeSet = GraphTheory::SimpleGraph::Value::EdgeSet->new($context, @edgeSet); + $edgeSet->{open} = '{'; + $edgeSet->{close} = '}'; + return $edgeSet; +} + +sub addVerticesToContext { + my ($self, $caseSensitive, $context) = @_; + $context = Value::isContext($context) ? $context : main::Context(); + $context->strings->add( + map { $_ => { caseSensitive => $caseSensitive // 1 } } + grep { !defined $context->strings->all->{$_} } @{ $self->labels } + ); + $context->strings->all->{$_}{isVertex} = 1 for @{ $self->labels }; + return; +} + +sub numVertices { + my $self = shift; + return scalar @{ $self->{adjacencyMatrix} }; +} + +sub lastVertexIndex { + my $self = shift; + return $#{ $self->{adjacencyMatrix} }; +} + +sub numEdges { + my $self = shift; + my $edgeCount = 0; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $i - 1) { + next unless $self->hasEdge($i, $j); + ++$edgeCount; + } + } + return $edgeCount; +} + +sub labels { + my ($self, $labels) = @_; + if (ref $labels eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @$labels < $self->numVertices; + $self->{labels} = [ @$labels[ 0 .. $self->numVertices ] ]; + } + return $self->{labels}; +} + +sub labelsString { + my $self = shift; + return join(', ', @{ $self->{labels} }); +} + +sub vertexLabel { + my ($self, $vertexIndex) = @_; + return $self->{labels}[$vertexIndex]; +} + +sub vertexIndex { + my ($self, $vertexLabel) = @_; + for (0 .. $#{ $self->{labels} }) { + return $_ if $vertexLabel eq $self->{labels}[$_]; + } + return -1; +} + +sub vertexDegree { + my ($self, $vertex) = @_; + my $degree = 0; + for my $j (0 .. $self->lastVertexIndex) { + ++$degree if $self->hasEdge($vertex, $j); + } + return $degree; +} + +sub degrees { + my $self = shift; + return map { $self->vertexDegree($_) } 0 .. $self->lastVertexIndex; +} + +sub numComponents { + my $self = shift; + + my @adjacencyMatrix = map { [@$_] } @{ $self->{adjacencyMatrix} }; + + my $result = @adjacencyMatrix; + for my $i (0 .. $#adjacencyMatrix) { + my $connected = 0; + for my $j ($i + 1 .. $#adjacencyMatrix) { + if ($adjacencyMatrix[$i][$j] != 0) { + ++$connected; + for my $k (0 .. $#adjacencyMatrix) { + $adjacencyMatrix[$j][$k] += $adjacencyMatrix[$i][$k]; + $adjacencyMatrix[$k][$j] += $adjacencyMatrix[$k][$i]; + } + } + } + --$result if $connected > 0; + } + return $result; +} + +sub edgeWeight { + my ($self, $i, $j, $weight) = @_; + if (defined $weight) { + $self->{adjacencyMatrix}[$i][$j] = $weight; + $self->{adjacencyMatrix}[$j][$i] = $weight; + } + return $self->{adjacencyMatrix}[$i][$j]; +} + +sub addEdge { + my ($self, $i, $j, $weight) = @_; + $self->edgeWeight($i, $j, $weight || 1); + return; +} + +sub removeEdge { + my ($self, $i, $j) = @_; + $self->edgeWeight($i, $j, 0); + return; +} + +sub hasEdge { + my ($self, $i, $j) = @_; + return $self->edgeWeight($i, $j) != 0; +} + +sub setRandomWeights { + my ($self, %options) = @_; + + my $incrementalRandom = + defined $options{startEdgeWeight} + && $options{startEdgeWeight} > 0 + && ($options{edgeWeightIncrement} // 1) > 0; + + return $self + unless $incrementalRandom + || (ref $options{edgeWeightRange} eq 'ARRAY' && @{ $options{edgeWeightRange} } >= 2); + + my $edgeCount = $options{edgeCount} // $self->numEdges; + + my @weights = + $incrementalRandom + ? main::random_subset($edgeCount, + map { $options{startEdgeWeight} + $_ * ($options{edgeWeightIncrement} // 1) } 0 .. $edgeCount - 1) + : map { main::random(@{ $options{edgeWeightRange} }) } 1 .. $edgeCount; + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + $self->edgeWeight($i, $j, shift(@weights) // 1) if $self->hasEdge($i, $j); + } + } + + return $self; +} + +sub isEqual { + my ($self, $other) = @_; + return 0 unless ref $other eq 'GraphTheory::SimpleGraph'; + return 0 if @{ $self->{adjacencyMatrix} } != @{ $other->{adjacencyMatrix} }; + for my $i (0 .. $#{ $self->{adjacencyMatrix} }) { + return 0 if @{ $self->{adjacencyMatrix}[$i] } != @{ $other->{adjacencyMatrix}[$i] }; + for my $j (0 .. $i - 1) { + return 0 if $self->{adjacencyMatrix}[$i][$j] != $other->{adjacencyMatrix}[$i][$j]; + } + } + return 1; +} + +sub isIsomorphic { + my ($self, $other) = @_; + + return 0 unless ref $other eq 'GraphTheory::SimpleGraph' && $self->numVertices == $other->numVertices; + + my @degrees = main::num_sort($self->degrees); + my @otherDegrees = main::num_sort($other->degrees); + for (0 .. $#degrees) { + return 0 unless $degrees[$_] == $otherDegrees[$_]; + } + + my $permutations = [ [0] ]; + + for my $i (1 .. $self->lastVertexIndex) { + my @newPermutations; + for my $permutation (@$permutations) { + for my $j (0 .. @$permutation) { + my @new = @$permutation; + splice(@new, $j, 0, $i); + push(@newPermutations, \@new); + } + } + $permutations = \@newPermutations; + } + + # The last permutation is the original vertex order, so remove it. + pop @$permutations; + + for my $permutation (@$permutations) { + my @shuffledGraph; + for my $i (0 .. $other->lastVertexIndex) { + for my $j (0 .. $other->lastVertexIndex) { + $shuffledGraph[ $permutation->[$i] ][ $permutation->[$j] ] = $other->edgeWeight($i, $j); + } + } + return 1 if $self->isEqual($self->new(\@shuffledGraph)); + } + + return 0; +} + +sub image { + my ($self, %options) = @_; + + return $self->gridLayoutImage(%options) if $self->{gridLayout}; + if ($self->{bipartiteLayout}) { + return $self->bipartiteLayoutImage(%options) if ref $self->{bipartiteLayout} eq 'ARRAY'; + # Attempt to partition the graph into two sets in which no edge connects vertices in the same set. + # If not found, then fall through and use the default circle layout. + my ($top, $bottom) = $self->bipartitePartition; + if (ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom) { + $self->{bipartiteLayout} = [ $top, $bottom ]; + return $self->bipartiteLayoutImage(%options); + } + } + return $self->wheelLayoutImage(%options) if defined $self->{wheelLayout}; + + $options{width} //= 250; + $options{height} //= $options{width}; + $options{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + width => $options{width}, + height => $options{height}, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0 + ); + + my $gap = 2 * $main::PI / ($self->numVertices || 1); + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ cos($i * $gap), sin($i * $gap) ]; + $plot->add_stamp(@$iVertex, color => 'blue'); + + $plot->add_label( + 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], + label => "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $options{showLabels}; + + my $u = 0.275; + my $v = 1 - $u; + + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($j * $gap), sin($j * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($options{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0] + $perp[0] * 0.06, + $u * $iVertex->[1] + $v * $jVertex->[1] + $perp[1] * 0.06, + label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub gridLayoutImage { + my ($self, %options) = @_; + + die 'Grid layout is not defined, or is but does not have a row and column dimension.' + unless ref $self->{gridLayout} eq 'ARRAY' && @{ $self->{gridLayout} } == 2; + + $options{showLabels} //= 1; + + my $gridGap = 20; + my $gridShift = $gridGap / 2; + my $labelShift = $gridGap / 15; + + my $plot = main::Plot( + xmin => -$gridShift, + xmax => $self->{gridLayout}[1] * $gridGap - $gridShift, + ymin => -$gridShift, + ymax => $self->{gridLayout}[0] * $gridGap - $gridShift, + width => 7 * ($self->{gridLayout}[1] - 1) * $gridGap, + height => 7 * ($self->{gridLayout}[0] - 1) * $gridGap, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0 + ); + + for my $i (0 .. $self->{gridLayout}[0] - 1) { + for my $j (0 .. $self->{gridLayout}[1] - 1) { + my $x = $gridGap * $j; + my $y = $gridGap * ($self->{gridLayout}[0] - $i - 1); + $plot->add_stamp($x, $y, color => 'blue'); + $plot->add_label( + $x - $labelShift, $y + 2 * $labelShift, + label => "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $options{showLabels}; + } + } + + my $u = 0.6666; + my $v = 1 - $u; + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ + int($i / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($i % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ + int($j / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($j % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black', width => 1); + my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; + if ($options{showWeights}) { + my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0] - $vector->[1] / $norm * 2, + $u * $iVertex->[1] + $v * $jVertex->[1] + $vector->[0] / $norm * 2, + label => "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", + color => 'red' + ); + } + } + } + } + + return $plot; +} + +sub bipartiteLayoutImage { + my ($self, %options) = @_; + + my ($top, $bottom); + + if (ref $self->{bipartiteLayout} eq 'ARRAY' + && @{ $self->{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $self->{bipartiteLayout} }) + { + ($top, $bottom) = @{ $self->{bipartiteLayout} }; + } elsif ($self->{bipartiteLayout}) { + ($top, $bottom) = $self->bipartitePartition; + die 'Graph is not bipartite.' unless ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom; + } else { + die 'Bipartite layout is not defined.'; + } + + $options{width} //= 250; + $options{height} //= $options{width}; + $options{showLabels} //= 1; + + my ($low, $high, $width) = (0, 15, 20); + my @shift = (0, 0); + + my $diff = @$top - @$bottom; + + my $x_max; + if ($diff == 0) { + $x_max = @$top * $width - 10; + } elsif ($diff % 2 == 0 && $diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += $width * $diff / 2; + } elsif ($diff % 2 == 0) { + $x_max = @$bottom * $width - 10; + $shift[0] += -$width * $diff / 2; + } elsif ($diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += ($width / 2) * $diff; + } else { + $x_max = @$bottom * $width - 10; + $shift[0] += (-$width / 2) * $diff; + } + + my $plot = main::Plot( + xmin => -10, + xmax => $x_max, + ymin => -5, + ymax => 20, + width => $options{width}, + height => $options{height}, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0 + ); + + for my $i (0 .. $#$top) { + $plot->add_stamp($i * $width + $shift[0], $high, color => 'blue'); + $plot->add_label( + $i * $width + $shift[0], $high + 2 / 3, + label => "\\\\($self->{labels}[$top->[$i]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'bottom' + ) if $options{showLabels}; + } + for my $j (0 .. $#$bottom) { + $plot->add_stamp($j * $width + $shift[1], $low, color => 'blue'); + $plot->add_label( + $j * $width + $shift[1], $low - 2 / 3, + label => "\\\\($self->{labels}[$bottom->[$j]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'top' + ) if $options{showLabels}; + } + + my ($u, $v) = $diff >= 0 ? (2 / 3, 1 / 3) : (1 / 3, 2 / 3); + + for my $i (0 .. $#$top) { + for my $j (0 .. $#$bottom) { + next unless $self->hasEdge($top->[$i], $bottom->[$j]); + my $point1 = [ $i * $width + $shift[0], $high ]; + my $point2 = [ $j * $width + $shift[1], $low ]; + $plot->add_dataset($point1, $point2, color => 'black'); + if ($options{showWeights}) { + my $vector = [ $point2->[0] - $point1->[0], $point2->[1] - $point1->[1] ]; + my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); + $plot->add_label( + $u * $point1->[0] + $v * $point2->[0] - $vector->[1] / $norm * 5 / 4, + $u * $point1->[1] + $v * $point2->[1] + $vector->[0] / $norm * 5 / 4, + label => "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", + color => 'red' + ); + } + } + } + + return $plot; +} + +sub wheelLayoutImage { + my ($self, %options) = @_; + + die 'Wheel layout is not defined.' unless defined $self->{wheelLayout}; + + $options{width} //= 250; + $options{height} //= $options{width}; + $options{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + width => $options{width}, + height => $options{height}, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0 + ); + + my $gap = 2 * $main::PI / ($self->lastVertexIndex || 1); + + $plot->add_stamp(0, 0, color => 'blue'); + $plot->add_label( + 0.1, 0.2, + label => "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $options{showLabels}; + + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $self->{wheelLayout}; + + my $iRel = $i > $self->{wheelLayout} ? $i - 1 : $i; + + my $iVertex = [ cos($iRel * $gap), sin($iRel * $gap) ]; + $plot->add_stamp(@$iVertex, color => 'blue'); + + $plot->add_label( + 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], + label => "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $options{showLabels}; + + if ($self->hasEdge($self->{wheelLayout}, $i)) { + $plot->add_dataset([ 0, 0 ], $iVertex, color => 'black'); + if ($options{showWeights}) { + my $norm = sqrt($iVertex->[0]**2 + $iVertex->[1]**2); + my @perp = ($iVertex->[1] / $norm, -$iVertex->[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + $iVertex->[1] / $norm * 0.1, + 0.5 * $iVertex->[1] - $iVertex->[0] / $norm * 0.1, + label => "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + + for my $j ($i + 1 .. $self->lastVertexIndex) { + next if $j == $self->{wheelLayout}; + + my $jRel = $j > $self->{wheelLayout} ? $j - 1 : $j; + + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($jRel * $gap), sin($jRel * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($options{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + 0.5 * $jVertex->[0] + $vector[1] / $norm * 0.1, + 0.5 * $iVertex->[1] + 0.5 * $jVertex->[1] - $vector[0] / $norm * 0.1, + label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub copy { + my $self = shift; + return $self->new($self); +} + +sub shuffle { + my ($self, $permuteLabels) = @_; + my @shuffledGraph; + my @vertexPermutation = main::random_subset($self->numVertices, 0 .. $self->lastVertexIndex); + my @inverseVertexPermutation; # Only needed if labels are also permuted. + @inverseVertexPermutation[@vertexPermutation] = 0 .. $#vertexPermutation; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $self->lastVertexIndex) { + $shuffledGraph[ $vertexPermutation[$i] ][ $vertexPermutation[$j] ] = $self->edgeWeight($i, $j); + } + } + return $self->new( + \@shuffledGraph, + labels => $permuteLabels ? [ @{ $self->{labels} }[@inverseVertexPermutation] ] : $self->{labels}, + ref $self->{bipartiteLayout} eq 'ARRAY' ? () : (bipartiteLayout => $self->{bipartiteLayout}), + defined $self->{wheelLayout} ? (wheelLayout => $vertexPermutation[ $self->{wheelLayout} ]) : () + ); +} + +sub nearestNeighborPath { + my ($self, $vertex) = @_; + + my @visited = (undef) x $self->numVertices; + $visited[$vertex] = 1; + + my @path = ($vertex); + my $weight = 0; + my $currentVertex = $vertex; + + while (@path < $self->numVertices) { + my $nearest; + my $min = 0; + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $currentVertex || defined $visited[$i] || !$self->hasEdge($currentVertex, $i); + if ($min == 0 || $self->edgeWeight($currentVertex, $i) < $min) { + $min = $self->edgeWeight($currentVertex, $i); + $nearest = $i; + } + } + last unless defined $nearest; + push @path, $nearest; + $visited[$nearest] = 1; + $weight += $self->edgeWeight($currentVertex, $nearest); + $currentVertex = $nearest; + } + + if ($self->hasEdge($currentVertex, $vertex)) { + push @path, $vertex; + $weight += $self->edgeWeight($currentVertex, $vertex); + } + + return (\@path, $weight); +} + +sub kruskalGraph { + my $self = shift; + + my $graph = $self->copy; + my $tree = GraphTheory::SimpleGraph->new($graph->numVertices, labels => $graph->labels); + my $numTreeComponents = $tree->numComponents; + + my $treeWeight = 0; + + my $weight = 0; + my @treeWeights; + + my @weights; + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + push(@weights, $graph->edgeWeight($i, $j)) if $graph->hasEdge($i, $j); + } + } + @weights = main::num_sort(@weights); + + while (@weights > 0) { + $weight = shift @weights; + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + if ($graph->edgeWeight($i, $j) == $weight) { + $graph->removeEdge($i, $j); + $tree->addEdge($i, $j, $weight); + my $currentTreeNumComponents = $tree->numComponents; + if ($currentTreeNumComponents < $numTreeComponents) { + $numTreeComponents = $currentTreeNumComponents; + $treeWeight += $weight; + push @treeWeights, $weight; + } else { + $tree->removeEdge($i, $j); + } + last; + } + } + } + } + + return ($tree, $treeWeight, \@treeWeights); +} + +sub hasEulerCircuit { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + for my $degree ($self->degrees) { + return wantarray ? (0, $main::PG->maketext('The degrees of the vertices in this graph are not all even.')) : 0 + if $degree % 2 != 0; + } + + return 1; +} + +sub hasEulerTrail { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + my ($odd, $even) = (0, 0); + + for my $degree ($self->degrees) { + if ($degree % 2 == 0) { ++$even } + else { ++$odd } + } + + return wantarray + ? ( + 0, + $main::PG->maketext( + 'This graph does not have two vertices of odd degree and all other vertices of even degree.') + ) + : 0 + if $even != $self->numVertices && $odd != 2; + return 1; +} + +sub pathIsEulerTrail { + my ($self, @path) = @_; + + my $graph = $self->copy; + + my $i = shift @path; + do { + my $j = shift @path; + return + wantarray ? (0, $main::PG->maketext('An edge traversed by this path does not exist in the graph.')) : 0 + unless $graph->hasEdge($i, $j); + $graph->removeEdge($i, $j); + $i = $j; + } while @path > 0; + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j (0 .. $i - 1) { + return wantarray ? (0, $main::PG->maketext('This path does not traverse all edges.')) : 0 + if $graph->hasEdge($i, $j); + } + } + + return 1; +} + +sub pathIsEulerCircuit { + my ($self, @path) = @_; + my @isEulerTrail = $self->pathIsEulerTrail(@path); + return wantarray ? @isEulerTrail : 0 unless $isEulerTrail[0]; + return wantarray ? (0, $main::PG->maketext('This path is not a circuit.')) : 0 unless $path[0] == $path[-1]; + return 1; +} + +sub hasCircuit { + my $self = shift; + + my $graph = $self->copy; + + for (my $i = 0; $i < $graph->numVertices; ++$i) { + my $sum = 0; + for my $j (0 .. $graph->lastVertexIndex) { + $sum += $graph->hasEdge($i, $j) ? 1 : 0; + } + if ($sum == 1) { + for my $j (0 .. $graph->lastVertexIndex) { + $graph->removeEdge($i, $j); + } + $i = -1; + } + } + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + return 1 if $graph->hasEdge($i, $j); + } + } + + return 0; +} + +sub isTree { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents > 1; + return 1; +} + +sub isForest { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return 1; +} + +# Returns 1 if the given $graph is bipartite, and 0 otherwise. +sub isBipartite { + my $self = shift; + + my @vertexColors = (0) x $self->numVertices; + my @color = (1, 2); + + my $done; + do { + my $i = 0; + while ($vertexColors[$i] != 0) { ++$i; } + $vertexColors[$i] = $color[0]; + + my @verticesToClassify; + push @verticesToClassify, $i; + + do { + my $vertex = shift @verticesToClassify; + my $currentColor = $vertexColors[$vertex]; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return 0 if $currentColor == $vertexColors[$i]; + if ($vertexColors[$i] == 0) { + push @verticesToClassify, $i; + $vertexColors[$i] = $color[ 2 - $currentColor ]; + } + } + } + } while @verticesToClassify; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + $done *= $vertexColors[$i]; + } + } while $done == 0; + + return 1; +} + +sub bipartitePartition { + my $self = shift; + + my @partition = ([], []); + + my %partitionAssignments; + + my $done; + do { + my $i = 0; + ++$i while defined $partitionAssignments{$i}; + $partitionAssignments{$i} = 0; + push(@{ $partition[0] }, $i); + + my @vertices; + push @vertices, $i; + + do { + my $vertex = shift @vertices; + my $currentPartition = $partitionAssignments{$vertex}; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return if defined $partitionAssignments{$i} && $currentPartition == $partitionAssignments{$i}; + if (!defined $partitionAssignments{$i}) { + push @vertices, $i; + $partitionAssignments{$i} = $currentPartition ? 0 : 1; + push(@{ $partition[ $partitionAssignments{$i} ] }, $i); + } + } + } + } while @vertices; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + unless (defined $partitionAssignments{$i}) { + $done = 0; + last; + } + } + } while !$done; + + $partition[0] = [ main::num_sort(@{ $partition[0] }) ]; + $partition[1] = [ main::num_sort(@{ $partition[1] }) ]; + + return @partition; +} + +sub dijkstraPath { + my ($self, $start, $end) = @_; + + my ($ind, $new, @dist, @path, @prev, @used); + + for my $i (0 .. $self->lastVertexIndex) { + $dist[$i] = -1; + $prev[$i] = -1; + } + $dist[$start] = 0; + + my @available = (0 .. $self->lastVertexIndex); + + while (@available) { + my $min = 1000000000; # Infinity (for all practical purposes) + for my $i (0 .. $#available) { + my $loc = $available[$i]; + if ($dist[$loc] >= 0 && $dist[$loc] < $min) { + $new = $loc; + $ind = $i; + $min = $dist[$new]; + } + } + push @used, $new; + splice @available, $ind, 1; + @used = main::num_sort(@used); + + for my $i (0 .. $self->lastVertexIndex) { + my $weight = $self->edgeWeight($new, $i); + if ($weight != 0) { + if ($dist[$i] > $min + $weight || $dist[$i] < 0) { + $dist[$i] = $min + $weight; + $prev[$i] = $new; + } + } + } + } + + my $loc = $end; + while ($loc != $start) { + unshift @path, $loc; + $loc = $prev[$loc]; + } + unshift @path, $start; + + return ($dist[$end], @path); +} + +sub sortedEdgesPath { + my $self = shift; + + my @weights; + my $sortedGraph = GraphTheory::SimpleGraph->new($self->numVertices, labels => $self->labels); + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push @weights, $self->edgeWeight($i, $j); + } + } + + @weights = main::num_sort(@weights); + + # Returns 1 if an edge can be added to the sorted edges based graph and 0 otherwise. An edge can be added if it does + # not make a vertex have more than two edges connected to it, and it does not create a circuit in the graph (unless + # it is the last vertex in which case that is okay since it completes the circuit). + my $goodEdge = sub { + my $graph = shift; + + my $sum = 0; + + for my $i (0 .. $graph->lastVertexIndex) { + my $degree = $graph->vertexDegree($i); + return 0 if $degree > 2; + $sum += $degree; + } + + return $sum < 2 * $graph->numVertices && $graph->hasCircuit ? 0 : 1; + }; + + my @pathWeights; + + do { + my $weight = shift @weights; + for my $i (0 .. $sortedGraph->lastVertexIndex) { + for my $j ($i + 1 .. $sortedGraph->lastVertexIndex) { + if ($weight == $self->edgeWeight($i, $j)) { + $sortedGraph->addEdge($i, $j, $self->edgeWeight($i, $j)); + if ($goodEdge->($sortedGraph)) { + push @pathWeights, $weight; + } else { + $sortedGraph->removeEdge($i, $j); + } + } + } + } + } while @pathWeights < $sortedGraph->numVertices && @weights > 0; + + return ($sortedGraph, \@pathWeights); +} + +sub chromaticNumber { + my $self = shift; + return Chromatic::computeBestColoring(@{ $self->{adjacencyMatrix} }); +} + +sub kColoring { + my ($self, $k) = @_; + + my @colors = (-1) x $self->numVertices; + my @nextColor = (0) x $self->numVertices; + my $v = 0; + + while ($v >= 0) { + my $assigned = 0; + while ($nextColor[$v] < $k) { + my $c = ++$nextColor[$v]; + my $ok = 1; + for my $u (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($v, $u) && $colors[$u] == $c) { + $ok = 0; + last; + } + } + if ($ok) { + $colors[$v] = $c; + ++$v; + return @colors if $v == $self->numVertices; + $nextColor[$v] = 0; + $assigned = 1; + last; + } + } + unless ($assigned) { + $colors[$v] = -1; + $nextColor[$v] = 0; + --$v; + } + } + + return; +} + +# The GraphTheory::SimpleGraph::Parser::List, GraphTheory::SimpleGraph::EdgeSet and GraphTheory::SimpleGraph::Edge +# packages are special lists for the edgeSet return value context. + +package GraphTheory::SimpleGraph::Parser::List; +our @ISA = qw(Parser::List::List); + +sub _check { + my $self = shift; + $self->SUPER::_check; + + # Only handle implicit lists or lists explicitly opened with a brace. + return if $self->{open} && $self->{open} ne '{'; + + my $entryType = $self->typeRef->{entryType}; + + # Since there can only be one brace paren, the distinction between an edge and an edge set needs to be made here. + # An empty list or a list that contains another list is an edge set. All other lists are edges. + my $isEdgeSet = $self->length ? 0 : 1; + for (@{ $self->{coords} }) { + if ($_->{type}{list} && $_->{type}{list} == 1) { $isEdgeSet = 1; last; } + } + + if ($isEdgeSet) { + $self->{type} = Value::Type('EdgeSet', scalar(@{ $self->{coords} }), $entryType, list => 1); + } elsif ($self->{type}{name} ne 'Edge') { + $self->{type} = Value::Type('Edge', scalar(@{ $self->{coords} }), $entryType, list => 1); + } + + if ($self->{type}{name} eq 'Edge') { + my $strings = $self->context->strings->all; + for (@{ $self->{coords} }) { + $self->Error('An edge may only contain vertices.') + unless ref $_ eq 'Parser::String' + && defined $strings->{ $_->{value} } + && $strings->{ $_->{value} }{isVertex}; + } + $self->Error('An edge must contain exactly two vertices.') if $self->length != 2; + } elsif ($self->{type}{name} eq 'EdgeSet') { + for (@{ $self->{coords} }) { + $self->Error('An edge set may only contain edges.') unless $_->{type}{name} eq 'Edge'; + } + } +} + +package GraphTheory::SimpleGraph::Value::EdgeSet; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'edge', + list_type => 'edge set', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +package GraphTheory::SimpleGraph::Value::Edge; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'vertex', + list_type => 'edge', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +1; + +=head1 NAME + +SimpleGraph.pl - Tools for displaying and manipulating simple graphs from graph +theory. + +=head1 DESCRIPTION + +The core of this macro is the C object which +represents simple graphs from graph theory via an adjacency matrix. + +=head1 FUNCTIONS + +The following functions can be used to construct a C +object. + +=head2 SimpleGraph + + $graph = SimpleGraph($definition, %options); + +This is an alias for the C constructor. + +The C<$definition> argument is required and can be the number of vertices in the +graph, another C object, a reference to an array of +arrays of numbers, a reference to a MathObject C, a reference to an +array of arrays containing two strings and possibly a number (an edge set with +optional edge weights), a reference to an array of +C objects, or a reference to a +C object. + +If C<$definition> is the number of vertices in the graph, then the graph that is +returned will have that number of vertices and no edges. Note that the number of +vertices must be greater than 0. + +If C<$definition> is another C object, then a copy of +the graph represented by that object will be returned. + +If C<$definition> is a one of the matrix forms (a reference to an array of +arrays of numbers or a reference to a MathObject C), then that matrix +will be used for the adjacency matrix of the graph. Note that it must be a +square symmetric matrix with zero entries along the diagonal and must have size +greater than or equal to 1. For example, + + [ + [ 0, 1, 0], + [ 1, 0, 2], + [ 0, 2, 0] + ] + +represents a graph that has three vertices, an edge between the first and +second vertices with weight one, and an edge between the second and third +vertices with weight 2. + +If C<$definition> is one of the edge set definition forms (a reference to an +array of arrays containing two strings and possibly a number, a reference to an +array of C objects, or a reference to a +C object), then the C option +is required. For the reference to an array of arrays form each edge in the edge +set must be an array containing two strings and possibly a third numeric +element. The two strings are the labels for the vertices connected by the edge. +The optional third element is the weight of the edge. If the optional third +element is not given, then the edge will have a weight of one. Note that all +labels provided in the C option will be used with this definition, and +will all be vertices in the graph. For example, if C<$definition> is given as + + [ + [ 'A', 'B' ], + [ 'A', 'C', 2 ], + [ 'B', 'C', 3 ], + ] + +and the C option is C<['A', 'B', 'C', 'D']>, then the return object will +represent a graph that has at four vertices labeled 'A', 'B', 'C', and 'D', +respectively, an edge between 'A' and 'B' with weight one, an edge between 'A' +and 'C' with weight 2, an edge between 'B' and 'C' with weight 3, and no edges +connecting to the vertex 'D'. Note that setting weights when a graph is +constructed via a reference to an array of C +objects or a reference to a C object is not +supported. However, weights can still be set after construction with the +L method or the L method. + +The arguments that may be passed in C<%options> are as follows. Note that all of +these options are undefined by default. + +=over + +=item labels + +The value of this option must be a reference to an array of strings. These are +the labels for the vertices of the graph. The array must contain at least as +many strings as there are vertices of the graph. Any extraneous strings will not +be used (except in the edge set definition case as mentioned above). If this +option is not given, then a random set of consecutive letters from the alphabet +will be used for the labels of the vertices. Note that if an edge set version of +the C<$definition> argument is used, then this option must be provided. + +=item gridLayout + +If this option is given, then it must be a reference to an array containing two +numbers, for example, C<[3, 4]>. In this case when the L method is +called to create the image of the graph, the vertices will be arranged into a +grid with the number of rows equal to the first number in the array, and the +number of columns equal to the second number in the array. Note that many graphs +will not work well in a grid layout. This is primarily intended for graphs +created via the L function where its first argument is used +as the value for this option. This option is generally intended for internal use +in this macro specifically for that function. However, there may be other graphs +that may also work well in a grid layout. + +=item bipartiteLayout + +If this option is given, then it must either be 1 or a reference to an array +containing two arrays of vertex indices. If this is 1, then a partition of the +vertices into two sets in which no two vertices in the same set are connected by +an edge will attempt to be obtained internally via the L +method. If the graph is not bipartite then no such partition will be found and +the L method will not use the bipartite layout. Instead the image will +be displayed in the default circle layout. If this is an array containing two +arrays of vertex indices, then the two arrays must be the partition of the +vertices of the graph in which no two vertices in the same set are connected by +an edge. In this case when the L method is called to create the image of +the graph, the vertices will be arranged into to rows with the vertices in the +first set in the top row, and the vertices in the second set in the bottom row. +Note that an exception will be thrown if this option is 1 and the graph is not +bipartite. + +=item wheelLayout + +If this option is given, then it must be an index of one of the vertices in the +graph (0, 1, 2, ...), and when the L method is called to create the image +of the graph, that vertex will be placed in the center of a circle, and the +other vertices will be evenly spaced around the perimeter of the circle. + +=back + +=head2 randomSimpleGraph + + $graph = randomSimpleGraph($size, %options); + +This function returns a C object in which the +existence of an edge between any two vertices is randomly determined. + +The C<$size> argument is required and must either be an integer that is greater +than zero, or a reference to an array containing two integers, both of which are +greater than zero. If this argument is an integer that is greater than zero, +then that will be the number of vertices of the graph. If this argument is a +reference to an array containing two integers that are greater than zero, then +the number of vertices in the graph will be the product of those two integers. +Furthermore, this array reference will be passed as the C option to +the C constructor, and the image of the graph will +have the vertices arranged into rows and columns. Furthermore, in the graph will +be generated such that the only possible edges will connect vertices that are +adjacent in that grid (including diagonal adjacency). + +The arguments that may be passed in C<%options> are as follows. + +=over + +=item labels + +The value of this option, if given, must be a reference to an array of strings. +These are the labels for the vertices of the graph. The array must contain at +least as many strings as there are vertices of the graph. Any extraneous strings +will not be used (except in the edge set definition case as mentioned above). If +this option is not given, then a random set of consecutive letters from the +alphabet will be used for the labels of the vertices. + +=item edgeProbability + +This is the probability that there will be an edge between any two vertices. By +default this is 0.5. Note that if C<$size> is a reference to an array of +integers, then this only applies to vertices that are adjacent in the grid, and +there will never be edges between vertices that are not adjacent in the grid. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. Furthermore, if neither this option nor the +C option are given, then all edge weights will be 1. + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=back + +The examples below demonstrate the usage of this function and its options in +more details. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 1); + +will return a complete graph with 3, 4, or 5 vertices and all edge weights equal +to 1. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 0); + +will return a graph with 3, 4, or 5 vertices that has no edges. Note that +C<$graph = SimpleGraph(random(3, 5))> is equivalent and is slightly more +efficient. + + $graph = randomSimpleGraph( + 5, + edgeProbability => 0.3, + edgeWeightRange => [1, 10] + ); + +will return a graph with 5 vertices with probability 0.3 of an edge between any +two vertices, and random edge weights from 1 to 10 (in increments of 1). + + $graph = randomSimpleGraph( + 5, + startEdgeWeight => 2, + edgeWeightIncrement => 3 + ); + +will return a graph with 5 vertices with probability 0.5 of an edge between any +two vertices, and the edge weights will be 2, 5, 8, 11, ... (which will be +randomly assigned to the edges in the graph). + + $graph = randomSimpleGraph([3, 4], edgeProbability => 0.6); + +will return a graph with 12 vertices and all edge weights equal to 1. When the +image of this graph is displayed the vertices will be arranged into a grid with +3 rows and 4 columns. The probability of an edge between vertices that are +adjacent in the grid is 0.6. There will be no edges between vertices that are +not adjacent in the grid. + +=head2 randomGraphWithEulerCircuit + + $graph = randomGraphWithEulerCircuit($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler circuit. This function +also accepts the C, C, C, and +C options accepted by the L function. + +=head2 randomGraphWithEulerTrail + + $graph = randomGraphWithEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler trail, but does not have +an Euler circuit. This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithoutEulerTrail + + $graph = randomGraphWithoutEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that does not have an Euler trail (or +circuit). This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomBipartiteGraph + + $graph = randomBipartiteGraph($size, %options); + +This function returns a C object that represents a +random bipartite graph. + +The C<$size> argument can either be a number or a reference to an array of two +integers that are greater than zero. If it is a number, then the graph will have +C<$size> vertices, and the number of vertices in the two parts of the bipartite +partition will be randomly determined. If it is a reference to an array of two +integers, then graph will have the number of vertices equal to the sum of those +two numbers, and the two sets in the bipartite partition will have those numbers +of elements. + +This function also accepts the C, C, +C, C, and C options +accepted by the L function. + +=head2 randomTreeGraph + + $graph = randomTreeGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a tree. This function also accepts +the C, C, C, and +C options accepted by the L function. + +=head2 randomForestGraph + + $graph = randomForestGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a forest, but is not a tree. This +function also accepts the C, C, C, +and C options accepted by the L function. + +=head2 randomHamiltonianGraph + + $graph = randomHamiltonianGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is Hamiltonian. Note that C<$size> must +be 5 or more, or an exception will be thrown. The C may be passed in +C<%options>, and is the same as for the above functions. + +=head2 randomNonHamiltonianGraph + + $graph = randomNonHamiltonianGraph($size, $type, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is not Hamiltonian. + +If C<$type> is even, then the graph will have a vertex of degree one. If +C<$type> is odd, then the graph will consist of two cycles joined by a single +edge. Note that for the odd C<$type> graph, the C<$size> must be at least 6, or +an exception will be thrown. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithDegreeSequence + + $graph = randomGraphWithDegreeSequence($degrees, %options); + +This function returns a C object that represents a +random graph that has the given C<$degree> sequence, if such a graph is +possible, and undefined otherwise. + +The C<$degrees> argument must be a reference to an array containing the desired +vertex degrees. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 cycleGraph + + $graph = cycleGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a cycle. This function also accepts the +C, C, C, and C +options accepted by the L function. + +=head2 completeGraph + + $graph = completeGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a complete graph. This function also +accepts the C, C, C, and +C options accepted by the L function. + +=head2 wheelGraph + + $graph = wheelGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a wheel. This function also accepts the +C, C, C, and C +options accepted by the L function. + +Note that the returned graph object will have the C graph option +set to the vertex at the hub of the wheel, and that vertex will be displayed in +the center in the graph image. + +=head2 completeBipartiteGraph + + $graph = completeBipartiteGraph($m, $n, %options); + +This function returns a C object that represents a +graph with C<$m * $n> vertices that is a complete bipartite graph with C<$m> +vertices in first set in the bipartite partition, and C<$n> vertices in the +other set in the bipartite partition. This function also accepts the C, +C, C, and C options +accepted by the L function. + +Note that the returned graph object will have the C graph +option set so that the graph image is nicely displayed with the vertices in the +first set in the bipartite partition in a row on the top, and the vertices in +the other set in a row on the bottom. + +=head2 EdgeSet + + $edgeSet = EdgeSet($edges); + $edgeSet = EdgeSet($edge1, $edge2, ...); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array of edges or +just a list of edges. An edges can be a reference to an array containing two +vertices, or can be a C MathObject. As +with all MathObject constructors, the first argument can optionally be a +context. Note that vertices must be strings in the current context or the +context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed to the C +function to create a graph with that edge set. + +=head2 Edge + + $edge = Edge($vertex1, $vertex2); + $edge = EdgeSet([ $vertex1, $vertex2 ]); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array containing two +vertices or a list of two vertices. As with all MathObject constructors, the +first argument can optionally be a context. Note that vertices must be strings +in the current context or the context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed in an array reference to +the C function to create a graph with that edge set. + +=head1 GraphTheory::SimpleGraph Methods + +The C package is the heart of this macro. All of the +functions described above return an object that is an instance of this package +class. See the L function for the constructor usage. In addition, +the following object methods are available. + +=head2 adjacencyMatrix + + $matrix = $graph->adjacencyMatrix; + +This method returns the adjacency matrix that defines the graph. That is a +reference to an array of array references each containing numbers. The matrix +will be a symmetric square matrix with zero entries along the diagonal. The +entry in the i-th row and j-th column will be zero if there is not an edge +connecting the i-th and j-th vertex, and will be nonzero if there is such an +edge. Furthermore, the entry represents the edge weight in the case that it is +not zero. + +Note that indexing of vertices is zero based. So the first vertex has index 0, +and the last vertex has index one less than the number of vertices in the graph. + +=head2 edgeSet + + $edgeSet = $graph->edgeSet(%options); + +This method returns the edge set for the graph as a MathObject C +containing MathObject Cs. For example, if a graph has adjacency matrix + + [ + [ 0, 1, 0, 0 ], + [ 1, 0, 1, 0 ], + [ 0, 1, 0, 1 ] + [ 0, 0, 1, 0 ] + ] + +and labels 'A', 'B', 'C', and 'D', then this method would return + + {{A, B}, {B, C}, {C, D}} + +where the outer set is a MathObject C, the elements of that set are +MathObject Cs, and the elements of those sets are MathObject Cs. +The returned C will be either be in a created C context with +the vertex labels of the graph defined as strings and marked as being vertices +in that context, or in a context provided in the C<%options> as described below. + +This return value can be used as an answer to a question, and will display in +output as shown above. + +Note, that edge weights are not represented in the return value of this method. + +The following options that may be passed in C<%options>. + +=over + +=item context (Default: C<< context => undef >>) + +The context to use for the returned MathObject C object. If this is not +provided, then a C context will be created, the vertex labels added as +strings to the context and marked as being vertices and the returned object will +be in the created context. + +=item caseSensitive (Default: C<< caseSensitive => 1 >>) + +Whether vertex labels that are added to the context are case sensitive or not. + +=back + +=head2 addVerticesToContext + + $graph->addVerticesToContext($caseSensitive, $context); + +This adds the vertex labels of the graph to the context and marks them as being +vertices (by adding the C flag). + +The C<$caseSensitive> and C<$context> arguments are optional. + +If C<$caseSensitive> is 1 or the argument is not provided, then the vertices +will be case sensitive. So entering "a" will not be accepted as correct for the +vertex labeled "A". If C<$caseSensitive> is 0, then vertices will not be case +sensitive. So "a" will be accepted as correct for the vertex labeled "A". + +If C<$context> is provided, then the strings will be added to that context. +Otherwise the strings will be added to the current context. + +=head2 numVertices + + $n = $graph->numVertices; + +This method returns the number of vertices in the graph. + +=head2 lastVertex + + $n = $graph->lastVertexIndex; + +This method returns the index of the last vertex in the graph (i.e., it is one +less then the number of vertices). + +=head2 numEdges + + $n = $graph->numEdges; + +This method returns the number of edges in the graph. + +=head2 labels + + $labels = $graph->labels; + $graph->labels($labels); + +Get or set the vertex labels for the graph. In both examples above, C<$labels> +is a reference to an array of strings, for example, C<[ 'M', 'N', 'O', 'P' ]>. + +=head2 labelsString + + $labelsString = $graph->labelsString; + +This returns the vertex labels joined with a comma. This is a convenience method +for displaying labels in a problem. For example, the labels string can be +inserted into a PGML block with C<< [`[$graph->labelsString]`] >>. + +=head2 vertexLabel + + $label = $graph->vertexLabel($index); + +This method returns the label of the vertex at index C<$index> in the graph. + +=head2 vertexIndex + + $index = $graph->vertexIndex($label); + +This method returns the index of the vertex in the graph that is labeled +C<$label> if the given C<$label> exists for a vertex, and -1 otherwise. + +=head2 vertexDegree + + $degree = $graph->vertexDegree($index); + +This method returns the degree of the vertex at index C<$index> in the graph. + +=head2 degrees + + @degrees = $graph->degrees; + +This method returns an array of the degrees of the vertices in the graph. + +=head2 numComponents + + $c = $graph->numComponents; + +This method returns the number of connected components in the graph. + +=head2 edgeWeight + + $c = $graph->edgeWeight($i, $j); + $graph->edgeWeight($i, $j, $weight); + +Get or set the weight of the edge between the vertices at indices C<$i> and +C<$i>. If the optional third C<$weight> argument is provided, then the weight +of the edge between the vertices at indices C<$i> and C<$j> will be set to +C<$weight>. In any case, the weight of the edge between the vertices at indices +C<$i> and C<$j> will be returned. Note that an edge weight of zero means that +there is no edge connecting the vertices. + +Setting the edge weight to zero is equivalent to removing the edge if it was +nonzero, and setting the edge weight to a nonzero number when it was zero adds +an edge. However, if your only intent is to add or remove an edge, then it is +recommended to use the C or C methods instead for clarity +in the code. + +=head2 addEdge + + $graph->addEdge($i, $j, $weight); + +Add and edge that connects the vertices at indices C<$i> and C<$j>. The +C<$weight> argument is optional, and if not provided the weight of the edge will +be set to 1. + +=head2 removeEdge + + $graph->removeEdge($i, $j); + +Remove the edge that connects the vertices at indices C<$i> and C<$j>. This is +accomplished by setting the weight of the edge between the two vertices to 0. + +=head2 hasEdge + + $graph->hasEdge($i, $j); + +This method returns a true value if the graph has an edge that connects the +vertices at indices C<$i> and C<$j>, and a false value otherwise. + +=head2 setRandomWeights + + $graph->setRandomWeights(%options); + +Set random weights for the edges of the graph. This method does not add or +remove any edges, but randomly sets the weights of the existing edges. + +The arguments that may be passed in C<%options> are as follows. If neither of +the C or C options is given, then this +method does not change the current edge weights of the graph. + +=over + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. + +=item edgeCount + +The number of edges in the graph for which to set random weights. If this +option is not given, then the number of edges in the graph will be computed, and +all the weights of all existing edges will be randomized. If the number of edges +in the graph is already known, then set this option to that value for efficiency +so that this method does not need to compute that number again. + +=back + +=head2 isEqual + + $graph->isEqual($other); + +This method returns 1 if the graph object represented by C<$graph> and the graph +represented by C<$other> are the same. This does not necessarily return 1 for +isomorphic graphs. The two graphs must literally have the same adjacency matrix. + +=head2 isIsomorphic + + $graph->isIsomorphic($other); + +This method returns 1 if the graph represented by C<$graph> and the graph +represented by C<$other> are isomorphic. + +WARNING: This method uses a brute force approach and compares the first graph to +all possible permutations of the other graph, and so should not be used for +graphs with a large number of vertices (probably no more than 8). + +=head2 image + + $graph->image(%options); + +Constructs and returns a L object via the L macro that +provides a pictorial representation of the graph. The returned object may be +inserted into the problem via the C method (see L), L, or using the PGML image syntax +(for example, C<< [!alt text!]{$graph->image} >>). + +Note that the C, C, and C options that +can be set when the C object is created affect how the +graph is displayed. See the L function for details on those +options. Note that those options can also be set as properties of the object +anytime after construction and before this method is called (for example, +C<< $self->{bipartiteLayout = 1; >>). If none of those layout options are used, +then the vertices of the graph will be evenly spaced around the perimeter of a +circle with the vertex at index 0 on the right. + +The following options can be set via the C<%options> argument. + +=over + +=item width + +This is the width of the image. Default is 250. + +Note that the width option is not honored if the C is used. Note +that the width can still be set via the L C method, or +via the width option for the PGML image syntax. + +=item height + +This is the height of the image. Default is the value of the width option above. + +Note that the height option is not honored if the C is used. Note +that the height can still be set via the L C method, or +via the height option for the PGML image syntax. + +=item showLabels + +If this is 1, then vertex labels will be shown. If this is 0, then vertex labels +will not be shown. Default is 1 (so labels must be explicitly hidden by setting +this to 0). + +=item showWeights + +If this is 1, then edge weights will be shown. If this is 0, then edge weights +will not be shown. Default is 0. + +Note that the display of edge weights often does not work well as it can be +unclear which edge a given weight belongs to in the image. Particularly if a +graph has a large number of edges. Graphs that are created via the +C function using the row and column C<$size> argument (and +hence are displayed using the grid layout) do work quite well for this. + +=back + +=head2 gridLayoutImage + + $graph->gridLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this method +is called directly and the C property is set, then it will still +work. Otherwise an exception will be thrown. It accepts the same options as the +L method, but does not honor the C and C options. + +=head2 bipartiteLayoutImage + + $graph->bipartiteLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it +will still work. Otherwise an exception will be thrown. Note that an exception +will also be thrown if the C property is set to 1, and the +graph is not bipartite. It accepts the same options as the L method. + +=head2 wheelLayoutImage + + $graph->wheelLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it will +still work. Otherwise an exception will be thrown. It accepts the same options +as the L method. + +=head2 copy + + $copy = $graph->copy; + +This method returns a copy of C<$graph>. This is an exact copy with all +properties duplicated to the returned object. + +=head2 shuffle + + $shuffledGraph = $graph->shuffle($permuteLabels); + +This method returns a randomization of C<$graph> obtained by permuting the +vertices. The edges that connected the vertices in the original graph will still +connect the permuted vertices in the resulting graph. In other words the +returned graph is isomorphic to the original. + +If the optional C<$permuteLabels> argument is provided and is true, then the +labels will also be permuted with the vertices. Otherwise the labels will remain +in the same order that they were in the original graph, but the shuffled graph +will still have the same labels. + +If the C property is set to 1 for the original graph, then the +shuffled graph will also have also have that property set to 1. However, the +array form of the C property will not be preserved. + +The C property is not preserved in the shuffled graph in any case. + +The C property is preserved in the shuffled graph, and its value +will be permuted so that the vertex it marked as the hub of the wheel is still +the hub. This means that the hub of the wheel will still be displayed in the +center when the L method is used to display the graph. If that is not +desired, then delete the C property for the result. For example, + + $shuffledGraph = $graph->shuffle; + delete $shuffledGraph->{wheelLayout}; + +=head2 nearestNeighborPath + + ($path, $weight) = $graph->nearestNeighborPath($vertex); + +This is an implementation of the nearest neighbor algorithm. It attempts to find +the shortest path starting at the vertex indexed by C<$vertex> that visits all +vertices in the graph exactly once and returns to the starting vertex. Note that +if such a path is not possible, then the algorithm will go as far as it can, but +the path that is returned may not visit all vertices and may not be a circuit. +This method will always succeed for complete graphs. + +The method returns a list whose first entry is a reference to an array +containing the path found, and whose second entry is the total weight of that +path. + +=head2 kruskalGraph + + ($tree, $treeWeight, $treeWeights) = $graph->kruskalGraph($vertex); + +This is an implementation of Kruskal's algorithm. It attempts to find a minimum +spanning tree or forest for the graph. Note that if the graph is connected, then +the result will be a tree, and otherwise it will be a forest consisting of +minimal spanning trees for each component. + +The method returns a list with three entries. The first entry is a +C object representing the tree or forest found. The +second entry is the total weight of that tree or forest. The last entry is a +reference to an array containing the weights of the edges in the tree or forest +in the order that they are added by the algorithm. + +=head2 hasEulerCircuit + + $hasEulerCircuit = $graph->hasEulerCircuit; + ($hasEulerCircuit, $message) = $graph->hasEulerCircuit; + +In scalar context this method returns 1 if the graph has an Euler circuit, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +circuit, and if the graph does not have an Euler circuit it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler circuit. + +=head2 hasEulerTrail + + $hasEulerTrail = $graph->hasEulerTrail; + ($hasEulerTrail, $message) = $graph->hasEulerTrail; + +In scalar context this method returns 1 if the graph has an Euler trail, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +trail, and if the graph does not have an Euler trail it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler trail. + +=head2 pathIsEulerTrail + + $pathIsEulerTrail = $graph->pathIsEulerTrail(@path); + ($pathIsEulerTrail, $message) = $graph->pathIsEulerTrail(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler trail in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler trail in the graph, and if the path +does not form an Euler trail in the graph it returns a list whose first entry is +0 and whose second entry is a message stating the reason that the path does not +form an Euler trail. + +=head2 pathIsEulerCircuit + + $pathIsEulerCircuit = $graph->pathIsEulerCircuit(@path); + ($pathIsEulerCircuit, $message) = $graph->pathIsEulerCircuit(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler circuit in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler circuit in the graph, and if the +path does not form an Euler circuit in the graph it returns a list whose first +entry is 0 and whose second entry is a message stating the reason that the path +does not form an Euler circuit. + +=head2 hasCircuit + + $hasCircuit = $graph->hasCircuit; + +This method returns 1 if the graph has a circuit, and 0 otherwise. + +=head2 isTree + + $isTree = $graph->isTree; + ($isTree, $message) = $graph->isTree; + +In scalar context this method returns 1 if the graph is a tree, and 0 otherwise. +In list context this method returns 1 if the graph is a tree, and if the graph +is not a tree it returns a list whose first entry is 0 and whose second entry is +a message stating the reason that the graph is not a tree. + +=head2 isForest + + $isForest = $graph->isForest; + ($isForest, $message) = $graph->isForest; + +In scalar context this method returns 1 if the graph is a forest, and 0 +otherwise. In list context this method returns 1 if the graph is a forest, and +if the graph is not a forest it returns a list whose first entry is 0 and whose +second entry is a message stating the reason that the graph is not a forest. + +=head2 isBipartite + + $isBipartite = $graph->isBipartite; + +This method returns 1 if the graph is bipartite, and 0 otherwise. + +=head2 bipartitePartition + + ($upper, $lower) = $graph->bipartitePartition; + +If the graph is bipartite, then this method returns a list containing two +entries that form a partition of the vertices of the graph into two sets for +which no two vertices in the same set have an edge connecting them. If the graph +is not bipartite, then this method returns undefined. + +=head2 dijkstraPath + + ($distance, @path) = $graph->dijkstraPath($start, $end); + +This is an implementation of Dijkstra's algorithm for finding the shortest path +between nodes in a weighted graph. + +The C<$start> and C<$end> arguments are required and should be indices of two +vertices in the graph representing the start vertex and end vertex for which to +find the shortest path between. + +The return value will be a list whose first entry is the shortest distance from +the start vertex to the end vertex, and whose remaining entries are the indices +of the vertices that form the shortest path from the start vertex to the end +vertex. + +=head2 sortedEdgesPath + + ($sortedEdgesPath, $edgeWeights) = $graph->sortedEdgesPath; + +This is an implementation of the sorted edges algorithm for finding the shortest +Hamiltonian circuit in a graph. That is a path that visits each vertex in the +graph exactly once. The return value will be a list with two entries The first +entry is the resulting sorted edges graph, and the second entry is a reference +to an array containing the weights of the edges in the path in the order that +they are chosen by the algorithm. Note that the returned graph will contain a +Hamiltonian circuit from the original graph if one exists. In any case the graph +will contain all edges chosen in the algorithm. + +=head2 chromaticNumber + + $graph->chromaticNumber; + +This method returns the chromatic number of the graph. That is the minimum +number of colors required to color the vertices such that no two adjacent +vertices share the same color. + +=head2 kColoring + + $graph->kColoring($k); + +The argument C<$k> is required and is the desired number of colors for which to +find a k-coloring. That is a coloring of the graph consisting of at most k +colors such that no two adjacent vertices share the same color. If such a +coloring is possible, then this method will return a list with the number of +entries equal to the number of vertices in the graph, and whose i-th entry will +be an integer from 1 to k where the integers represent a coloring for the vertex +with index i in the graph and such that the list forms a k-coloring. If a +k-coloring is not possible, then this method returns undefined. + +=head1 EdgeSet Context + +A context for edge sets is provided. To activate the context Call + + Context('EdgeSet'); + +Then C and +C objects can be constructed with + + $edge = Compute('{B, E}'); + $edgeSet = Compute('{{A, B}, {A, E}, {A, F}, {E, F}}'); + +or by using the C or C methods described before (those methods +can also be used outside of this context). Note that the vertices must be added +as strings to the context and marked as being vertices with the C +flag before constructing the C or C. For example, by calling + + Context()->strings->add( + map { $_ => { isVertex => 1, caseSensitive => 1 } } 'A' .. 'F' + ); + +If it is prefered that the vertices not be case sensitive, then remove +C<< caseSensitive => 1 >> from the above call. + +If the vertices in the C or C belong to a C +object that has already been constructed, then the L +method can instead be used to add the vertices to the context. + +=cut diff --git a/macros/math/SimpleGraphCatalog.pl b/macros/math/SimpleGraphCatalog.pl new file mode 100644 index 0000000000..c90fd2c711 --- /dev/null +++ b/macros/math/SimpleGraphCatalog.pl @@ -0,0 +1,10735 @@ + +=head1 NAME + +SimpleGraphCatalog.pl - A catalog of all simple graphs with fewer than 8 +vertices. + +=head1 DESCRIPTION + +This macro defines the C<$graphCatalog> hash. It has 8 keys (0, 1, ..., 8). the +value of each key is a reference to an array containing all possible graphs (up +to isomorphism) with that number of vertices. + +Graphs are represented as a reference to an array of arrays which are suitable +for passing to L to create a +C object. + +For example, C<$graphCatalog{3}[3]> is + + [ [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ] + +=cut + +BEGIN { strict->import; } + +sub _GraphCatalog_init { } + +%main::graphCatalog = ( + 1 => [ [ [0] ] ], + 2 => [ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 1 ], [ 1, 0 ] ] ], + 3 => [ + [ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ], + [ [ 0, 0, 1 ], [ 0, 0, 0 ], [ 1, 0, 0 ] ], + [ [ 0, 0, 1 ], [ 0, 0, 1 ], [ 1, 1, 0 ] ], + [ [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ], + ], + 4 => [ + [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 0 ], [ 1, 0, 0, 1 ], [ 1, 0, 1, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 1, 1 ], [ 1, 1, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 1, 1, 1 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 1, 1, 1, 0 ] ] + ], + 5 => [ + [ [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 0, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0 ], [ 1, 0, 1, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 0 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 1 ], [ 1, 0, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 1, 1, 1, 1 ], [ 1, 0, 1, 1, 1 ], [ 1, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ] + ], + 6 => [ + [ + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 1, 1, 1, 1, 1 ], + [ 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ] + ], + 7 => [ + [ + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 1, 1, 1, 1, 1, 1 ], + [ 1, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ] + ] +); + +1; From f1950c55cf05bdc012e28600b1fa67576d7ba35b Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 16 Dec 2025 20:26:36 -0600 Subject: [PATCH 074/111] Rework the width and height for images. First, an `aspect_ratio` option is added. This option is 1 by default. The option is actually just passed to the underlying Plots::Axes object which implements this. So now the default width is 250, the default height is undefined, and the default aspect_ratio is 1. The width or height can be changed using the `PGbasicmacros.pl` `image` method width or height options, or via the PGML image syntax width or height options. Furthermore, all other options passed to the image methods are just passed on to the underlying Plots object constructor except for the `showLabels` and `showWeights` options that are specific to the simple graph object image. --- macros/math/SimpleGraph.pl | 180 ++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 82 deletions(-) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index 957e6863fc..7874eb6263 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -770,22 +770,23 @@ sub image { } return $self->wheelLayoutImage(%options) if defined $self->{wheelLayout}; - $options{width} //= 250; - $options{height} //= $options{width}; - $options{showLabels} //= 1; + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; my $plot = main::Plot( - xmin => -1.5, - xmax => 1.5, - ymin => -1.5, - ymax => 1.5, - width => $options{width}, - height => $options{height}, - xlabel => '', - ylabel => '', - xvisible => 0, - yvisible => 0, - show_grid => 0 + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + width => 250, + aspect_ratio => 1, + %options ); my $gap = 2 * $main::PI / ($self->numVertices || 1); @@ -800,7 +801,7 @@ sub image { color => 'blue', h_align => 'center', v_align => 'middle' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; my $u = 0.275; my $v = 1 - $u; @@ -810,7 +811,7 @@ sub image { my $jVertex = [ cos($j * $gap), sin($j * $gap) ]; $plot->add_dataset($iVertex, $jVertex, color => 'black'); - if ($options{showWeights}) { + if ($graphOptions{showWeights}) { my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); my $norm = sqrt($vector[0]**2 + $vector[1]**2); my @perp = ($vector[1] / $norm, -$vector[0] / $norm); @@ -837,24 +838,27 @@ sub gridLayoutImage { die 'Grid layout is not defined, or is but does not have a row and column dimension.' unless ref $self->{gridLayout} eq 'ARRAY' && @{ $self->{gridLayout} } == 2; - $options{showLabels} //= 1; + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; my $gridGap = 20; my $gridShift = $gridGap / 2; my $labelShift = $gridGap / 15; my $plot = main::Plot( - xmin => -$gridShift, - xmax => $self->{gridLayout}[1] * $gridGap - $gridShift, - ymin => -$gridShift, - ymax => $self->{gridLayout}[0] * $gridGap - $gridShift, - width => 7 * ($self->{gridLayout}[1] - 1) * $gridGap, - height => 7 * ($self->{gridLayout}[0] - 1) * $gridGap, - xlabel => '', - ylabel => '', - xvisible => 0, - yvisible => 0, - show_grid => 0 + xmin => -$gridShift, + xmax => $self->{gridLayout}[1] * $gridGap - $gridShift, + ymin => -$gridShift, + ymax => $self->{gridLayout}[0] * $gridGap - $gridShift, + width => 7 * ($self->{gridLayout}[1] - 1) * $gridGap, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options ); for my $i (0 .. $self->{gridLayout}[0] - 1) { @@ -868,7 +872,7 @@ sub gridLayoutImage { color => 'blue', h_align => 'center', v_align => 'middle' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; } } @@ -888,7 +892,7 @@ sub gridLayoutImage { ]; $plot->add_dataset($iVertex, $jVertex, color => 'black', width => 1); my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; - if ($options{showWeights}) { + if ($graphOptions{showWeights}) { my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); $plot->add_label( $u * $iVertex->[0] + $v * $jVertex->[0] - $vector->[1] / $norm * 2, @@ -921,9 +925,9 @@ sub bipartiteLayoutImage { die 'Bipartite layout is not defined.'; } - $options{width} //= 250; - $options{height} //= $options{width}; - $options{showLabels} //= 1; + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; my ($low, $high, $width) = (0, 15, 20); my @shift = (0, 0); @@ -948,17 +952,18 @@ sub bipartiteLayoutImage { } my $plot = main::Plot( - xmin => -10, - xmax => $x_max, - ymin => -5, - ymax => 20, - width => $options{width}, - height => $options{height}, - xlabel => '', - ylabel => '', - xvisible => 0, - yvisible => 0, - show_grid => 0 + xmin => -10, + xmax => $x_max, + ymin => -5, + ymax => 20, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options ); for my $i (0 .. $#$top) { @@ -969,7 +974,7 @@ sub bipartiteLayoutImage { color => 'blue', h_align => 'center', v_align => 'bottom' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; } for my $j (0 .. $#$bottom) { $plot->add_stamp($j * $width + $shift[1], $low, color => 'blue'); @@ -979,7 +984,7 @@ sub bipartiteLayoutImage { color => 'blue', h_align => 'center', v_align => 'top' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; } my ($u, $v) = $diff >= 0 ? (2 / 3, 1 / 3) : (1 / 3, 2 / 3); @@ -990,7 +995,7 @@ sub bipartiteLayoutImage { my $point1 = [ $i * $width + $shift[0], $high ]; my $point2 = [ $j * $width + $shift[1], $low ]; $plot->add_dataset($point1, $point2, color => 'black'); - if ($options{showWeights}) { + if ($graphOptions{showWeights}) { my $vector = [ $point2->[0] - $point1->[0], $point2->[1] - $point1->[1] ]; my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); $plot->add_label( @@ -1011,22 +1016,23 @@ sub wheelLayoutImage { die 'Wheel layout is not defined.' unless defined $self->{wheelLayout}; - $options{width} //= 250; - $options{height} //= $options{width}; - $options{showLabels} //= 1; + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; my $plot = main::Plot( - xmin => -1.5, - xmax => 1.5, - ymin => -1.5, - ymax => 1.5, - width => $options{width}, - height => $options{height}, - xlabel => '', - ylabel => '', - xvisible => 0, - yvisible => 0, - show_grid => 0 + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options ); my $gap = 2 * $main::PI / ($self->lastVertexIndex || 1); @@ -1038,7 +1044,7 @@ sub wheelLayoutImage { color => 'blue', h_align => 'center', v_align => 'middle' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; for my $i (0 .. $self->lastVertexIndex) { next if $i == $self->{wheelLayout}; @@ -1054,11 +1060,11 @@ sub wheelLayoutImage { color => 'blue', h_align => 'center', v_align => 'middle' - ) if $options{showLabels}; + ) if $graphOptions{showLabels}; if ($self->hasEdge($self->{wheelLayout}, $i)) { $plot->add_dataset([ 0, 0 ], $iVertex, color => 'black'); - if ($options{showWeights}) { + if ($graphOptions{showWeights}) { my $norm = sqrt($iVertex->[0]**2 + $iVertex->[1]**2); my @perp = ($iVertex->[1] / $norm, -$iVertex->[0] / $norm); $plot->add_label( @@ -1082,7 +1088,7 @@ sub wheelLayoutImage { my $jVertex = [ cos($jRel * $gap), sin($jRel * $gap) ]; $plot->add_dataset($iVertex, $jVertex, color => 'black'); - if ($options{showWeights}) { + if ($graphOptions{showWeights}) { my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); my $norm = sqrt($vector[0]**2 + $vector[1]**2); my @perp = ($vector[1] / $norm, -$vector[0] / $norm); @@ -2368,22 +2374,6 @@ =head2 image =over -=item width - -This is the width of the image. Default is 250. - -Note that the width option is not honored if the C is used. Note -that the width can still be set via the L C method, or -via the width option for the PGML image syntax. - -=item height - -This is the height of the image. Default is the value of the width option above. - -Note that the height option is not honored if the C is used. Note -that the height can still be set via the L C method, or -via the height option for the PGML image syntax. - =item showLabels If this is 1, then vertex labels will be shown. If this is 0, then vertex labels @@ -2401,8 +2391,33 @@ =head2 image C function using the row and column C<$size> argument (and hence are displayed using the grid layout) do work quite well for this. +=item width + +This is the width of the image. Default is 250 (except in the case that the +C is used, in which case the default is the product of 140 and one +less than the number of columns in the grid layout). + +Note that the width can also be set via the L C +method, or via the width option for the PGML image syntax. + +=item height + +This is the height of the image. Default is undefined. + +Note that the height can also be set via the L C +method, or via the height option for the PGML image syntax. + +=item aspect_ratio + +If this is set and the C option above is not set, then the height of the +image will be computed from the width using this aspect_ratio. Default is 1. + =back +Note that aside from the C and C options, all other +options (including any not listed above) that are provided are actually just +passed to the underlying C object. + =head2 gridLayoutImage $graph->gridLayoutImage(%options); @@ -2411,7 +2426,8 @@ =head2 gridLayoutImage method if the C property is set for the graph object. If this method is called directly and the C property is set, then it will still work. Otherwise an exception will be thrown. It accepts the same options as the -L method, but does not honor the C and C options. +L method. Note that the default value for the C option is the +product of 140 and one less than the number of columns in the grid layout. =head2 bipartiteLayoutImage From 6a4df5e670768133039b873bad212209e6634dd6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 30 Dec 2025 17:39:33 -0600 Subject: [PATCH 075/111] Clean up how the feedback reveal button works. Instead of automatically hiding the reveal button every time the feedback popover is opened after the reveal button has been used for the first time, completely remove the reveal button from the popover content, and remove the `d-none` class from the `div` containing the correct answer. Thus on subsequent loads the correct answer is shown cleanly. Currently you can see the reveal button for a brief instant before it is removed. --- htdocs/js/Feedback/feedback.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js index 5328334ec2..731dcb8c55 100644 --- a/htdocs/js/Feedback/feedback.js +++ b/htdocs/js/Feedback/feedback.js @@ -47,12 +47,17 @@ if (feedbackPopover.tip) feedbackPopover.tip.dataset.iframeHeight = '1'; const revealCorrectBtn = feedbackPopover.tip?.querySelector('.reveal-correct-btn'); - if (revealCorrectBtn && feedbackPopover.correctRevealed) { - revealCorrectBtn.nextElementSibling?.classList.remove('d-none'); - revealCorrectBtn.remove(); - } else { - revealCorrectBtn?.addEventListener('click', () => { - feedbackPopover.correctRevealed = true; + if (revealCorrectBtn) { + const fragment = new DocumentFragment(); + const container = document.createElement('div'); + container.innerHTML = feedbackBtn.dataset.bsContent; + const button = container.querySelector('.reveal-correct-btn'); + button?.nextElementSibling?.classList.remove('d-none'); + button?.remove(); + fragment.append(container); + const feedbackNoRevealBtn = fragment.firstElementChild.innerHTML; + + revealCorrectBtn.addEventListener('click', () => { revealCorrectBtn.classList.add('fade-out'); revealCorrectBtn.parentElement.classList.add('resize-transition'); revealCorrectBtn.parentElement.style.maxWidth = `${revealCorrectBtn.parentElement.offsetWidth}px`; @@ -65,6 +70,12 @@ revealCorrectBtn.remove(); feedbackPopover.update(); }); + + feedbackBtn.addEventListener( + 'hidden.bs.popover', + () => feedbackPopover.setContent({ '.popover-body': feedbackNoRevealBtn }), + { once: true } + ); }); } }); From b9d6c55a97b05022d1f279c519a9bcf8c4735185 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Fri, 26 Dec 2025 18:18:43 -0700 Subject: [PATCH 076/111] 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 34cba7fc06..e31f84a3b9 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 077/111] 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 e31f84a3b9..fce60022d8 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 078/111] 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 fce60022d8..7b05201f7f 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 079/111] 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 7b05201f7f..6fa51628ee 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 080/111] 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 6fa51628ee..4dcad380b5 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 081/111] 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 4dcad380b5..f744634602 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 082/111] 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 f744634602..cbe1f71501 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 a9c6b4605d..47a70e34ff 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 083/111] 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 cbe1f71501..add465be8c 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 084/111] 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 add465be8c..96b0cb455f 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 = []; From 2f096b5f8f7ed082235a95756723c675b5d0dcb7 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 10 Oct 2025 07:21:23 -0500 Subject: [PATCH 085/111] Improvements to the plots.pl macro. The first objective of this pull request is to make the output of the JSXGraph and TikZ formats for the plots.pl macro more consistent. The intent is that JSXGraph will be the primary display mode used in HTML, and TikZ the display mode in hardcopy, and the two should produce images that are a close as possible to the same. These changes are summarized as follows: * Switch the JSXGraph arrows used for curve end markers from type 4 to type 2, and tweak the TikZ arrows to match. The type 4 arrows look quite bad on curves in almost all cases. The problem is that the curve line goes all the way to the end of the arrow, and so with the really sharp arrow point of the type 4 arrow the curve can be seen sticking out past the end of the arrow. The type 2 arrows are a little better, although they still have some of the same problem. The JSXGraph documentation recommends using type 7 arrows for this reason, but those are just ugly. * Tweak the sizes of marks (or points) for the two formats. Note that the line width is now taken into account for points. TikZ does this by default for marks on a curve, but now also does this for all points even those that don't belong to a curve. JSXGraph also now takes this into account. * Open marks (`open_circle`, `open_square`, `open_triangle`, and `open_diamond`) are now implemented as the closed variant filled with white. That is how it was done already for JSXGraph, but it is now also done that way for TikZ. I have seen the same issue with TikZ that was seen with the JSXGraph format that led to doing this there. That is the curve poking into the open part of the mark. * The JSXGraph output now has ticks as well as the grid just like the pgfplots images. The tick sizes for both formats were tweaked for consistency. * The JSXGraph grid is now limited within the axes in the same way that the pgfplots grid is. Also, the JSXGraph format places the axes labels on the edges in the cases that pgfplots does so. Additiionally, the JSXGraph format no longer reserves additional space for an axis in the cases that the axis is not shown. This again is the same as what pgfplots does. * Vector/slope fields in tikz are now drawn 2 dimensionally (i.e., via addplot instead of addplot3) for pgfplots. In order to get the entire field the slope field at each level is drawn. This also means that the slope field now correctly honors the ysteps setting. Furthermore, using `addplot3` messes with the 2 dimensional axes. pgfplots automatically changes the way things are drawn as soon as `addplot3` is called. One obvious issue is that tick labels on the axes are not drawn when this is done. * The `linestyle => 'none'` setting now works differently. The previous approach was inconsistent between the two formats. It set `only marks` for TikZ, but didn't draw curves at all for JSXGraph. That means that marks would be drawn for TikZ, but nothing for JSXGraph. Also, if `fill => 'self'` was also set, then the fill would appear in TikZ, but still nothing in JSXGraph. So now `only marks` is not set for TikZ, and instead `draw=none` is set, and for JSXGraph the curve is drawn, but with `strokeWidth: 0`. So now if `linestyle => 'none'` is set, then no marks are shown (unless the `marks` option is also given) and that is consistent for both formats. If `fill => 'self'` is also set, then the fill appears for both formats, still with no curve drawn. The second objective is to add some new features and improve the way the macro works. Those changes are as follows: * Allow the grid to be shown without the axes. The `plots.pl` documentation does not indicate that hiding the axes also hides the grid, but it does. That is not desirable, and there are images in which one might want the grid without the axes. * Add axis `minor_grids` option. If this is 1 (the default), then grid lines are shown at minor tick locations, and otherwise they are not. This allows having minor ticks without associated grid lines which is sometimes desirable. The `minor` option still is the number of minor ticks (and minor grid lines if `minor_grids` is 1) and its documentation is updated to state this (instead of saying it is the number of minor grid lines even though it really was both). * Tick labels are now displayed with MathJax by default in the JSXGraph format. This can be disabled by setting the `mathajx_tick_labels` axes style option to 0. * The way the `add_label` method should be called is changed. Instead of `$plot->add_label($y, $y, label => $label, %options);` use `$plot->add_label($y, $y, $label, %options);` The first way will still work, but the second is how it should be done. The `$label` argument which is the text of the label and is an essential argument for a label, should not be an optional parameter. * Add a `rounded_corners` option to round the corners on images. This is a general option that is passed to the `Plot` method. To make this work well the `framed` TikZ package cannot be used anymore. Instead the pgfplots axes is drawn in a `savebox`. Then the axes dimensions can be obtained and used to fill the box (with the corners clipped if corners are rounded) before the save box is actually rendered, and then draw the boundary box (again with rounded corners if desired). * Add the new axes style option `axes_arrows_both` to have arrows in both directions on the axes. * Move the JSXGraph board setup into a JavaScript file. The macro just passes the options for the board to the JavaScript. This results in a much smaller footprint in the generated page HTML sent to the browser, particularly if multiple images are in one problem. In addition, more features can be added in the JavaScript without adding to that footprint (such as the code for displaying tick labels as fractions, mixed numbers, and scientific notation -- see below). The new JavaScript file and the `jsxgraphcore.js` file both have the `defer` attribute. The `jsxgraphcore.js` file should have been loaded deferred before. * There are no font sizes corresponding to all of the basic TeX font size declarations except `scriptsize` and `footnotesize`. So 'tiny', 'small', 'normalsize', 'large', 'Large', 'huge', and 'Huge' are the available font sizes. The `medium` and `giant` sizes from before are marked as deprecated, but still work. `normalsize` replaces `medium` and `Large` replaces `giant`. The reason that `scriptsize` and `footnotesize` were not included is because there isn't really room between `tiny` (8) and `small` (10) in the JSXGraph translation of sizes to put anything in between. I suppose one could be added at size 9, but you can barely see the difference, and at such small sizes I am not sure it matters. * Add an `add_point` method, and deprecate the `add_stamp` method. The points added by the `add_point` method are basically datasets consisting of a single point, but are drawn after everything else so that they appear on top. * Vector/slope fields are drawn in the order that the author adds them to the plot. Previously they were drawn after everything else which was just wrong. That meant that if a curve was added to the plot after a vector field it would be drawn behind the vector field (barring the use of a layer), and that really should not be the case. This is also needed in the code to ensure that points are drawn after everything else, and the reuse the existing dataset drawing code. * An invalid color name no longer causes the problem to fail to render. Furthermore, SVG color names can be used directly without being defined by the `add_color` method. See section 4.3 of the TeX xcolor package for a list of SVG color names (https://ctan.mirrors.hoobly.com/macros/latex/contrib/xcolor/xcolor.pdf). Those work for both TikZ and JSXGraph directly. * Add `layer` and `fill_layer` options. This allows fill regions to be drawn on the axis background layer, and is a much better approach than using the `axis_on_top` option. Using the `axis_on_top` option results in the axis being on top of all curves and function graphs, and generally looks bad. In addition, the `axis_on_top` option is not implemented at all for the JSXGraph format. By using layers the fill can be drawn on the background and the curve on the foreground. Note that the "standard" layer set for the TikZ format is now different than the pgfplots default. The "axis tick labels" is after the "pre main" and "main" layers. This is consistent with where JSXGraph places them, and is better than what pgplots does. Axis tick labels are textual elements that should be in front of the things that are drawn, together with the "axis descriptions". On the other hand, the JSXGraph axis layer is adjusted to match the pgfplot axis layer, which is above the axis tick layer. Further adjustments may be needed, but for now this gives a rather consistent match up. I decided to leave the general `layer` option exposing all layers (we discussed turning that into a `draw_on_background` option only). Instead I tweaked the pgfplots standard layer and the JSXGrpah default layers to make them more consistent. Also, I saw another use where another layer is best. That is for vector/slope fields. Those should be drawn on the `pre main` layer so that the arrows are in front of the grid and axis lines, but behind other curves and textual components such as the tick labels and axis labels. * The fill between fill regions are no longer deferred until after everything else is drawn. That causes unintended side effects. Particularly, it is inconsistent with how `fill => 'self'` is done. In that case the fill is done immediately. As a result if both a "self" fill and a "fill between" fill are used, then the "self" fill ends up behind the "fill between" fill regardless of the order the two are created. So this respects the order of creation which is the author's intended order. Note that to protect against this the names of datasets that have been created are tracked, and if an author attempts to fill between a dataset and another dataset that has not yet been created, then the fill is not created and a warning is issued. * The documented default for the `arrow_size` option was 10. That was the default for the TikZ format, but the actual JSXGraph default was 8. The two formats certainly cannot use different defaults. So now the default is 8 for both formats and documented as such. Furthermore, with the mark size tweaks mentioned earlier, that default (and other size settings) are similar for both formats. * Add tick_distance, tick_scale, and tick_scale_symbol options. The `tick_distance` and `tick_scale` options give more fine grained control over tick placement than the former `tick_delta` option. The `tick_delta` option is all but deprecated (but I did not say so). The `tick_delta` is the product of the `tick_distance` and the `tick_scale`. The point is that the `tick_distance`, `tick_scale`, and `tick_scale_symbol` can be used to do things such as having ticks at multiples of `pi` and labeled as such. For example, if `tick_distance => 1 / 4`, `tick_scale => pi`, and `tick_scale_symbol => '\pi'`, then the ticks will be labeled `0.25\pi`, `0.5\pi`, `0.75\pi`, `\pi`, etc., and of course these ticks will appear at those actual distances on the axis (the `tick_delta` will be `pi / 4`). * Add axis `tick_label_format` option. This can be one of "decimal", "fraction", "mixed", or "scinot" (default is "decimal"). It should be clear what those values mean. Note that this works well with the above options. So with the example for those options above and `tick_label_format => "fraction"`, the tick labels will be `\frac{1}{4}\pi`, `\frac{1}{2}\pi`, `\frac{3}{4}\pi`, `\pi`, etc. * Add `extra_js_code` and `extra_tikz_code` options. These can be used to add extra JavaScript or TikZ code to draw things that are not covered by the macro directly. These are advanced options that should be used with care, only by those that really know what they are doing, and always both options used together to keep the JSXGraph and TikZ output formats the same. * Fix a bug that prevented functions defined by Perl functions from appearing at all in the TikZ format. * Some issues with the `Plots::Data::function_string` method were fixed. First the absolute value was not working. The issue is the the absolute value in a MathObject does not stringify as the `abs` function. Something like `abs(x)` stringifies as `|x|`. The `function_string` parsing approach cannot handle something like that. To fix this a new `stringifyAbsAsFunction` context flag was added, and if that flag is set for the context the absolute value stringifies as `abs`. So `abs(x)` stringifies as `abs(x)`. In addition there are no JavaScript functions `Math.ln`, `Math.arcsosh`, or `Math.arctanh`. So those "tokens" were fixed with the correct JavaScript functions which are `Math.log` (which is the natural log), `Math.acosh`, and `Math.atanh`, respectively. Note that the `GD` image format (the `Plots::GD` package) for the plots macro has been removed. That format shouldn't be used anyway as it generates low quality graphics (at least in its current form). --- conf/pg_config.dist.yml | 2 +- htdocs/js/Plots/plots.js | 487 +++++++++++++++++++++++ htdocs/js/Plots/plots.scss | 6 +- lib/Parser/Context/Default.pm | 1 + lib/Parser/Function/numeric.pm | 2 + lib/Plots/Axes.pm | 164 ++++++-- lib/Plots/Data.pm | 22 +- lib/Plots/GD.pm | 362 ----------------- lib/Plots/JSXGraph.pm | 685 +++++++++++++++++--------------- lib/Plots/Plot.pm | 139 ++++--- lib/Plots/Tikz.pm | 700 +++++++++++++++++++++++---------- macros/graph/plots.pl | 221 ++++++++--- 12 files changed, 1722 insertions(+), 1069 deletions(-) create mode 100644 htdocs/js/Plots/plots.js delete mode 100644 lib/Plots/GD.pm diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml index 25295f05e4..4335415b49 100644 --- a/conf/pg_config.dist.yml +++ b/conf/pg_config.dist.yml @@ -236,7 +236,7 @@ modules: - [Multiple] - [PGrandom] - [Regression] - - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph', 'Plots::GD'] + - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph'] - [Select] - [Units] - [VectorField] diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js new file mode 100644 index 0000000000..df2624b035 --- /dev/null +++ b/htdocs/js/Plots/plots.js @@ -0,0 +1,487 @@ +/* global JXG */ + +'use strict'; + +const PGplots = { + async plot(boardContainerId, plotContents, options) { + const drawBoard = (id) => { + const boundingBox = options.board?.boundingBox ?? [-5, 5, 5, -5]; + + // Disable highlighting for all elements. + JXG.Options.elements.highlight = false; + + // Adjust layers to match standard TikZ layers. The "axis" is bumped up a layer so it is above "axis + // ticks". The rest are on layer 3 by default, so they are moved up to main layer. The remaining layer + // settings should be okay for now. + JXG.Options.layer.axis = 3; + JXG.Options.layer.polygon = 5; + JXG.Options.layer.sector = 5; + JXG.Options.layer.angle = 5; + JXG.Options.layer.integral = 5; + + const board = JXG.JSXGraph.initBoard( + id, + JXG.merge( + { + title: options.board?.title ?? 'Graph', + boundingBox, + showCopyright: false, + axis: false, + drag: { enabled: false }, + showNavigation: options.board?.showNavigation ?? false, + pan: { enabled: options.board?.showNavigation ?? false }, + zoom: { enabled: options.board?.showNavigation ?? false } + }, + options.board?.overrideOptions ?? {} + ) + ); + + // The board now has its own clone of the options with the custom settings above which will apply for + // anything created on the board. So reset the JSXGraph defaults so that other JSXGraph images on the page + // don't get these settings. + JXG.Options.elements.highlight = true; + JXG.Options.layer.axis = 2; + JXG.Options.layer.polygon = 3; + JXG.Options.layer.sector = 3; + JXG.Options.layer.angle = 3; + JXG.Options.layer.integral = 3; + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${id}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Generated graph'; + board.containerObj.after(descriptionSpan); + board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + + // Convert a decimal number into a fraction or mixed number. This is basically the JXG.toFraction method + // except that the "mixed" parameter is added, and it returns an improper fraction if mixed is false. + const toFraction = (x, useTeX, mixed, order) => { + const arr = JXG.Math.decToFraction(x, order); + + if (arr[1] === 0 && arr[2] === 0) { + return '0'; + } else { + let str = ''; + // Sign + if (arr[0] < 0) str += '-'; + if (arr[2] === 0) { + // Integer + str += arr[1]; + } else if (!(arr[2] === 1 && arr[3] === 1)) { + // Proper fraction + if (mixed) { + if (arr[1] !== 0) str += arr[1] + ' '; + if (useTeX) str += `\\frac{${arr[2]}}{${arr[3]}}`; + else str += `${arr[2]}/${arr[3]}`; + } else { + if (useTeX) str += `\\frac{${arr[3] * arr[1] + arr[2]}}{${arr[3]}}`; + else str += `${arr[3] * arr[1] + arr[2]}/${arr[3]}`; + } + } + return str; + } + }; + + // Override the default axis generateLabelText method so that 0 is displayed + // using MathJax if the axis is configured to show tick labels using MathJax. + const generateLabelText = function (tick, zero, value) { + if (JXG.exists(value)) return this.formatLabelText(value); + const distance = this.getDistanceFromZero(zero, tick); + return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale); + }; + + const trimTrailingZeros = (value) => { + if (value.indexOf('.') > -1 && value.endsWith('0')) { + value = value.replace(/0+$/, ''); + // Remove the decimal if it is now at the end. + value = value.replace(/\.$/, ''); + } + return value; + }; + + // Override the formatLabelText method for the axes ticks so that + // better number formats can be used for tick labels. + const formatLabelText = function (value) { + let labelText; + + if (JXG.isNumber(value)) { + if (this.visProp.label.format === 'fraction' || this.visProp.label.format === 'mixed') { + labelText = toFraction( + value, + this.visProp.label.usemathjax, + this.visProp.label.format === 'mixed' + ); + } else if (this.visProp.label.format === 'scinot') { + const [mantissa, exponent] = value.toExponential(this.visProp.digits).toString().split('e'); + labelText = this.visProp.label.usemathjax + ? `${trimTrailingZeros(mantissa)}\\cdot 10^{${exponent}}` + : `${trimTrailingZeros(mantissa)} x 10^${exponent}`; + } else { + labelText = trimTrailingZeros(value.toFixed(this.visProp.digits).toString()); + } + } else { + labelText = value.toString(); + } + + if (this.visProp.scalesymbol.length > 0) { + if (labelText === '1') labelText = this.visProp.scalesymbol; + else if (labelText === '-1') labelText = `-${this.visProp.scalesymbol}`; + else if (labelText !== '0') labelText = labelText + this.visProp.scalesymbol; + } + + return this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + }; + + board.suspendUpdate(); + + // This axis provides the vertical grid lines. + if (options.grid?.x) { + board.create( + 'axis', + [ + [options.xAxis?.min ?? -5, options.xAxis?.position ?? 0], + [options.xAxis?.max ?? 5, options.xAxis?.position ?? 0] + ], + JXG.merge( + { + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis?.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.xAxis?.ticks?.distance ?? 2, + scale: options.xAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.x.minorGrids ? (options.xAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ] + }, + withLabel: false + }, + options.grid.x.overrideOptions ?? {} + ) + ); + } + + // This axis provides the horizontal grid lines. + if (options.grid?.y) { + board.create( + 'axis', + [ + [options.yAxis?.position ?? 0, options.yAxis?.min ?? -5], + [options.yAxis?.position ?? 0, options.yAxis?.max ?? -5] + ], + JXG.merge( + { + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis?.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.yAxis?.ticks?.distance ?? 2, + scale: options.yAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.y.minorGrids ? (options.yAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ] + }, + withLabel: 0 + }, + options.grid.y.overrideOptions ?? {} + ) + ); + } + + if (options.xAxis?.visible) { + const xAxis = board.create( + 'axis', + [ + [options.xAxis.min ?? -5, options.xAxis.position ?? 0], + [options.xAxis.max ?? 5, options.xAxis.position ?? 0] + ], + JXG.merge( + { + name: options.xAxis.name ?? '\\(x\\)', + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.axesArrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.xAxis.location === 'middle' ? true : false, + label: { + anchorX: 'right', + anchorY: 'middle', + highlight: false, + offset: [-5, -3], + position: '100% left', + useMathJax: true + }, + ticks: { + drawLabels: options.xAxis.ticks?.labels && options.xAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.yAxis?.visible || + (options.yAxis.location === 'center' && (options.yAxis.position ?? 0) != 0) || + ((options.yAxis.location === 'left' || options.yAxis.location === 'box') && + (options.yAxis.min ?? -5) != 0) || + (options.yAxis.location === 'right' && (options.yAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.xAxis.ticks?.distance ?? 2, + scale: options.xAxis.ticks?.scale ?? 1, + scaleSymbol: options.xAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.xAxis.ticks?.minorTicks ?? 3, + majorHeight: options.xAxis.ticks?.show ? 8 : 0, + minorHeight: options.xAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + tickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + digits: options.xAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'bottom' : 'top', + offset: options.xAxis.location === 'top' ? [0, 4] : [0, -4], + highlight: 0, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.xAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.xAxis.overrideOptions ?? {} + ) + ); + xAxis.defaultTicks.generateLabelText = generateLabelText; + xAxis.defaultTicks.formatLabelText = formatLabelText; + + if (options.xAxis.location !== 'middle') { + board.create( + 'text', + [ + (xAxis.point1.X() + xAxis.point2.X()) / 2, + options.xAxis.location === 'top' ? board.getBoundingBox()[1] : board.getBoundingBox()[3], + options.xAxis.name ?? '\\(x\\)' + ], + { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'top' : 'bottom', + highlight: false, + color: 'black', + fixed: true, + useMathJax: true + } + ); + } + } + + if (options.yAxis?.visible) { + const yAxis = board.create( + 'axis', + [ + [options.yAxis.position ?? 0, options.yAxis.min ?? -5], + [options.yAxis.position ?? 0, options.yAxis.max ?? -5] + ], + JXG.merge( + { + name: options.yAxis.name ?? '\\(y\\)', + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.axesArrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.yAxis.location === 'center' ? true : false, + label: { + anchorX: 'middle', + anchorY: 'top', + highlight: false, + distance: 1, + offset: [5, 1], + position: '100% right', + useMathJax: true + }, + ticks: { + drawLabels: options.yAxis.ticks?.labels && options.yAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.xAxis?.visible || + (options.xAxis.location === 'middle' && (options.xAxis.position ?? 0) != 0) || + ((options.xAxis.location === 'bottom' || options.xAxis.location === 'box') && + (options.xAxis.min ?? -5) != 0) || + (options.xAxis.location === 'top' && (options.xAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.yAxis.ticks?.distance ?? 2, + scale: options.yAxis.ticks?.scale ?? 1, + scaleSymbol: options.yAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.yAxis.ticks?.minorTicks ?? 3, + majorHeight: options.yAxis.ticks?.show ? 8 : 0, + minorHeight: options.yAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + tickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + digits: options.yAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: options.yAxis.location === 'right' ? 'left' : 'right', + anchorY: 'middle', + offset: options.yAxis.location === 'right' ? [6, 0] : [-6, 0], + highlight: false, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.yAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.yAxis.overrideOptions ?? {} + ) + ); + yAxis.defaultTicks.generateLabelText = generateLabelText; + yAxis.defaultTicks.formatLabelText = formatLabelText; + + if (options.yAxis.location !== 'center') { + board.create( + 'text', + [ + options.yAxis.location === 'right' ? boundingBox[2] : boundingBox[0], + (yAxis.point1.Y() + yAxis.point2.Y()) / 2, + options.yAxis.name ?? '\\(y\\)' + ], + { + anchorX: 'middle', + anchorY: options.yAxis.location === 'right' ? 'bottom' : 'top', + rotate: 90, + highlight: 0, + color: 'black', + fixed: 1, + useMathJax: 1 + } + ); + } + } + + plotContents(board); + + board.unsuspendUpdate(); + + return board; + }; + + const container = document.getElementById(boardContainerId); + if (!container) return; + + const drawPromise = (id) => + new Promise((resolve) => { + if (container.offsetWidth === 0) { + setTimeout(async () => resolve(await drawPromise(id)), 100); + return; + } + resolve(drawBoard(id)); + }); + + await drawPromise(boardContainerId); + + let jsxBoard = null; + container.addEventListener('shown.imageview', async () => { + document + .getElementById(`magnified-${boardContainerId}`) + ?.classList.add(...Array.from(container.classList).filter((c) => c !== 'image-view-elt')); + jsxBoard = await drawPromise(`magnified-${boardContainerId}`); + }); + container.addEventListener('resized.imageview', () => { + jsxBoard?.resizeContainer(jsxBoard.containerObj.clientWidth, jsxBoard.containerObj.clientHeight, true); + }); + container.addEventListener('hidden.imageview', () => { + if (jsxBoard) JXG.JSXGraph.freeBoard(jsxBoard); + jsxBoard = null; + }); + } +}; diff --git a/htdocs/js/Plots/plots.scss b/htdocs/js/Plots/plots.scss index 19c3586878..116a1b3a76 100644 --- a/htdocs/js/Plots/plots.scss +++ b/htdocs/js/Plots/plots.scss @@ -1,4 +1,8 @@ .plots-jsxgraph { display: inline-block; - border-radius: 0px; + vertical-align: middle; + + &:not(.plots-jsxgraph-rounded) { + border-radius: 0px; + } } diff --git a/lib/Parser/Context/Default.pm b/lib/Parser/Context/Default.pm index c182fdcd9c..d66af413ba 100644 --- a/lib/Parser/Context/Default.pm +++ b/lib/Parser/Context/Default.pm @@ -435,6 +435,7 @@ $flags = { reduceConstantFunctions => 1, # 1 = compute function values of constants showExtraParens => 1, # 1 = add useful parens, 2 = make things painfully unambiguous stringifyNoBrackets => 0, # 1 = only use parentheses not brackets when stringifying + stringifyAbsAsFunction => 0, # 1 = abs(x) is stringified as abs(x) instead of |x| formatStudentAnswer => 'evaluated', # or 'parsed' or 'reduced' allowMissingOperands => 0, # 1 is used by Typeset context allowMissingFunctionInputs => 0, # 1 is used by Typeset context diff --git a/lib/Parser/Function/numeric.pm b/lib/Parser/Function/numeric.pm index d9540e4198..79edcc75a3 100644 --- a/lib/Parser/Function/numeric.pm +++ b/lib/Parser/Function/numeric.pm @@ -82,6 +82,8 @@ $Parser::reduce->{'ln(e^x)'} = 1; # sub string { my $self = shift; + return 'abs(' . $self->{params}[0]->string . ')' + if $self->{name} eq 'abs' && $self->context->flag('stringifyAbsAsFunction'); return '|' . $self->{params}[0]->string . '|' if $self->{name} eq 'abs'; return $self->SUPER::string(@_); } diff --git a/lib/Plots/Axes.pm b/lib/Plots/Axes.pm index f1da7cb454..9f9f094b5c 100644 --- a/lib/Plots/Axes.pm +++ b/lib/Plots/Axes.pm @@ -87,19 +87,52 @@ The maximum value the axis shows. Default is 5. This is the number of major tick marks to include on the axis. This number is used to compute the C as the difference between the C and C values -and the number of ticks. Default: 5. +and the number of ticks. Note that this is only used if C is zero +and C is undefined. Default: 5. =item tick_delta -This is the distance between each major tick mark, starting from the origin. -If this is set to 0, this distance is set by using the number of ticks, C. -Default is 0. +This is the distance between each major tick mark, starting from the origin. If +this is set to 0 and C is not 0, then this distance is computed +to be the product of the C and the C, and if this is +set to 0 and C is undefined then this is computed to be the +difference between the C and C divided by the C. Default: 0 =item tick_labels This can be either 1 (show) or 0 (don't show) the labels for the major ticks. Default: 1 +=item tick_label_format + +This can be one of "decimal", "fraction", "multiple", or "scinot". If this is +"decimal", then tick labels will be displayed in decimal format. If this is +"fraction", then tick labels will be displayed as (improper) fractions. If this +is "mixed", then tick labels will be displayed as mixed numbers. If this is +"scinot", then tick labels will be displayed in scientific notation. Default: +"decimal" + +=item tick_label_digits + +The number of decimal places to round tick labels to when the +C is "decimal" or "scinot". Default: 2 + +=item tick_distance + +This is the unscaled distance between each major tick mark starting from the +origin when the axis is scaled by the C factor. If this is 0, then +this will be computed to be the C divided by the C. +Default: 0 + +=item tick_scale + +This is used in combination with the C above to calculate the +C. Default: 1 + +=item tick_scale_symbol + +This is appended to major tick labels. Default: '' + =item show_ticks This can be either 1 (show) or 0 (don't show) the tick lines. If ticks are @@ -115,8 +148,13 @@ Show (1) or don't show (0) grid lines at the tick marks. Default is 1. =item minor -This sets the number of minor grid lines per major grid line. If this is -set to 0, no minor grid lines are shown. Default is 3. +This sets the number of minor ticks (and minor grid lines if minor_grids is 1) +per major tick. If this is set to 0, no minor ticks are shown. Default: 3 + +=item minor_grids + +If this is 1, then grid lines are shown at minor ticks, and if this is 0, then +grid lines are not shown at minor ticks. Default: 1 =item visible @@ -141,7 +179,13 @@ set to 'middle' or 'center'. Default is 0. =item jsx_options -A hash reference of options to be passed to the JSXGraph axis objects. +A hash reference of options to be passed to the JSXGraph axis object. + +=item jsx_grid_options + +A hash reference of options to be passed to the JSXGraph grid object. Note that +the grid is implemented as an axis with ticks the extend to infinity. So the +options are really JSXGraph axis options. =back @@ -185,6 +229,29 @@ Configures if the Tikz axis should be drawn on top of the graph (1) or below the Useful when filling a region that covers an axis, if the axis are on top they will still be visible after the fill, otherwise the fill will cover the axis. Default: 0 +Note that this setting is not honored for the JSXGraph image type. + +This is not the best way of ensuring that axis elements are not covered by a +fill. If this is used, then not only is the fill region placed behind the axis +and the grid, but all graphed elements are behind the axis and the grid which is +usually not desirable. A better way is to use the "axis background" C to +only place the fill on the "axis background" layer, and leave everything else on +top of the axis. + +=item axes_arrows_both + +Configures if arrows should be drawn in both directions (1) or only in the +positive direction (0) at the axes ends. In other words, this is a choice +between the convention that arrows are meant to indicate that the axes lines +continue forever, or the convention that arrows are meant to indicate the +positive direction of the axes only. Default: 0 + +=item mathjax_tick_labels + +If this is 1, then tick labels will be displayed using MathJax. If this is 0, +then ticks will be displayed as basic text. This only applies to the JSXGraph +output type. Default: 1 + =item jsx_navigation Either allow (1) or don't allow (0) the user to pan and zoom the view port of the @@ -196,6 +263,13 @@ of the graph that can be zoomed in or out. Default: 0 A hash reference of options to be passed to the JSXGraph board object. +=item tikz_options + +Additional options to be passed to the pgfplots axis definition. This should be +a single string. For example, to make longer and thicker x axis ticks use + + tikz_options => 'x tick style={line width=2pt},major tick length=0.6cm' + =back =cut @@ -206,18 +280,21 @@ use strict; use warnings; sub new { - my $class = shift; - my $self = bless { + my ($class, @options) = @_; + my $self = bless { xaxis => {}, yaxis => {}, styles => { - aria_label => 'Graph', - aria_description => 'Generated graph', - grid_color => 'gray', - grid_alpha => 40, - show_grid => 1, + aria_label => 'Graph', + aria_description => 'Generated graph', + grid_color => 'gray', + grid_alpha => 40, + show_grid => 1, + axis_on_top => 0, + axes_arrows_both => 0, + mathjax_tick_labels => 1, }, - @_ + @options }, $class; $self->xaxis($self->axis_defaults('x')); @@ -228,18 +305,24 @@ sub new { sub axis_defaults { my ($self, $axis) = @_; return ( - visible => 1, - min => -5, - max => 5, - label => $axis eq 'y' ? '\(y\)' : '\(x\)', - location => $axis eq 'y' ? 'center' : 'middle', - position => 0, - tick_labels => 1, - show_ticks => 1, - tick_delta => 0, - tick_num => 5, - major => 1, - minor => 3, + visible => 1, + min => -5, + max => 5, + label => $axis eq 'y' ? '\(y\)' : '\(x\)', + location => $axis eq 'y' ? 'center' : 'middle', + position => 0, + tick_labels => 1, + tick_label_format => 'decimal', + tick_label_digits => 2, + tick_distance => 0, + tick_scale => 1, + tick_scale_symbol => '', + show_ticks => 1, + tick_delta => 0, + tick_num => 5, + major => 1, + minor => 3, + minor_grids => 1 ); } @@ -256,8 +339,11 @@ sub axis { map { $self->{$axis}{$_} = $item->{$_}; } (keys %$item); return; } - # Deal with ticks individually since they may need to be generated. - return $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) : $self->{$axis}{$item}; + # Deal with the tick_delta and tick_distance individually since they may need to be computed. + return + $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) + : $item eq 'tick_distance' ? $self->tick_distance($self->{$axis}) + : $self->{$axis}{$item}; } sub xaxis { @@ -322,14 +408,28 @@ sub style { sub tick_delta { my ($self, $axis) = @_; return $axis->{tick_delta} if $axis->{tick_delta}; - return 2 unless $axis->{tick_num}; - $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num} if $axis->{tick_num}; + if ($axis->{tick_distance}) { + $axis->{tick_delta} = $axis->{tick_distance} * ($axis->{tick_scale} || 1); + } elsif ($axis->{tick_num}) { + $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num}; + } else { + $axis->{tick_delta} = 2; + } return $axis->{tick_delta}; } +sub tick_distance { + my ($self, $axis) = @_; + return $axis->{tick_distance} if $axis->{tick_distance}; + my $tick_delta = $self->tick_delta($axis); + $axis->{tick_distance} = $axis->{tick_delta} / ($axis->{tick_scale} || 1); + return $axis->{tick_distance}; +} + sub grid { my $self = shift; - return $self->get('xmajor', 'xminor', 'xtick_delta', 'ymajor', 'yminor', 'ytick_delta'); + return $self->get('xmajor', 'xminor_grids', 'xminor', 'xtick_delta', 'ymajor', 'yminor_grids', 'yminor', + 'ytick_delta'); } sub bounds { diff --git a/lib/Plots/Data.pm b/lib/Plots/Data.pm index 35c10f7f24..cdd1840d56 100644 --- a/lib/Plots/Data.pm +++ b/lib/Plots/Data.pm @@ -217,13 +217,11 @@ sub set_function { }; for my $key ('Fx', 'Fy', 'xvar', 'yvar', 'xmin', 'xmax', 'ymin', 'ymax', 'xsteps', 'ysteps') { next unless defined $options{$key}; - $f->{$key} = $options{$key}; - delete $options{$key}; + $f->{$key} = delete $options{$key}; } for my $key ('var', 'min', 'max', 'steps') { next unless defined $options{$key}; - $f->{"x$key"} = $options{$key}; - delete $options{$key}; + $f->{"x$key"} = delete $options{$key}; } return unless $f->{Fy} ne ''; @@ -257,13 +255,15 @@ sub function_string { # Ensure -x^2 gets print as -(x^2), since JavaScript finds this ambiguous. my $extraParens = $formula->context->flag('showExtraParens'); - my $format = $formula->context->{format}{number}; - $formula->context->flags->set(showExtraParens => 2); + # Ensure that abs(x) is stringified as abs(x) instead of |x|. + my $stringifyAbsAsFunction = $formula->context->flag('stringifyAbsAsFunction'); + my $format = $formula->context->{format}{number}; + $formula->context->flags->set(showExtraParens => 2, stringifyAbsAsFunction => 1); $formula->context->{format}{number} = "%f#"; # Get no bracket string for $formula my $func = $formula . ""; $func =~ s/\s//g; - $formula->context->flags->set(showExtraParens => $extraParens); + $formula->context->flags->set(showExtraParens => $extraParens, stringifyAbsAsFunction => $stringifyAbsAsFunction); $formula->context->{format}{number} = $format; my %tokens; @@ -318,8 +318,8 @@ sub function_string { ceil => 'Math.ceil', sign => 'Math.sign', int => 'Math.trunc', - log => 'Math.ln', - ln => 'Math.ln', + log => 'Math.log', + ln => 'Math.log', cos => 'Math.cos', sin => 'Math.sin', tan => 'Math.tan', @@ -334,11 +334,11 @@ sub function_string { sinh => 'Math.sinh', tanh => 'Math.tanh', acosh => 'Math.acosh', - arccosh => 'Math.arccosh', + arccosh => 'Math.acosh', asinh => 'Math.asinh', arcsinh => 'Math.asinh', atanh => 'Math.atanh', - arctanh => 'Math.arctanh', + arctanh => 'Math.atanh', min => 'Math.min', max => 'Math.max', random => 'Math.random', diff --git a/lib/Plots/GD.pm b/lib/Plots/GD.pm deleted file mode 100644 index 800374a002..0000000000 --- a/lib/Plots/GD.pm +++ /dev/null @@ -1,362 +0,0 @@ - -=head1 DESCRIPTION - -This is the code that takes a C and creates the GD code for generation. - -See L for more details. - -=cut - -package Plots::GD; - -use GD; - -use strict; -use warnings; - -sub new { - my ($class, $plots) = @_; - return bless { - image => '', - plots => $plots, - position => [ 0, 0 ], - colors => {}, - image => GD::Image->new($plots->size) - }, $class; -} - -sub plots { - my $self = shift; - return $self->{plots}; -} - -sub im { - my $self = shift; - return $self->{image}; -} - -sub position { - my ($self, $x, $y) = @_; - return wantarray ? @{ $self->{position} } : $self->{position} unless (defined($x) && defined($y)); - $self->{position} = [ $x, $y ]; - return; -} - -sub color { - my ($self, $color) = @_; - $self->{colors}{$color} = $self->im->colorAllocate(@{ $self->plots->colors($color) }) - unless $self->{colors}{$color}; - return $self->{colors}{$color}; -} - -# Translate x and y coordinates to pixels on the graph. -sub im_x { - my ($self, $x) = @_; - return unless defined($x); - my $plots = $self->plots; - my ($xmin, $xmax) = ($plots->axes->xaxis('min'), $plots->axes->xaxis('max')); - return int(($x - $xmin) * $plots->{width} / ($xmax - $xmin)); -} - -sub im_y { - my ($self, $y) = @_; - return unless defined($y); - my $plots = $self->plots; - my ($ymin, $ymax) = ($plots->axes->yaxis('min'), $plots->axes->yaxis('max')); - (undef, my $height) = $plots->size; - return int(($ymax - $y) * $height / ($ymax - $ymin)); -} - -sub moveTo { - my ($self, $x, $y) = @_; - $x = $self->im_x($x); - $y = $self->im_y($y); - $self->position($x, $y); - return; -} - -sub lineTo { - my ($self, $x, $y, $color, $width, $dashed) = @_; - $color = 'default_color' unless defined($color); - $color = $self->color($color); - $width = 1 unless defined($width); - $dashed = 0 unless defined($dashed); - $x = $self->im_x($x); - $y = $self->im_y($y); - - $self->im->setThickness($width); - if ($dashed =~ /dash/) { - my @dashing = ($color) x (4 * $width * $width); - my @spacing = (GD::gdTransparent) x (3 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } elsif ($dashed =~ /dot/) { - my @dashing = ($color) x (1 * $width * $width); - my @spacing = (GD::gdTransparent) x (2 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } else { - $self->im->line($self->position, $x, $y, $color); - } - $self->im->setThickness(1); - $self->position($x, $y); - return; -} - -# Draw functions / lines / arrows -sub draw_data { - my ($self, $pass) = @_; - my $plots = $self->plots; - $pass = 0 unless $pass; - for my $data ($plots->data('function', 'dataset')) { - $data->gen_data; - my $n = $data->size - 1; - my $x = $data->x; - my $y = $data->y; - my $color = $data->style('color'); - my $width = $data->style('width'); - $self->moveTo($x->[0], $y->[0]); - for (1 .. $n) { - $self->lineTo($x->[$_], $y->[$_], $color, $width, $data->style('linestyle')); - } - - if ($pass == 2) { - my $r = int(3 + $width); - my $start = $data->style('start_mark') || 'none'; - if ($start eq 'circle' || $start eq 'closed_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color, 1); - } elsif ($start eq 'open_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color); - } elsif ($start eq 'arrow') { - $self->draw_arrow_head($data->x(1), $data->y(1), $data->x(0), $data->y(0), $color, $width); - } - - my $end = $data->style('end_mark') || 'none'; - if ($end eq 'circle' || $end eq 'closed_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color, 1); - } elsif ($end eq 'open_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color); - } elsif ($end eq 'arrow') { - $self->draw_arrow_head($data->x($n - 1), $data->y($n - 1), $data->x($n), $data->y($n), $color, $width); - } - } - } - return; -} - -# Label helpers -sub get_gd_font { - my ($self, $font) = @_; - if ($font eq 'tiny') { return GD::gdTinyFont; } - elsif ($font eq 'small') { return GD::gdSmallFont; } - elsif ($font eq 'large') { return GD::gdLargeFont; } - elsif ($font eq 'giant') { return GD::gdGiantFont; } - return GD::gdMediumBoldFont; -} - -sub label_offset { - my ($self, $loc, $str, $fontsize) = @_; - my $offset = 0; - # Add an additional 2px offset for the edges 'right', 'bottom', 'left', and 'top'. - if ($loc eq 'right') { $offset -= length($str) * $fontsize + 2; } - elsif ($loc eq 'bottom') { $offset -= $fontsize + 2; } - elsif ($loc eq 'center') { $offset -= length($str) * $fontsize / 2; } - elsif ($loc eq 'middle') { $offset -= $fontsize / 2; } - else { $offset = 2; } # Both 'left' and 'top'. - return $offset; -} - -sub draw_label { - my ($self, $str, $x, $y, %options) = @_; - my $font = $self->get_gd_font($options{fontsize} || 'medium'); - my $color = $self->color($options{color} || 'default_color'); - my $xoff = $self->label_offset($options{h_align} || 'center', $str, $font->width); - my $yoff = $self->label_offset($options{v_align} || 'middle', $str, $font->height); - - if ($options{orientation} && $options{orientation} eq 'vertical') { - $self->im->stringUp($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } else { - $self->im->string($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } - return; -} - -sub draw_arrow_head { - my ($self, $x1, $y1, $x2, $y2, $color, $w) = @_; - return unless @_ > 4; - $color = $self->color($color || 'default_color'); - $w = 1 unless $w; - ($x1, $y1) = ($self->im_x($x1), $self->im_y($y1)); - ($x2, $y2) = ($self->im_x($x2), $self->im_y($y2)); - - my $dx = $x2 - $x1; - my $dy = $y2 - $y1; - my $len = sqrt($dx * $dx + $dy * $dy); - my $ux = $dx / $len; # Unit vector in direction of arrow. - my $uy = $dy / $len; - my $px = -1 * $uy; # Unit vector perpendicular to arrow. - my $py = $ux; - my $hbx = $x2 - 7 * $w * $ux; - my $hby = $y2 - 7 * $w * $uy; - my $head = GD::Polygon->new; - $head->addPt($x2, $y2); - $head->addPt($hbx + 3 * $w * $px, $hby + 3 * $w * $py); - $head->addPt($hbx - 3 * $w * $px, $hby - 3 * $w * $py); - $self->im->setThickness($w); - $self->im->filledPolygon($head, $color); - $self->im->setThickness(1); - return; -} - -sub draw_circle_stamp { - my ($self, $x, $y, $r, $color, $filled) = @_; - my $d = $r ? 2 * $r : 8; - $color = $self->color($color || 'default_color'); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $self->color('white')); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $color, $filled ? () : GD::gdNoFill); - return; -} - -sub draw { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $grid = $axes->grid; - my ($width, $height) = $plots->size; - - # Initialize image - $self->im->interlaced('true'); - $self->im->fill(1, 1, $self->color('white')); - - # Plot data first, then fill in regions before adding axes, grid, etc. - $self->draw_data(1); - - # Fill regions - for my $region ($plots->data('fill_region')) { - $self->im->fill($self->im_x($region->x(0)), $self->im_y($region->y(0)), $self->color($region->style('color'))); - } - - # Gridlines - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - my $grid_color = $axes->style('grid_color'); - my $grid_style = $axes->style('grid_style'); - my $show_grid = $axes->style('show_grid'); - if ($show_grid && $grid->{xmajor}) { - my $xminor = $grid->{xminor} || 0; - my $dx = $grid->{xtick_delta} || 1; - my $x = (int($xmax / $dx) + 1) * $dx; - my $end = (int($xmin / $dx) - 1) * $dx; - while ($x >= $end) { - $self->moveTo($x, $ymin); - $self->lineTo($x, $ymax, $grid_color, 0.5, 1); - for (0 .. $xminor) { - my $tmp_x = $x + $_ * $dx / ($xminor + 1); - $self->moveTo($tmp_x, $ymin); - $self->lineTo($tmp_x, $ymax, $grid_color, 0.5, 1); - } - $x -= $dx; - } - } - if ($show_grid && $grid->{ymajor}) { - my $yminor = $grid->{yminor} || 0; - my $dy = $grid->{ytick_delta} || 1; - my $y = (int($ymax / $dy) + 1) * $dy; - my $end = (int($ymin / $dy) - 1) * $dy; - while ($y >= $end) { - $self->moveTo($xmin, $y); - $self->lineTo($xmax, $y, $grid_color, 0.5, 1); - for (0 .. $yminor) { - my $tmp_y = $y + $_ * $dy / ($yminor + 1); - $self->moveTo($xmin, $tmp_y); - $self->lineTo($xmax, $tmp_y, $grid_color, 0.5, 1); - } - $y -= $dy; - } - } - - # Plot axes - my $xloc = $axes->xaxis('location') || 'middle'; - my $yloc = $axes->yaxis('location') || 'center'; - my $xpos = ($yloc eq 'box' || $yloc eq 'left') ? $xmin : $yloc eq 'right' ? $xmax : $axes->yaxis('position'); - my $ypos = ($xloc eq 'box' || $xloc eq 'bottom') ? $ymin : $xloc eq 'top' ? $ymax : $axes->xaxis('position'); - $xpos = $xmin if $xpos < $xmin; - $xpos = $xmax if $xpos > $xmax; - $ypos = $ymin if $ypos < $ymin; - $ypos = $ymax if $ypos > $ymax; - - if ($axes->xaxis('visible')) { - my $xlabel = $axes->xaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_y($ymin) - $self->im_y($ypos) < 5) ? 'bottom' : 'top'; - my $label_align = ($self->im_y($ypos) - $self->im_y($ymax) < 5) ? 'top' : 'bottom'; - my $label_loc = $yloc eq 'right' && ($xloc eq 'top' || $xloc eq 'bottom') ? $xmin : $xmax; - - $self->moveTo($xmin, $ypos); - $self->lineTo($xmax, $ypos, 'black', 1.5, 0); - $self->draw_label( - $xlabel, $label_loc, $ypos, - fontsize => 'large', - v_align => $label_align, - h_align => $label_loc == $xmin ? 'left' : 'right' - ); - my $dx = $grid->{xtick_delta} || 1; - my $x = int($xmax / $dx) * $dx; - my $end = int($xmin / $dx) * $dx; - - while ($x >= $end) { - $self->draw_label($x, $x, $ypos, font => 'large', v_align => $tick_align, h_align => 'center') - unless $x == $xpos && $axes->yaxis('visible'); - $x -= $dx; - } - } - if ($axes->yaxis('visible')) { - my $ylabel = $axes->yaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_x($xpos) - $self->im_x($xmin) < 5) ? 'left' : 'right'; - my $label_align = ($self->im_x($xmax) - $self->im_x($xpos) < 5) ? 'right' : 'left'; - my $label_loc = ($yloc eq 'left' && $xloc eq 'top') || ($yloc eq 'right' && $xloc eq 'top') ? $ymin : $ymax; - - $self->moveTo($xpos, $ymin); - $self->lineTo($xpos, $ymax, 'black', 1.5, 0); - $self->draw_label( - $ylabel, $xpos, $label_loc, - fontsize => 'large', - v_align => $label_loc == $ymin ? 'bottom' : 'top', - h_align => $label_align - ); - - my $dy = $grid->{ytick_delta} || 1; - my $y = int($ymax / $dy) * $dy; - my $end = int($ymin / $dy) * $dy; - while ($y >= $end) { - $self->draw_label($y, $xpos, $y, font => 'large', v_align => 'middle', h_align => $tick_align) - unless $y == $ypos && $axes->xaxis('visible'); - $y -= $dy; - } - } - - # Draw data a second time to cleanup any issues with the grid and axes. - $self->draw_data(2); - - # Print Labels - for my $label ($plots->data('label')) { - $self->draw_label($label->style('label'), $label->x(0), $label->y(0), %{ $label->style }); - } - - # Draw stamps - for my $stamp ($plots->data('stamp')) { - my $symbol = $stamp->style('symbol'); - my $color = $stamp->style('color'); - my $r = $stamp->style('radius') || 4; - if ($symbol eq 'circle' || $symbol eq 'closed_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color, 1); - } elsif ($symbol eq 'open_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color); - } - } - - # Put a black frame around the picture - $self->im->rectangle(0, 0, $width - 1, $height - 1, $self->color('black')); - - return $plots->ext eq 'gif' ? $self->im->gif : $self->im->png; -} - -1; diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 6b531d8612..af1248a664 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -17,9 +17,10 @@ sub new { $plots->add_css_file('node_modules/jsxgraph/distrib/jsxgraph.css'); $plots->add_css_file('js/Plots/plots.css'); - $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js'); + $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js', { defer => undef }); + $plots->add_js_file('js/Plots/plots.js', { defer => undef }); - return bless { plots => $plots }, $class; + return bless { plots => $plots, names => { xaxis => 1 } }, $class; } sub plots { @@ -29,57 +30,130 @@ sub plots { sub HTML { my $self = shift; - my $name = $self->{name}; - my ($width, $height) = $self->plots->size; - - my $imageviewClass = $self->plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; - my $tabindex = $self->plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; - my $details = $self->plots->{description_details} =~ s/LONG-DESCRIPTION-ID/${name}_details/r; - my $aria_details = $details ? qq! aria-details="${name}_details"! : ''; - my $divs = qq!
    plots; + my ($width, $height) = $plots->size; + + my $imageviewClass = $plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; + my $tabindex = $plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; + my $roundedCornersClass = $plots->{rounded_corners} ? ' plots-jsxgraph-rounded' : ''; + my $details = $plots->{description_details} =~ s/LONG-DESCRIPTION-ID/$self->{name}_details/r; + my $aria_details = $details ? qq! aria-details="$self->{name}_details"! : ''; + + my $divs = + qq!
    !; - $divs = qq!
    $divs$details
    ! if ($details); + $divs = qq!
    $divs$details
    ! if $details; + + my $axes = $plots->axes; + my $xaxis_loc = $axes->xaxis('location'); + my $yaxis_loc = $axes->yaxis('location'); + my $xaxis_pos = $axes->xaxis('position'); + my $yaxis_pos = $axes->yaxis('position'); + my $show_grid = $axes->style('show_grid'); + my $grid = $axes->grid; + my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; + + my ($xvisible, $yvisible) = ($axes->xaxis('visible'), $axes->yaxis('visible')); + + my $options = {}; + + $options->{ariaDescription} = $axes->style('aria_description') if defined $axes->style('aria_description'); + + $options->{board}{title} = $axes->style('aria_label'); + $options->{board}{showNavigation} = $axes->style('jsx_navigation') ? 1 : 0; + $options->{board}{overrideOptions} = $axes->style('jsx_options') if $axes->style('jsx_options'); + + # Set the bounding box. Add padding for the axes at the edge of graph if needed. + $options->{board}{boundingBox} = [ + $xmin - ( + $yvisible + && ($yaxis_loc eq 'left' || $yaxis_loc eq 'box' || $xmin == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0 + ), + $ymax + ($xvisible && ($xaxis_loc eq 'top' || $ymax == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0), + $xmax + ($yvisible && ($yaxis_loc eq 'right' || $xmax == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0), + $ymin - ( + $xvisible + && ($xaxis_loc eq 'bottom' || $xaxis_loc eq 'box' || $ymin == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0 + ) + ]; + + $options->{xAxis}{visible} = $xvisible; + if ($xvisible || ($show_grid && $grid->{xmajor})) { + ($options->{xAxis}{min}, $options->{xAxis}{max}) = ($xmin, $xmax); + $options->{xAxis}{position} = $xaxis_pos; + $options->{xAxis}{location} = $xaxis_loc; + $options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale'); + $options->{xAxis}{ticks}{distance} = $axes->xaxis('tick_distance'); + $options->{xAxis}{ticks}{minorTicks} = $grid->{xminor}; + } + + $options->{yAxis}{visible} = $yvisible; + if ($yvisible || ($show_grid && $grid->{ymajor})) { + ($options->{yAxis}{min}, $options->{yAxis}{max}) = ($ymin, $ymax); + $options->{yAxis}{position} = $yaxis_pos; + $options->{yAxis}{location} = $yaxis_loc; + $options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale'); + $options->{yAxis}{ticks}{distance} = $axes->yaxis('tick_distance'); + $options->{yAxis}{ticks}{minorTicks} = $grid->{yminor}; + } + + if ($show_grid) { + if ($grid->{xmajor} || $grid->{ymajor}) { + $options->{grid}{color} = $self->get_color($axes->style('grid_color')); + $options->{grid}{opacity} = $axes->style('grid_alpha') / 200; + } + + if ($grid->{xmajor}) { + $options->{grid}{x}{minorGrids} = $grid->{xminor_grids}; + $options->{grid}{x}{overrideOptions} = $axes->xaxis('jsx_grid_options') if $axes->xaxis('jsx_grid_options'); + } + + if ($grid->{ymajor}) { + $options->{grid}{y}{minorGrids} = $grid->{yminor_grids}; + $options->{grid}{y}{overrideOptions} = $axes->yaxis('jsx_grid_options') if $axes->yaxis('jsx_grid_options'); + } + } + + if ($xvisible || $yvisible) { + $options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels'); + $options->{axesArrowsBoth} = $axes->style('axes_arrows_both'); + } + + if ($xvisible) { + $options->{xAxis}{name} = $axes->xaxis('label'); + $options->{xAxis}{ticks}{show} = $axes->xaxis('show_ticks'); + $options->{xAxis}{ticks}{labels} = $axes->xaxis('tick_labels'); + $options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format'); + $options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits'); + $options->{xAxis}{ticks}{scaleSymbol} = $axes->xaxis('tick_scale_symbol'); + $options->{xAxis}{overrideOptions} = $axes->xaxis('jsx_options') if $axes->xaxis('jsx_options'); + } + if ($yvisible) { + $options->{yAxis}{name} = $axes->yaxis('label'); + $options->{yAxis}{ticks}{show} = $axes->yaxis('show_ticks'); + $options->{yAxis}{ticks}{labels} = $axes->yaxis('tick_labels'); + $options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format'); + $options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits'); + $options->{yAxis}{ticks}{scaleSymbol} = $axes->yaxis('tick_scale_symbol'); + $options->{yAxis}{overrideOptions} = $axes->yaxis('jsx_options') if $axes->yaxis('jsx_options'); + } + + $self->{JS} //= ''; + $plots->{extra_js_code} //= ''; return <<~ "END_HTML"; $divs END_HTML @@ -88,7 +162,9 @@ sub HTML { sub get_color { my ($self, $color) = @_; $color = 'default_color' unless $color; - return sprintf("#%02x%02x%02x", @{ $self->plots->colors($color) }); + my $colorParts = $self->plots->colors($color); + return $color unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + return sprintf("#%02x%02x%02x", @$colorParts); } sub get_linestyle { @@ -107,39 +183,95 @@ sub get_linestyle { || 0; } +# Translate pgfplots layers to JSXGraph layers. +# FIXME: JSXGraph layers work rather differently than pgfplots layers. So this is a bit fuzzy, and may need adjustment. +# The layers chosen are as close as possible to the layers that JSXGraph uses by default, although "pre main" and "main" +# don't really have an equivalent. See https://jsxgraph.uni-bayreuth.de/docs/symbols/JXG.Options.html#layer. +# This also does not honor the "axis_on_top" setting. +sub get_layer { + my ($self, $data, $useFillLayer) = @_; + my $layer = $data->style($useFillLayer ? 'fill_layer' : 'layer'); + return unless $layer; + return { + 'axis background' => 0, + 'axis grid' => 1, + 'axis ticks' => 2, + 'axis lines' => 3, + 'pre main' => 4, + 'main' => 5, + 'axis tick labels' => 9, + 'axis descriptions' => 9, + 'axis foreground' => 10 + }->{$layer} // undef; +} + sub get_options { my ($self, $data, %extra_options) = @_; - my $options = Mojo::JSON::encode_json({ - highlight => 0, - strokeColor => $self->get_color($data->style('color')), - strokeWidth => $data->style('width'), - $data->style('start_mark') eq 'arrow' - ? (firstArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('end_mark') eq 'arrow' ? (lastArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('fill') eq 'self' - ? ( - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') - || 0.5 - ) - : (), - dash => $self->get_linestyle($data), - %extra_options, - }); - return $data->style('jsx_options') - ? "JXG.merge($options, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' - : $options; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $self->get_layer($data); + my $fillLayer = $self->get_layer($data, 1) // $drawLayer; + + my $drawFillSeparate = + $fill eq 'self' + && $data->style('linestyle') ne 'none' + && defined $fillLayer + && (!defined $drawLayer || $drawLayer != $fillLayer); + + my (%drawOptions, %fillOptions); + + if ($data->style('linestyle') ne 'none') { + $drawOptions{layer} = $drawLayer if defined $drawLayer; + $drawOptions{dash} = $self->get_linestyle($data); + $drawOptions{strokeColor} = $self->get_color($data->style('color')); + $drawOptions{strokeWidth} = $data->style('width'); + $drawOptions{firstArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('start_mark') eq 'arrow'; + $drawOptions{lastArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('end_mark') eq 'arrow'; + } + + if ($drawFillSeparate) { + $fillOptions{strokeWidth} = 0; + $fillOptions{layer} = $fillLayer; + $fillOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $fillOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + @fillOptions{ keys %extra_options } = values %extra_options; + } elsif ($fill eq 'self') { + if (!%drawOptions) { + $drawOptions{strokeWidth} = 0; + $drawOptions{layer} = $fillLayer if defined $fillLayer; + } + $drawOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $drawOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + } elsif ($data->style('name') && $data->style('linestyle') eq 'none') { + # This forces the curve to be drawn invisibly if it has been named, but the linestyle is 'none'. + $drawOptions{strokeWidth} = 0; + } + + @drawOptions{ keys %extra_options } = values %extra_options if %drawOptions; + + my $drawOptions = %drawOptions ? Mojo::JSON::encode_json(\%drawOptions) : ''; + my $fillOptions = $drawFillSeparate ? Mojo::JSON::encode_json(\%fillOptions) : ''; + return ( + $drawOptions && $data->style('jsx_options') + ? "JXG.merge($drawOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $drawOptions, + $fillOptions && $data->style('jsx_options') + ? "JXG.merge($fillOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $fillOptions + ); } sub add_curve { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my $curve_name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $plotOptions = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my ($plotOptions, $fillOptions) = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); my $type = 'curve'; my $data_points; @@ -172,68 +304,83 @@ sub add_curve { $data_points = '[[' . join(',', $data->x) . '],[' . join(',', $data->y) . ']]'; } - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('$type', $data_points, $plotOptions);"; - $self->add_point($data, $data->get_start_point, $data->style('width'), $data->style('start_mark')) - if $data->style('start_mark') =~ /circle/; - $self->add_point($data, $data->get_end_point, $data->style('width'), $data->style('end_mark')) - if $data->style('end_mark') =~ /circle/; - + $self->{JS} .= "const curve_${curve_name} = " if $curve_name; + $self->{JS} .= "board.create('$type', $data_points, $plotOptions);" if $plotOptions; + $self->{JS} .= "board.create('$type', $data_points, $fillOptions);" if $fillOptions; + $self->add_point( + $data, $data->get_start_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('start_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('start_mark') =~ /circle/; + $self->add_point( + $data, $data->get_end_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('end_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('end_mark') =~ /circle/; + + my $fill = $data->style('fill') || 'none'; if ($fill ne 'none' && $fill ne 'self') { - if ($curve_name) { - my $fill_min = $data->str_to_real($data->style('fill_min')); - my $fill_max = $data->str_to_real($data->style('fill_max')); - my $fillOptions = Mojo::JSON::encode_json({ - strokeWidth => 0, - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') || 0.5, - highlight => 0, - }); - - if ($fill eq 'xaxis') { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + if ($self->{names}{$fill}) { + if ($curve_name) { + my $fill_min = $data->str_to_real($data->style('fill_min')); + my $fill_max = $data->str_to_real($data->style('fill_max')); + my $fill_layer = $self->get_layer($data, 1) // $self->get_layer($data); + my $fillOptions = Mojo::JSON::encode_json({ + strokeWidth => 0, + fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), + fillOpacity => $data->style('fill_opacity') || 0.5, + defined $fill_layer ? (layer => $fill_layer) : (), + }); + + if ($fill eq 'xaxis') { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + $self->{JS} .= + ";this.dataX = points.map( p => p.usrCoords[1] );" + . "this.dataY = points.map( p => p.usrCoords[2] );" + . "this.dataX.push(points[points.length - 1].usrCoords[1], " + . "points[0].usrCoords[1], points[0].usrCoords[1]);" + . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" + . "board.update();"; + } else { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points1 = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + $self->{JS} .= ";const points2 = curve_${fill}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + $self->{JS} .= + ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" + . "points2.map( p => p.usrCoords[1] ).reverse());" + . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" + . "points2.map( p => p.usrCoords[2] ).reverse());" + . "this.dataX.push(points1[0].usrCoords[1]);" + . "this.dataY.push(points1[0].usrCoords[2]);" . "};" + . "board.update();"; } - $self->{JSend} .= - ";this.dataX = points.map( p => p.usrCoords[1] );" - . "this.dataY = points.map( p => p.usrCoords[2] );" - . "this.dataX.push(points[points.length - 1].usrCoords[1], " - . "points[0].usrCoords[1], points[0].usrCoords[1]);" - . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" - . "board.update();"; } else { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points1 = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= ";const points2 = curve_${fill}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= - ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" - . "points2.map( p => p.usrCoords[1] ).reverse());" - . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" - . "points2.map( p => p.usrCoords[2] ).reverse());" - . "this.dataX.push(points1[0].usrCoords[1]);" - . "this.dataY.push(points1[0].usrCoords[2]);" . "};" - . "board.update();"; + warn q{Unable to create fill. Missing 'name' attribute.}; } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } return; @@ -241,13 +388,15 @@ sub add_curve { sub add_multipath { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); - my $var = $data->{function}{var}; - my $curve_name = $data->style('name'); - my $plotOptions = $self->get_options($data); + my @paths = @{ $data->{paths} }; + my $n = scalar(@paths); + my $var = $data->{function}{var}; + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + my ($plotOptions, $fillOptions) = $self->get_options($data); my $jsFunctionx = 'function (x){'; my $jsFunctiony = 'function (x){'; @@ -269,13 +418,14 @@ sub add_multipath { $jsFunctionx .= 'return 0;}'; $jsFunctiony .= 'return 0;}'; - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $plotOptions);"; + $self->{JS} .= "const curve_${curve_name} = " if $curve_name; + $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $plotOptions);" if $plotOptions; + $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $fillOptions);" if $fillOptions; return; } sub add_point { - my ($self, $data, $x, $y, $size, $mark) = @_; + my ($self, $data, $x, $y, $size, $strokeWidth, $mark) = @_; my $color = $self->get_color($data->style('color')); my $fill = $color; @@ -318,7 +468,7 @@ sub add_point { strokeColor => $color, fillColor => $fill, size => $size, - highlight => 0, + strokeWidth => $strokeWidth, showInfoBox => 0, }); $pointOptions = "JXG.merge($pointOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' @@ -337,29 +487,60 @@ sub add_points { $data->gen_data if $data->name eq 'function'; for (0 .. $data->size - 1) { - $self->add_point($data, $data->x($_), $data->y($_), $data->style('mark_size') || $data->style('width'), $mark); + $self->add_point( + $data, $data->x($_), $data->y($_), + $data->style('mark_size') || 2, + $data->style('width') || 2, $mark + ); } return; } +sub add_vectorfield { + my ($self, $data) = @_; + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); + + if ($xfunction ne '' && $yfunction ne '') { + my ($options) = $self->get_options( + $data, + scale => $data->style('scale') || 1, + ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), + ); + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $xfunction = $xtmp; + } + + $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " + . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; + } else { + warn 'Vector field not created due to missing JavaScript functions.'; + } +} + sub add_circle { my ($self, $data) = @_; - my $x = $data->x(0); - my $y = $data->y(0); - my $r = $data->style('radius'); - my $linestyle = $self->get_linestyle($data); - my $circleOptions = $self->get_options($data); + my $x = $data->x(0); + my $y = $data->y(0); + my $r = $data->style('radius'); + my ($circleOptions, $fillOptions) = $self->get_options($data); $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);"; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $fillOptions);" if $fillOptions; return; } sub add_arc { - my ($self, $data) = @_; - my ($x1, $y1) = ($data->x(0), $data->y(0)); - my ($x2, $y2) = ($data->x(1), $data->y(1)); - my ($x3, $y3) = ($data->x(2), $data->y(2)); - my $arcOptions = $self->get_options( + my ($self, $data) = @_; + my ($x1, $y1) = ($data->x(0), $data->y(0)); + my ($x2, $y2) = ($data->x(1), $data->y(1)); + my ($x3, $y3) = ($data->x(2), $data->y(2)); + my ($arcOptions, $fillOptions) = $self->get_options( $data, anglePoint => { visible => 0 }, center => { visible => 0 }, @@ -367,195 +548,34 @@ sub add_arc { ); $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);"; + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $fillOptions);" if $fillOptions; return; } -sub init_graph { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $xaxis_loc = $axes->xaxis('location'); - my $yaxis_loc = $axes->yaxis('location'); - my $xaxis_pos = $axes->xaxis('position'); - my $yaxis_pos = $axes->yaxis('position'); - my $show_grid = $axes->style('show_grid'); - my $allow_navigation = $axes->style('jsx_navigation') ? 1 : 0; - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - $xaxis_loc = 'bottom' if $xaxis_loc eq 'box'; - $yaxis_loc = 'left' if $yaxis_loc eq 'box'; - - # Determine if zero should be drawn on the axis. - my $x_draw_zero = - $allow_navigation - || ($yaxis_loc eq 'center' && $yaxis_pos != 0) - || ($yaxis_loc eq 'left' && $ymin != 0) - || ($yaxis_loc eq 'right' && $ymax != 0) ? 1 : 0; - my $y_draw_zero = - $allow_navigation - || ($xaxis_loc eq 'middle' && $xaxis_pos != 0) - || ($xaxis_loc eq 'bottom' && $xmin != 0) - || ($xaxis_loc eq 'top' && $xmax != 0) ? 1 : 0; - - # Adjust bounding box to add padding for axes at edge of graph. - $xmin -= 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'left' || $xmin == $yaxis_pos; - $xmax += 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'right' || $xmax == $yaxis_pos; - $ymin -= 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'bottom' || $ymin == $xaxis_pos; - $ymax += 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'top' || $ymax == $xaxis_pos; - - my $JSXOptions = Mojo::JSON::encode_json({ - title => $axes->style('aria_label'), - boundingBox => [ $xmin, $ymax, $xmax, $ymin ], - axis => 0, - showNavigation => $allow_navigation, - pan => { enabled => $allow_navigation }, - zoom => { enabled => $allow_navigation }, - showCopyright => 0, - drag => { enabled => 0 }, - }); - $JSXOptions = "JXG.merge($JSXOptions, " . Mojo::JSON::encode_json($axes->style('jsx_options')) . ')' - if $axes->style('jsx_options'); - my $XAxisOptions = Mojo::JSON::encode_json({ - name => $axes->xaxis('label'), - withLabel => 1, - position => $xaxis_loc eq 'middle' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $xaxis_loc eq 'top' ? 'left' : $xaxis_loc eq 'bottom' ? 'right' : 'right left', - visible => $axes->xaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% left', - offset => [ -10, 0 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->xaxis('tick_labels') && $axes->xaxis('show_ticks') ? 1 : 0, - drawZero => $x_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->xaxis('tick_delta'), - majorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->xaxis('minor'), - minorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => 'middle', - anchorY => $xaxis_loc eq 'top' ? 'bottom' : 'top', - offset => $xaxis_loc eq 'top' ? [ 0, 3 ] : [ 0, -3 ] - }, - }, - }); - $XAxisOptions = "JXG.merge($XAxisOptions, " . Mojo::JSON::encode_json($axes->xaxis('jsx_options')) . ')' - if $axes->xaxis('jsx_options'); - my $YAxisOptions = Mojo::JSON::encode_json({ - name => $axes->yaxis('label'), - withLabel => 1, - position => $yaxis_loc eq 'center' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $yaxis_loc eq 'center' ? 'right left' : $yaxis_loc, - visible => $axes->yaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% right', - offset => [ 6, -10 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->yaxis('tick_labels') && $axes->yaxis('show_ticks') ? 1 : 0, - drawZero => $y_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->yaxis('tick_delta'), - majorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->yaxis('minor'), - minorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => $yaxis_loc eq 'right' ? 'left' : 'right', - anchorY => 'middle', - offset => $yaxis_loc eq 'right' ? [ 6, 0 ] : [ -6, 0 ] - }, - }, - }); - $YAxisOptions = "JXG.merge($YAxisOptions, " . Mojo::JSON::encode_json($axes->yaxis('jsx_options')) . ')' - if $axes->yaxis('jsx_options'); - - $self->{JSend} = ''; - $self->{JS} = <<~ "END_JS"; - const board = JXG.JSXGraph.initBoard(id, $JSXOptions); - const descriptionSpan = document.createElement('span'); - descriptionSpan.id = `\${id}_description`; - descriptionSpan.classList.add('visually-hidden'); - descriptionSpan.textContent = '${\($axes->style('aria_description'))}'; - board.containerObj.after(descriptionSpan); - board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); - board.suspendUpdate(); - board.create('axis', [[$xmin, $xaxis_pos], [$xmax, $xaxis_pos]], $XAxisOptions); - board.create('axis', [[$yaxis_pos, $ymin], [$yaxis_pos, $ymax]], $YAxisOptions); - END_JS -} - sub draw { my $self = shift; my $plots = $self->plots; $self->{name} = $plots->get_image_name =~ s/-/_/gr; - $self->init_graph; - - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { if ($data->name eq 'circle') { $self->add_circle($data); } elsif ($data->name eq 'arc') { $self->add_arc($data); } elsif ($data->name eq 'multipath') { $self->add_multipath($data); + } elsif ($data->name eq 'vectorfield') { + $self->add_vectorfield($data); } else { - $self->add_curve($data); + $self->add_curve($data) unless $data->name eq 'point'; $self->add_points($data); } } - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); - - if ($xfunction ne '' && $yfunction ne '') { - my $options = $self->get_options( - $data, - scale => $data->style('scale') || 1, - ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), - ); - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $xfunction = $xtmp; - } - - $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " - . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; - } else { - warn "Vector field not created due to missing JavaScript functions."; - } - } - # Stamps for my $stamp ($plots->data('stamp')) { my $mark = $stamp->style('symbol'); @@ -566,7 +586,7 @@ sub draw { my $y = $stamp->y(0); my $size = $stamp->style('radius') || 4; - $self->add_point($stamp, $x, $y, $size, $mark); + $self->add_point($stamp, $x, $y, $size, $stamp->style('width') || 2, $mark); } # Labels @@ -574,18 +594,27 @@ sub draw { my $str = $label->style('label'); my $x = $label->x(0); my $y = $label->y(0); - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; my $textOptions = Mojo::JSON::encode_json({ - highlight => 0, - fontSize => { tiny => 8, small => 10, medium => 12, large => 14, giant => 16 }->{$fontsize}, - rotate => $label->style('rotate') || 0, + fontSize => { + tiny => 8, + small => 10, + normalsize => 12, + medium => 12, # deprecated + large => 14, + Large => 16, + giant => 16, # deprecated + Large => 16, + huge => 20, + Huge => 23 + }->{$fontsize}, + $label->style('rotate') ? (rotate => $label->style('rotate')) : (), strokeColor => $self->get_color($label->style('color')), anchorX => $h_align eq 'center' ? 'middle' : $h_align, anchorY => $v_align, - cssStyle => 'padding: 3px;', + cssStyle => 'padding: 3px 5px;', useMathJax => 1, }); $textOptions = "JXG.merge($textOptions, " . Mojo::JSON::encode_json($label->style('jsx_options')) . ')' diff --git a/lib/Plots/Plot.pm b/lib/Plots/Plot.pm index 660edba168..775a06365e 100644 --- a/lib/Plots/Plot.pm +++ b/lib/Plots/Plot.pm @@ -16,27 +16,24 @@ use Plots::Axes; use Plots::Data; use Plots::Tikz; use Plots::JSXGraph; -use Plots::GD; sub new { my ($class, %options) = @_; my $self = bless { - imageName => {}, - width => eval('$main::envir{onTheFlyImageSize}') || 350, - height => undef, - tex_size => 600, - axes => Plots::Axes->new, - colors => {}, - data => [], + imageName => {}, + width => eval('$main::envir{onTheFlyImageSize}') || 350, + height => undef, + tex_size => 600, + rounded_corners => 0, + axes => Plots::Axes->new, + colors => {}, + data => [], }, $class; # Besides for these core options, pass everything else to the Axes object. - for ('width', 'height', 'tex_size') { - if ($options{$_}) { - $self->{$_} = $options{$_}; - delete $options{$_}; - } + for ('width', 'height', 'tex_size', 'rounded_corners') { + $self->{$_} = delete $options{$_} if $options{$_}; } $self->axes->set(%options) if %options; @@ -48,13 +45,12 @@ sub new { sub pgCall { my ($call, @args) = @_; - WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); - return; + return WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); } sub add_js_file { - my ($self, $file) = @_; - pgCall('ADD_JS_FILE', $file); + my ($self, $file, $attributes) = @_; + pgCall('ADD_JS_FILE', $file, 0, $attributes); return; } @@ -164,6 +160,17 @@ sub image_type { my ($self, $type, $ext) = @_; return $self->{type} unless $type; + # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. + if ($self->{pg}{displayMode} eq 'TeX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'pdf'; + return; + } elsif ($self->{pg}{displayMode} eq 'PTX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'tgz'; + return; + } + # Check type and extension are valid. The first element of @validExt is used as default. my @validExt; $type = lc($type); @@ -173,9 +180,6 @@ sub image_type { } elsif ($type eq 'tikz') { $self->{type} = 'Tikz'; @validExt = ('svg', 'png', 'pdf', 'gif', 'tgz'); - } elsif ($type eq 'gd') { - $self->{type} = 'GD'; - @validExt = ('png', 'gif'); } else { warn "Plots: Invalid image type $type."; return; @@ -191,14 +195,6 @@ sub image_type { $self->{ext} = $validExt[0]; } - # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. - if ($self->{pg}{displayMode} eq 'TeX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'pdf'; - } elsif ($self->{pg}{displayMode} eq 'PTX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'tgz'; - } return; } @@ -215,7 +211,7 @@ sub tikz_code { # Add functions to the graph. sub _add_function { - my ($self, $Fx, $Fy, $var, $min, $max, @rest) = @_; + my ($self, $Fx, $Fy, $var, $min, $max, %rest) = @_; $var = 't' unless $var; $Fx = $var unless defined($Fx); @@ -229,9 +225,10 @@ sub _add_function { xmax => $max, color => 'default_color', width => 2, + mark_size => 2, dashed => 0, tikz_smooth => 1, - @rest + %rest ); $self->add_data($data); @@ -297,11 +294,8 @@ sub add_function { sub add_multipath { my ($self, $paths, $var, %options) = @_; my $data = Plots::Data->new(name => 'multipath'); - my $steps = 500; # Steps set high to help Tikz deal with boundaries of paths. - if ($options{steps}) { - $steps = $options{steps}; - delete $options{steps}; - } + my $steps = 100 * @$paths; # Steps set high to help Tikz deal with boundaries of paths. + $steps = delete $options{steps} if $options{steps}; $data->{context} = $self->context; $data->{paths} = [ map { { @@ -312,7 +306,7 @@ sub add_multipath { } } @$paths ]; $data->{function} = { var => $var, steps => $steps }; - $data->style(color => 'default_color', width => 2, %options); + $data->style(color => 'default_color', width => 2, mark_size => 2, %options); $self->add_data($data); return $data; @@ -329,8 +323,9 @@ sub _add_dataset { $data->add(@{ shift(@points) }); } $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @points ); @@ -351,9 +346,10 @@ sub _add_circle { my $data = Plots::Data->new(name => 'circle'); $data->add(@$point); $data->style( - radius => $radius, - color => 'default_color', - width => 2, + radius => $radius, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -374,8 +370,9 @@ sub _add_arc { my $data = Plots::Data->new(name => 'arc'); $data->add($point1, $point2, $point3); $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -396,18 +393,19 @@ sub add_vectorfield { my $data = Plots::Data->new(name => 'vectorfield'); $data->set_function( $self->context, - Fx => '', - Fy => '', - xvar => 'x', - yvar => 'y', - xmin => -5, - xmax => 5, - ymin => -5, - ymax => 5, - xsteps => 15, - ysteps => 15, - width => 1, - color => 'default_color', + Fx => '', + Fy => '', + xvar => 'x', + yvar => 'y', + xmin => -5, + xmax => 5, + ymin => -5, + ymax => 5, + xsteps => 15, + ysteps => 15, + width => 1, + mark_size => 1, + color => 'default_color', @options ); @@ -417,15 +415,17 @@ sub add_vectorfield { sub _add_label { my ($self, $x, $y, @options) = @_; - my $data = Plots::Data->new(name => 'label'); + my $data = Plots::Data->new(name => 'label'); + my $label = @options % 2 ? shift @options : ''; $data->add($x, $y); $data->style( color => 'default_color', fontsize => 'medium', orientation => 'horizontal', + rotate => 0, h_align => 'center', v_align => 'middle', - label => '', + label => $label, @options ); @@ -438,22 +438,17 @@ sub add_label { return ref($labels[0]) eq 'ARRAY' ? [ map { $self->_add_label(@$_); } @labels ] : $self->_add_label(@labels); } -# Fill regions only work with GD and are ignored in TikZ images. -sub _add_fill_region { - my ($self, $x, $y, $color) = @_; - my $data = Plots::Data->new(name => 'fill_region'); - $data->add($x, $y); - $data->style(color => $color || 'default_color'); - $self->add_data($data); +sub _add_point { + my ($self, $x, $y, %options) = @_; + $options{marks} = delete $options{mark} if $options{mark} && !defined $options{marks}; + my $data = $self->_add_dataset([ $x, $y ], marks => 'circle', %options); + $data->{name} = 'point'; return $data; } -sub add_fill_region { - my ($self, @regions) = @_; - return - ref($regions[0]) eq 'ARRAY' - ? [ map { $self->_add_fill_region(@$_); } @regions ] - : $self->_add_fill_region(@regions); +sub add_point { + my ($self, @points) = @_; + return ref($points[0]) eq 'ARRAY' ? [ map { $self->_add_point(@$_); } @points ] : $self->_add_point(@points); } sub _add_stamp { @@ -462,7 +457,7 @@ sub _add_stamp { $data->add($x, $y); $data->style( color => 'default_color', - size => 4, + radius => 4, symbol => 'circle', @options ); @@ -485,8 +480,6 @@ sub draw { $image = Plots::Tikz->new($self); } elsif ($type eq 'JSXGraph') { $image = Plots::JSXGraph->new($self); - } elsif ($type eq 'GD') { - $image = Plots::GD->new($self); } else { warn "Undefined image type: $type"; return; diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 08d18eacbf..02cedace32 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -15,19 +15,23 @@ use warnings; sub new { my ($class, $plots) = @_; my $image = LaTeXImage->new; - $image->environment([ 'tikzpicture', 'framed' ]); + $image->environment(['tikzpicture']); $image->svgMethod(eval('$main::envir{latexImageSVGMethod}') // 'dvisvgm'); $image->convertOptions(eval('$main::envir{latexImageConvertOptions}') // { input => {}, output => {} }); $image->ext($plots->ext); - $image->tikzLibraries('arrows.meta,plotmarks,backgrounds'); + $image->tikzLibraries('arrows.meta,plotmarks,calc'); $image->texPackages(['pgfplots']); - # Set the pgfplots compatibility, add the pgfplots fillbetween library, set a nice rectangle frame with white - # background for the backgrounds library, and redefine standard layers since the backgrounds library uses layers - # that conflict with the layers used by the fillbetween library. + # Set the pgfplots compatibility, add the pgfplots fillbetween library, define a save + # box that is used to wrap the axes in a nice rectangle frame with a white background, and redefine + # standard layers to include a background layer for the background. + # Note that "axis tick labels" is moved after "pre main" and "main" in the standard layer set. That is different + # than the pgfplots defaults, but is consistent with where JSXGraph places them, and is better than what pgplots + # does. Axis tick labels are textual elements that should be in front of the things that are drawn and together + # with the "axis descriptions". $image->addToPreamble( <<~ 'END_PREAMBLE'); \usepgfplotslibrary{fillbetween} - \tikzset{inner frame sep = 0pt, background rectangle/.style = { thick, draw = DarkBlue, fill = white }} + \newsavebox{\axesBox} \pgfplotsset{ compat = 1.18, layers/standard/.define layer set = { @@ -36,9 +40,9 @@ sub new { axis grid, axis ticks, axis lines, - axis tick labels, pre main, main, + axis tick labels, axis descriptions, axis foreground }{ @@ -68,7 +72,7 @@ sub new { } END_PREAMBLE - return bless { image => $image, plots => $plots, colors => {} }, $class; + return bless { image => $image, plots => $plots, colors => {}, names => { xaxis => 1 } }, $class; } sub plots { @@ -84,7 +88,9 @@ sub im { sub get_color { my ($self, $color) = @_; return '' if $self->{colors}{$color}; - my ($r, $g, $b) = @{ $self->plots->colors($color) }; + my $colorParts = $self->plots->colors($color); + return '' unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + my ($r, $g, $b) = @$colorParts; $self->{colors}{$color} = 1; return "\\definecolor{$color}{RGB}{$r,$g,$b}\n"; } @@ -94,183 +100,460 @@ sub get_mark { return { circle => '*', closed_circle => '*', - open_circle => 'o', + open_circle => '*, mark options={fill=white}', square => 'square*', - open_square => 'square', + open_square => 'square*, mark options={fill=white}', plus => '+', times => 'x', bar => '|', dash => '-', triangle => 'triangle*', - open_triangle => 'triangle', + open_triangle => 'triangle*, mark options={fill=white}', diamond => 'diamond*', - open_diamond => 'diamond', + open_diamond => 'diamond*, mark options={fill=white}', }->{$mark}; } -sub configure_axes { - my $self = shift; +# This is essentially copied from contextFraction.pl, and is exactly copied from parserGraphTool.pl. +# FIXME: Clearly there needs to be a single version of this somewhere that all three can use. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $tickFormat = $self->plots->axes->$axis('tick_label_format'); + if ($tickFormat eq 'fraction' || $tickFormat eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($tickFormat eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } elsif ($tickFormat eq 'scinot') { + my ($mantissa, $exponent) = split('e', sprintf('%e', $value)); + $value = + Plots::Plot::pgCall('Round', $mantissa, $self->plots->axes->$axis('tick_label_digits') // 2) + . "\\cdot 10^{$exponent}"; + } else { + $value = + sprintf('%f', Plots::Plot::pgCall('Round', $value, $self->plots->axes->$axis('tick_label_digits') // 2)); + if ($value =~ /\./) { + $value =~ s/0*$//; + $value =~ s/\.$//; + } + } + my $scaleSymbol = $self->plots->axes->$axis('tick_scale_symbol'); + return '\\(' + . ($value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value) . '\\)'; +} + +sub generate_axes { + my ($self, $plotContents) = @_; my $plots = $self->plots; my $axes = $plots->axes; my $grid = $axes->grid; my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; my ($axes_width, $axes_height) = $plots->size; - my $show_grid = $axes->style('show_grid'); - my $xvisible = $axes->xaxis('visible'); - my $yvisible = $axes->yaxis('visible'); - my $xmajor = $show_grid && $xvisible && $grid->{xmajor} && $axes->xaxis('show_ticks') ? 'true' : 'false'; - my $xminor_num = $grid->{xminor}; - my $xminor = $show_grid && $xvisible && $xmajor eq 'true' && $xminor_num > 0 ? 'true' : 'false'; - my $ymajor = $show_grid && $yvisible && $grid->{ymajor} && $axes->yaxis('show_ticks') ? 'true' : 'false'; - my $yminor_num = $grid->{yminor}; - my $yminor = $show_grid && $yvisible && $ymajor eq 'true' && $yminor_num > 0 ? 'true' : 'false'; - my $xticks = $axes->xaxis('show_ticks') ? "xtick distance=$grid->{xtick_delta}" : 'xtick=\empty'; - my $yticks = $axes->yaxis('show_ticks') ? "ytick distance=$grid->{ytick_delta}" : 'ytick=\empty'; - my $xtick_labels = $axes->xaxis('tick_labels') ? '' : "\nxticklabel=\\empty,"; - my $ytick_labels = $axes->yaxis('tick_labels') ? '' : "\nyticklabel=\\empty,"; - my $grid_color = $axes->style('grid_color'); - my $grid_color2 = $self->get_color($grid_color); - my $grid_alpha = $axes->style('grid_alpha'); - my $xlabel = $axes->xaxis('label'); - my $axis_x_line = $axes->xaxis('location'); - my $axis_x_pos = $axes->xaxis('position'); - my $ylabel = $axes->yaxis('label'); - my $axis_y_line = $axes->yaxis('location'); - my $axis_y_pos = $axes->yaxis('position'); - my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; - my $hide_x_axis = ''; - my $hide_y_axis = ''; - my $xaxis_plot = ($xmin <= 0 && $xmax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax,0);\n" : ''; - $axis_x_pos = $axis_x_pos ? ",\naxis x line shift=" . (-$axis_x_pos) : ''; - $axis_y_pos = $axis_y_pos ? ",\naxis y line shift=" . (-$axis_y_pos) : ''; - - unless ($xvisible) { - $xlabel = ''; - $hide_x_axis = "\nx axis line style={draw=none},\n" . "x tick style={draw=none},\n" . "xticklabel=\\empty,"; + my $show_grid = $axes->style('show_grid'); + my $xvisible = $axes->xaxis('visible'); + my $yvisible = $axes->yaxis('visible'); + my $xmajor = $show_grid && $grid->{xmajor} ? 'true' : 'false'; + my $xminor = $show_grid && $xmajor eq 'true' && $grid->{xminor_grids} && $grid->{xminor} > 0 ? 'true' : 'false'; + my $ymajor = $show_grid && $grid->{ymajor} ? 'true' : 'false'; + my $yminor = $show_grid && $ymajor eq 'true' && $grid->{yminor_grids} && $grid->{yminor} > 0 ? 'true' : 'false'; + my $grid_color = $axes->style('grid_color'); + my $grid_color_def = $self->get_color($grid_color); + my $grid_alpha = $axes->style('grid_alpha') / 100; + my $xaxis_location = $axes->xaxis('location'); + my $xaxis_pos = $xaxis_location eq 'middle' ? $axes->xaxis('position') : 0; + my $yaxis_location = $axes->yaxis('location'); + my $yaxis_pos = $yaxis_location eq 'center' ? $axes->yaxis('position') : 0; + my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; + my $negativeArrow = $axes->style('axes_arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $tikz_options = $axes->style('tikz_options') // ''; + + my $xlabel = $xvisible ? $axes->xaxis('label') : ''; + my $xaxis_style = + $xvisible + ? ",\nx axis line style={$negativeArrow-Latex[{round,scale=1.6}]}" + : ",\nx axis line style={draw=none},\nextra y ticks={0}"; + my $xtick_style = + $xvisible && $axes->xaxis('show_ticks') ? ",\nx tick style={line width=0.6pt}" : ",\nx tick style={draw=none}"; + + my $ylabel = $yvisible ? $axes->yaxis('label') : ''; + my $yaxis_style = + $yvisible + ? ",\ny axis line style={$negativeArrow-Latex[{round,scale=1.6}]}" + : ",\ny axis line style={draw=none},\nextra x ticks={0}"; + my $ytick_style = + $yvisible && $axes->yaxis('show_ticks') ? ",\ny tick style={line width=0.6pt}" : ",\ny tick style={draw=none}"; + + my $x_tick_distance = $axes->xaxis('tick_distance'); + my $x_tick_scale = $axes->xaxis('tick_scale') || 1; + + my @xticks = + grep { $_ > $xmin && $_ < $xmax } + map { -$_ * $x_tick_distance * $x_tick_scale } + reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale)); + push(@xticks, 0) if $xmin < 0 && $xmax > 0; + push(@xticks, + grep { $_ > $xmin && $_ < $xmax } + map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale))); + + my $xtick_labels = + $xvisible + && $axes->xaxis('show_ticks') + && $axes->xaxis('tick_labels') + ? (",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={" + . join(',', map { $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis') } @xticks) . '}') + : ",\nxticklabel=\\empty"; + + my @xminor_ticks; + if ($grid->{xminor} > 0) { + my @majorTicks = @xticks; + unshift(@majorTicks, ($majorTicks[0] // $xmin) - $x_tick_distance * $x_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $xmax) + $x_tick_distance * $x_tick_scale); + my $x_minor_delta = $x_tick_distance * $x_tick_scale / ($grid->{xminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@xminor_ticks, + grep { $_ > $xmin && $_ < $xmax } + map { $majorTicks[$tickIndex] + $_ * $x_minor_delta } 1 .. $grid->{xminor}); + } } - unless ($yvisible) { - $ylabel = ''; - $hide_y_axis = "\ny axis line style={draw=none},\n" . "y tick style={draw=none},\n" . "yticklabel=\\empty,"; + + my $y_tick_distance = $axes->yaxis('tick_distance'); + my $y_tick_scale = $axes->yaxis('tick_scale') || 1; + + my @yticks = + grep { $_ > $ymin && $_ < $ymax } + map { -$_ * $y_tick_distance * $y_tick_scale } + reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale)); + push(@yticks, 0) if $ymin < 0 && $ymax > 0; + push(@yticks, + grep { $_ > $ymin && $_ < $ymax } + map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale))); + + my $ytick_labels = + $yvisible + && $axes->yaxis('show_ticks') + && $axes->yaxis('tick_labels') + ? (",\nyticklabel shift=-3pt,\nyticklabels={" + . join(',', map { $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis') } @yticks) . '}') + : ",\nyticklabel=\\empty"; + + my @yminor_ticks; + if ($grid->{yminor} > 0) { + my @majorTicks = @yticks; + unshift(@majorTicks, ($majorTicks[0] // $ymin) - $y_tick_distance * $y_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $ymax) + $y_tick_distance * $y_tick_scale); + my $y_minor_delta = $y_tick_distance * $y_tick_scale / ($grid->{yminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@yminor_ticks, + grep { $_ > $ymin && $_ < $ymax } + map { $majorTicks[$tickIndex] + $_ * $y_minor_delta } 1 .. $grid->{yminor}); + } } + + my $xaxis_plot = ($ymin <= 0 && $ymax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax, 0);" : ''; + $xaxis_pos = $xaxis_pos ? ",\naxis x line shift=" . (($ymin > 0 ? $ymin : $ymax < 0 ? $ymax : 0) - $xaxis_pos) : ''; + $yaxis_pos = $yaxis_pos ? ",\naxis y line shift=" . (($xmin > 0 ? $xmin : $xmax < 0 ? $xmax : 0) - $yaxis_pos) : ''; + + my $roundedCorners = $plots->{rounded_corners} ? 'rounded corners = 10pt' : ''; + my $left = + $yvisible && ($yaxis_location eq 'left' || $yaxis_location eq 'box' || $xmin == $axes->yaxis('position')) + ? 'outer west' + : 'west'; + my $right = $yvisible && ($yaxis_location eq 'right' || $xmax == $axes->yaxis('position')) ? 'outer east' : 'east'; + my $lower = + $xvisible && ($xaxis_location eq 'bottom' || $xaxis_location eq 'box' || $ymin == $axes->xaxis('position')) + ? 'outer south' + : 'south'; + my $upper = $xvisible && ($xaxis_location eq 'top' || $ymax == $axes->xaxis('position')) ? 'outer north' : 'north'; + + # The savebox only actually saves the main layer. All other layers are actually drawn when the savebox is saved. + # So clipping of anything drawn on any other layer has to be done when things are drawn on the other layers. The + # axisclippath is used for this. The main layer is clipped at the end when the savebox is used. my $tikzCode = <<~ "END_TIKZ"; - \\begin{axis} - [ - trig format plots=rad, - view={0}{90}, - scale only axis, - height=$axes_height, - width=$axes_width, - ${axis_on_top}axis x line=$axis_x_line$axis_x_pos, - axis y line=$axis_y_line$axis_y_pos, - xlabel={$xlabel}, - ylabel={$ylabel}, - $xticks,$xtick_labels - $yticks,$ytick_labels - xmajorgrids=$xmajor, - xminorgrids=$xminor, - minor x tick num=$xminor_num, - ymajorgrids=$ymajor, - yminorgrids=$yminor, - minor y tick num=$yminor_num, - grid style={$grid_color!$grid_alpha}, - xmin=$xmin, - xmax=$xmax, - ymin=$ymin, - ymax=$ymax,$hide_x_axis$hide_y_axis - ] - $grid_color2$xaxis_plot + \\pgfplotsset{set layers=${\($axes->style('axis_on_top') ? 'axis on top' : 'standard')}}% + $grid_color_def + \\savebox{\\axesBox}{ + \\Large + \\begin{axis} + [ + trig format plots=rad, + scale only axis, + height=$axes_height, + width=$axes_width, + ${axis_on_top}axis x line=$xaxis_location$xaxis_pos$xaxis_style, + axis y line=$yaxis_location$yaxis_pos$yaxis_style, + xlabel={$xlabel}, + ylabel={$ylabel}, + xtick={${\(join(',', @xticks))}}$xtick_style$xtick_labels, + minor xtick={${\(join(',', @xminor_ticks))}}, + ytick={${\(join(',', @yticks))}}$ytick_style$ytick_labels, + minor ytick={${\(join(',', @yminor_ticks))}}, + xtick scale label code/.code={}, + ytick scale label code/.code={}, + major tick length=0.3cm, + minor tick length=0.2cm, + xmajorgrids=$xmajor, + xminorgrids=$xminor, + ymajorgrids=$ymajor, + yminorgrids=$yminor, + grid style={$grid_color, opacity=$grid_alpha}, + xmin=$xmin, + xmax=$xmax, + ymin=$ymin, + ymax=$ymax,$tikz_options + ] + $xaxis_plot + \\newcommand{\\axisclippath}{(current axis.south west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.south east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- cycle} + END_TIKZ + + $tikzCode .= $plotContents; + $tikzCode .= $plots->{extra_tikz_code} if $plots->{extra_tikz_code}; + + $tikzCode .= <<~ "END_TIKZ"; + \\end{axis} + } + \\pgfresetboundingbox + \\begin{pgfonlayer}{background} + \\filldraw[draw = DarkBlue, fill = white, $roundedCorners, line width = 0.5pt] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} + \\begin{scope} + \\clip[$roundedCorners] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\usebox{\\axesBox} + \\end{scope} + \\begin{pgfonlayer}{axis foreground} + \\draw[draw = DarkBlue, $roundedCorners, line width = 0.5pt, use as bounding box] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} END_TIKZ chop($tikzCode); - return $tikzCode =~ s/^\t//gr; + return $tikzCode; } -sub get_plot_opts { +sub get_options { my ($self, $data) = @_; - my $color = $data->style('color') || 'default_color'; - my $width = $data->style('width'); - my $linestyle = $data->style('linestyle') || 'solid'; - my $marks = $data->style('marks') || 'none'; - my $mark_size = $data->style('mark_size') || 0; - my $start = $data->style('start_mark') || 'none'; - my $end = $data->style('end_mark') || 'none'; - my $name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $fill_opacity = $data->style('fill_opacity') || 0.5; - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - my $smooth = $data->style('tikz_smooth') ? 'smooth, ' : ''; - - if ($start =~ /circle/) { - $start = '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($start eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $start = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $start = ''; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $data->style('layer'); + my $fillLayer = $data->style('fill_layer') || $drawLayer; + my $marks = $self->get_mark($data->style('marks')); + + my $drawFillSeparate = + $fill eq 'self' + && ($data->style('linestyle') ne 'none' || $marks) + && defined $fillLayer + && (!defined $drawLayer || $drawLayer ne $fillLayer); + + my (@drawOptions, @fillOptions); + + if ($data->style('linestyle') ne 'none' || $marks) { + my $linestyle = { + none => 'draw=none', + solid => 'solid', + dashed => 'dash={on 11pt off 8pt phase 6pt}', + short_dashes => 'dash pattern={on 6pt off 3pt}', + long_dashes => 'dash={on 20pt off 15pt phase 10pt}', + dotted => 'dotted', + long_medium_dashes => 'dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', + }->{ ($data->style('linestyle') || 'solid') =~ s/ /_/gr } + || 'solid'; + push(@drawOptions, $linestyle); + + my $width = $data->style('width'); + push(@drawOptions, "line width=${width}pt", "color=" . ($data->style('color') || 'default_color')); + + if ($linestyle ne 'draw=none') { + my $start = $data->style('start_mark') || ''; + if ($start =~ /circle/) { + $start = + '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($start eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $start = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $start = ''; + } + + my $end = $data->style('end_mark') || ''; + if ($end =~ /circle/) { + $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($end eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $end = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $end = ''; + } + + push(@drawOptions, "$start-$end") if $start || $end; + } + + if ($marks) { + push(@drawOptions, "mark=$marks"); + + my $mark_size = $data->style('mark_size') || 0; + if ($mark_size) { + $mark_size = $mark_size + $width / 2 if $marks =~ /^[*+]/; + $mark_size = $mark_size + $width if $marks eq 'x'; + push(@drawOptions, "mark size=${mark_size}pt"); + } + } + + push(@drawOptions, 'smooth') if $data->style('tikz_smooth'); } - if ($end =~ /circle/) { - $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($end eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $end = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $end = ''; + + my $tikz_options = $data->style('tikz_options'); + + if ($drawFillSeparate) { + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@fillOptions, 'draw=none', "fill=$fill_color", "fill opacity=$fill_opacity"); + push(@fillOptions, 'smooth') if $data->style('tikz_smooth'); + push(@fillOptions, $tikz_options) if $tikz_options; + } elsif ($fill eq 'self') { + if (!@drawOptions) { + push(@drawOptions, 'draw=none'); + $drawLayer = $fillLayer if defined $fillLayer; + } + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@drawOptions, "fill=$fill_color", "fill opacity=$fill_opacity"); } - my $end_markers = ($start || $end) ? ", $start-$end" : ''; - $marks = $self->get_mark($marks); - $marks = $marks ? $mark_size ? ", mark=$marks, mark size=${mark_size}pt" : ", mark=$marks" : ''; - - $linestyle =~ s/ /_/g; - $linestyle = { - none => ', only marks', - solid => ', solid', - dashed => ', dash={on 11pt off 8pt phase 6pt}', - short_dashes => ', dash pattern={on 6pt off 3pt}', - long_dashes => ', dash={on 20pt off 15pt phase 10pt}', - dotted => ', dotted', - long_medium_dashes => ', dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', - }->{$linestyle} - || ', solid'; - - if ($fill eq 'self') { - $fill = ", fill=$fill_color, fill opacity=$fill_opacity"; - } else { - $fill = ''; + + my $name = $data->style('name'); + if ($name) { + warn 'Duplicate plot name detected. This will most likely cause issues. ' + . 'Make sure that all names used are unique.' + if $self->{names}{$name}; + $self->{names}{$name} = 1; + # This forces the curve to be inserted invisibly if it has been named, + # but the curve would otherwise not be drawn. + push(@drawOptions, 'draw=none') if !@drawOptions; + push(@drawOptions, "name path=$name"); } - $name = ", name path=$name" if $name; - return "${smooth}color=$color, line width=${width}pt$marks$linestyle$end_markers$fill$name$tikz_options"; + push(@drawOptions, $tikz_options) if $tikz_options; + + return ([ join(', ', @drawOptions), $drawLayer ], @fillOptions ? [ join(', ', @fillOptions), $fillLayer ] : undef); +} + +sub draw_on_layer { + my ($self, $plot, $layer) = @_; + my $tikzCode; + $tikzCode .= "\\begin{scope}[on layer=$layer]\\begin{pgfonlayer}{$layer}\\clip\\axisclippath;\n" if $layer; + $tikzCode .= $plot; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $layer; + return $tikzCode; } sub draw { - my $self = shift; - my $plots = $self->plots; - my $tikzFill = ''; + my $self = shift; + my $plots = $self->plots; # Reset colors just in case. $self->{colors} = {}; - # Add Axes - my $tikzCode = $self->configure_axes; + my $tikzCode = ''; + + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { + my $color = $data->style('color') || 'default_color'; + my $layer = $data->style('layer'); - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { - my $n = $data->size; - my $color = $data->style('color') || 'default_color'; - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $tikz_options = $self->get_plot_opts($data); $tikzCode .= $self->get_color($color); + + if ($data->name eq 'vectorfield') { + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); + if ($xfunction ne '' && $yfunction ne '') { + my $width = $data->style('width'); + my $scale = $data->style('scale'); + my $arrows = $data->style('slopefield') ? '' : ', -stealth'; + my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $xfunction = $xtmp; + } + + my $yDelta = ($f->{ymax} - $f->{ymin}) / $f->{ysteps}; + my $next = $f->{ymin} + $yDelta; + my $last = $f->{ymax} + $yDelta / 2; # Adjust upward incase of rounding error in the foreach. + my $xSamples = $f->{xsteps} + 1; + $tikzCode .= $self->draw_on_layer( + "\\foreach \\i in {$f->{ymin}, $next, ..., $last}\n" + . "\\addplot[color=$color, line width=${width}pt$arrows, " + . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$xSamples, " + . "domain=$f->{xmin}:$f->{xmax}$tikz_options] {\\i};\n", + $layer + ); + } else { + warn "Vector field not created due to missing PGF functions."; + } + next; + } + + my $fill = $data->style('fill') || 'none'; + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; $tikzCode .= $self->get_color($fill_color) unless $fill eq 'none'; + my ($draw_options, $fill_options) = $self->get_options($data); + if ($data->name eq 'circle') { my $x = $data->x(0); my $y = $data->y(0); my $r = $data->style('radius'); - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x,$y) circle [radius=$r];\n"; + $tikzCode .= $self->draw_on_layer("\\fill[$fill_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", + $fill_options->[1]) + if $fill_options; + $tikzCode .= $self->draw_on_layer("\\draw[$draw_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", + $draw_options->[1]); next; } if ($data->name eq 'arc') { @@ -282,19 +565,29 @@ sub draw { my $theta2 = 180 * atan2($y3 - $y1, $x3 - $x1) / 3.14159265358979; $theta1 += 360 if $theta1 < 0; $theta2 += 360 if $theta2 < 0; - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x2,$y2) arc ($theta1:$theta2:$r);\n"; + $tikzCode .= $self->draw_on_layer( + "\\fill[$fill_options->[0]] (axis cs:$x2,$y2) " + . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", + $fill_options->[1] + ) if $fill_options; + $tikzCode .= $self->draw_on_layer( + "\\draw[$draw_options->[0]] (axis cs:$x2,$y2) " + . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", + $draw_options->[1] + ); next; } my $plot; + my $plot_options = ''; if ($data->name eq 'function') { my $f = $data->{function}; if (ref($f->{Fx}) ne 'CODE' && $f->{xvar} eq $f->{Fx}->string) { my $function = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($function ne '') { $data->update_min_max; - $tikz_options .= ", data cs=polar" if $data->style('polar'); - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + $plot_options .= ", data cs=polar" if $data->style('polar'); + $plot_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; $plot = "{$function}"; } } else { @@ -302,7 +595,7 @@ sub draw { my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($xfunction ne '' && $yfunction ne '') { $data->update_min_max; - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + $plot_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; $plot = "({$xfunction}, {$yfunction})"; } } @@ -329,73 +622,58 @@ sub draw { push(@tikzFunctionx, "(x>=$a)*(x<$last$b)*($xfunction)"); push(@tikzFunctiony, "(x>=$a)*(x<$last$b)*($yfunction)"); } - $tikz_options .= ", domain=0:1, samples=$data->{function}{steps}"; + $plot_options .= ", mark=none, domain=0:1, samples=$data->{function}{steps}"; $plot = "\n({" . join("\n+", @tikzFunctionx) . "},\n{" . join("\n+", @tikzFunctiony) . '})'; } unless ($plot) { $data->gen_data; - my $tikzData = join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $n - 1)); - $plot = "coordinates {$tikzData}"; + $plot = 'coordinates {' + . join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $data->size - 1)) . '}'; } - $tikzCode .= "\\addplot[$tikz_options] $plot;\n"; + $tikzCode .= $self->draw_on_layer("\\addplot[$fill_options->[0]$plot_options] $plot;\n", $fill_options->[1]) + if $fill_options; + $tikzCode .= $self->draw_on_layer("\\addplot[$draw_options->[0]$plot_options] $plot;\n", $draw_options->[1]); unless ($fill eq 'none' || $fill eq 'self') { - my $name = $data->style('name'); - if ($name) { - my $opacity = $data->style('fill_opacity') || 0.5; - my $fill_min = $data->style('fill_min'); - my $fill_max = $data->style('fill_max'); - my $fill_range = $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" : ''; - $opacity *= 100; - $tikzFill .= "\\addplot[$fill_color!$opacity] fill between[of=$name and $fill$fill_range];\n"; + if ($self->{names}{$fill}) { + my $name = $data->style('name'); + if ($name) { + my $opacity = $data->style('fill_opacity') || 0.5; + my $fill_min = $data->style('fill_min'); + my $fill_max = $data->style('fill_max'); + my $fill_range = + $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" : ''; + my $fill_layer = $data->style('fill_layer') || $layer; + $tikzCode .= + "\\begin{scope}[/tikz/fill between/on layer=$fill_layer]\\begin{pgfonlayer}{$fill_layer}" + . "\\clip\\axisclippath;\n" + if $fill_layer; + $tikzCode .= + "\\addplot[$fill_color, fill opacity=$opacity] fill between[of=$name and $fill$fill_range];\n"; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $fill_layer; + } else { + warn q{Unable to create fill. Missing 'name' attribute.}; + } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } } - # Add fills last to ensure all named graphs have been plotted first. - $tikzCode .= $tikzFill; - - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); - my $arrows = $data->style('slopefield') ? '' : ', -stealth'; - if ($xfunction ne '' && $yfunction ne '') { - my $color = $data->style('color'); - my $width = $data->style('width'); - my $scale = $data->style('scale'); - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $xfunction = $xtmp; - } - - $tikzCode .= $self->get_color($color); - $tikzCode .= - "\\addplot3[color=$color, line width=${width}pt$arrows, " - . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$f->{xsteps}, " - . "domain=$f->{xmin}:$f->{xmax}, domain y=$f->{ymin}:$f->{ymax}$tikz_options] {1};\n"; - } else { - warn "Vector field not created due to missing PGF functions."; - } - } # Stamps for my $stamp ($plots->data('stamp')) { - my $mark = $self->get_mark($stamp->style('symbol')); + my $mark = $self->get_mark($stamp->style('symbol')) // '*'; next unless $mark; - my $color = $stamp->style('color') || 'default_color'; - my $x = $stamp->x(0); - my $y = $stamp->y(0); - my $r = $stamp->style('radius') || 4; - $tikzCode .= $self->get_color($color) - . "\\addplot[$color, mark=$mark, mark size=${r}pt, only marks] coordinates {($x,$y)};\n"; + my $color = $stamp->style('color') || 'default_color'; + my $x = $stamp->x(0); + my $y = $stamp->y(0); + my $lineWidth = $stamp->style('width') || 2; + my $r = ($stamp->style('radius') || 4) + ($mark =~ /^[*+]/ ? $lineWidth / 2 : $mark eq 'x' ? $lineWidth : 0); + $tikzCode .= + $self->get_color($color) + . "\\addplot[$color, mark=$mark, mark size=${r}pt, line width=${lineWidth}pt, only marks] " + . "coordinates {($x,$y)};\n"; } # Labels @@ -404,30 +682,34 @@ sub draw { my $x = $label->x(0); my $y = $label->y(0); my $color = $label->style('color') || 'default_color'; - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $rotate = $label->style('rotate'); my $tikz_options = $label->style('tikz_options'); my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; + my $anchor = join(' ', + $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : (), + $h_align eq 'left' ? 'west' : $h_align eq 'right' ? 'east' : ()); $str = { - tiny => '\tiny ', - small => '\small ', - medium => '', - large => '\large ', - giant => '\Large ', + tiny => '\tiny ', + small => '\small ', + normalsize => '', + medium => '', # deprecated + large => '\large ', + Large => '\Large ', + giant => '\Large ', # deprecated + huge => '\huge ', + Huge => '\Huge ' }->{$fontsize} . $str; - $anchor .= $h_align eq 'left' ? ' west' : $h_align eq 'right' ? ' east' : ''; $tikz_options = $tikz_options ? "$color, $tikz_options" : $color; $tikz_options = "anchor=$anchor, $tikz_options" if $anchor; $tikz_options = "rotate=$rotate, $tikz_options" if $rotate; $tikzCode .= $self->get_color($color) . "\\node[$tikz_options] at (axis cs: $x,$y) {$str};\n"; } - $tikzCode .= '\end{axis}'; - $plots->{tikzCode} = $tikzCode; - $self->im->tex($tikzCode); + $plots->{tikzCode} = $self->generate_axes($tikzCode); + $self->im->tex($plots->{tikzCode}); return $plots->{tikzDebug} ? '' : $self->im->draw; } diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 64ad60e51a..a41f2e6c52 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -7,9 +7,9 @@ =head1 DESCRIPTION This macro creates a Plots object that is used to add data of different elements of a 2D plot, then draw the plot. The plots can be drawn using different -formats. Currently C (using PGFplots), C, and the legacy C -graphics format are available. Default is to use C for HTML output and -C for hardcopy. +formats. Currently C (using PGFplots) and C graphics format are +available. The default is to use C for HTML output and C for +hardcopy. Note, due to differences in features between C and C, not all options work with both. @@ -36,6 +36,41 @@ =head1 USAGE Options that start with C configure the xaxis, options that start with C configure the yaxis, and all other options are Axes styles. +In addition to the options for configuring the L, the +following options may be passed. + +=over + +=item width + +The width of the image. The default value of this option is the +C value in the environment, or C<350> if that is not set or +is C<0>. + +=item height + +The height of the image. The default value of this option is the C. If +this is explicitly set to a positive integer value, then that height will be +used. If this is C, then the height of the image will be automatically +determined. If the C style setting for the C object +is C<1>, then the height will be computed to maintain the aspect ratio of the +image. Otherwise it will be set to value of the C option. + +=item tex_size + +The size of the image in hardcopy. See L +for more details on this setting. + +=item rounded_corners + +Determines if the image will be displayed in a rectangle with rounded corners +or sharp corners. The default value for this option is C<0> which means that +sharp corners will be used. If this is set to C<1>, then rounded corners will +be used. Note that this may not work well for images that have elements of the +plot near or in the corners. + +=back + Add a function and other objects to the plot. $plot->add_function('-16t^2 + 80t + 384', 't', 0, 8, color => 'blue', width => 3); @@ -59,11 +94,11 @@ =head2 DATASETS can be added individually, or multiple at once as shown: # Add a single dataset - $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], @options); + $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], %options); # Add multiple datasets with single call $plot->add_dataset( - [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], @options1], - [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], @options2], + [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], %options1], + [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], %options2], ... ); @@ -90,11 +125,11 @@ =head2 PLOT FUNCTIONS functions can be added individually or multiple at once: # Add a single function - $plot->add_function($function, $variable, $min, $max, @options) + $plot->add_function($function, $variable, $min, $max, %options) # Add multiple functions $plot->add_function( - [$function1, $variable1, $min1, $max1, @options1], - [$function2, $variable2, $min2, $max2, @options2], + [$function1, $variable1, $min1, $max1, %options1], + [$function2, $variable2, $min2, $max2, %options2], ... ); @@ -196,7 +231,7 @@ =head2 PLOT MULTIPATH FUNCTIONS =head2 PLOT CIRCLES -Circles can be added to the plot by specifing its center and radius using the +Circles can be added to the plot by specifying its center and radius using the C<< $plot->add_circle >> method. This can either be done either one at a time or multiple at once. @@ -209,15 +244,17 @@ =head2 PLOT CIRCLES =head2 PLOT ARCS -Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> method. -This method takes three points. The first point is where the arc starts, the second point -is the center of the circle, and the third point specifies the ray from the center of -the circle the arc ends. Arcs always go in the counter clockwise direction. +Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> +method. This method takes three points. The first point is the center of the +circle, the second point is where the arc starts, and the arc ends at the point +on the circle that intersects the ray from the center of the circle pointing in +the direction of the third point. Arcs always go in the counter clockwise +direction. $plot->add_arc([$start_x, $start_y], [$center_x, $center_y], [$end_x, $end_y], %options); $plot->add_arc( - [[$start_x1, $start_y1], [$center_x1, $center_y1], [$end_x1, $end_y1], %options1], - [[$start_x2, $start_y2], [$center_x2, $center_y2], [$end_x2, $end_y2], %options2], + [[$center_x1, $center_y1], [$start_x1, $start_y1], [$end_x1, $end_y1], %options1], + [[$center_x2, $center_y2], [$start_x2, $start_y2], [$end_x2, $end_y2], %options2], ... ); @@ -262,8 +299,9 @@ =head2 PLOT VECTOR FIELDS =item xsteps, ysteps -The number of arrows drawn in each direction. Note, that in TikZ output, this cannot be -set individually so only C is used. Default: 15 +The number of steps from the domain minimum to the domain maximum at which to +draw arrows. The number of arrows drawn will be one more than the number of +steps. Default: 15 =item scale @@ -303,14 +341,17 @@ =head2 DATASET OPTIONS =item width -The line width of the plot. Default: 1 +The line width of the plot. Default: 2 =item linestyle -Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long dashes', -'long medium dashes' (alternates between long and medium dashes), or 'none'. If set -to 'none', only the points are shown (see marks for point options) For convince -underscores can also be used, such as 'long_dashes'. Default: 'solid' +Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long +dashes', 'long medium dashes' (alternates between long and medium dashes), or +'none'. If set to 'none', then the curve will not be drawn. This can be used to +show only points by setting the C option (see C for point +options), or to only show a fill region by setting the C option. For +convenience underscores can also be used, such as 'long_dashes'. +Default: 'solid' =item marks @@ -322,8 +363,7 @@ =head2 DATASET OPTIONS =item mark_size Configure the size of the marks (if shown). The size is a natural number, -and represents the point (pt) size of the mark. If the size is 0, the -default size is used. Default: 0 +and represents the point (pt) size of the mark. Default: 2 =item start_mark @@ -338,7 +378,7 @@ =head2 DATASET OPTIONS =item arrow_size Sets the arrow head size for C or C arrows. -Default: 10 +Default: 8 =item name @@ -352,8 +392,9 @@ =head2 DATASET OPTIONS If set to 'self', the object fills within itself, best used with closed datasets. If set to 'xaxis', this will fill the area between the curve and the x-axis. If set to another non-empty string, this is the name of -the other dataset to fill against. The C attribute must be set to -fill between the 'xaxis' or another curve. +the other dataset to fill against. Note that the other dataset must be +created first before attempting to fill against it. The C attribute +must be set to fill between the 'xaxis' or another curve. The following creates a filled rectangle: @@ -406,6 +447,25 @@ =head2 DATASET OPTIONS not defined, then the fill will use the full domain of the function. Default: undefined +=item layer + +The layer to draw on. Available layers are "axis background", "axis grid", +"axis ticks", "axis lines", "axis tick labels", "pre main", "main", +"axis descriptions", and "axis foreground". Note that the default order is the +order just given (from back to front). However, if C is true for +the axes, then "pre main" and "main" are after "axis background" and before +"axis grid". If this is undefined, then the default drawing layer will be used. +Default: undefined + +=item fill_layer + +The layer to place the fill region on. The curves will be drawn on the default +layer (or the layer specified by the C option) and the fill region will +be drawn on the layer specified by this option. Note that if this option is not +specified and the C option, then the curve and the fill region will both +be drawn on the specified C. See the C option above regarding +available layers to choose from. Default: undefined + =item steps This defines the number of points to generate for a dataset from a function. @@ -448,13 +508,16 @@ =head2 LABELS Similar to datasets this can be added individually or multiple at once. # Add a label at the point ($x, $y). - $plot->add_label($x, $y, label => $label, @options)> + $plot->add_label($x, $y, $label, %options) # Add multiple labels at once. $plot->add_label( - [$x1, $y1, label => $label1, @options1], - [$x2, $y2, label => $label2, @options2], + [$x1, $y1, $label1, %options1], + [$x2, $y2, $label2, %options2], ... - ); + ); + + # Deprecated way of adding a label with an option instead of the third argument. + $plot->add_label($x, $y, label => $label, %options) Labels can be configured using the following options: @@ -470,8 +533,11 @@ =head2 LABELS =item fontsize -The font size of the label used. This can be one of 'tiny', 'small', 'medium', -'large', or 'giant'. Default: 'medium' +The font size of the label used. This can be one of 'tiny', 'small', +'normalsize', 'large', 'Large', 'huge', or 'Huge' which correspond to the same +named TeX font sizes. Note that this list used to include 'medium' and 'giant' +which still work, but are deprecated. Instead of 'medium' use 'normalsize', and +instead of 'giant' use 'Large'. Default: 'normalsize' =item rotate @@ -499,8 +565,36 @@ =head2 LABELS =back +=head2 POINTS + +Points are really dataset marks (with no associated curve). Note that points +are drawn after all of the other graph objects except labels are drawn. Thus +points will always appear to be on top of everything else (except labels). + +Note that the C, C, C, and C dataset options are +valid for points. The C option is also a valid option that is an alias for +the C dataset option. The C or C options can be used to +change the symbol that is used for the point. By default the symbol is a +C. + + # Add a single point. + $plot->add_point($x1, $y1, color => $color, mark_size => $mark_size); + + # Add multiple points. + $plot->add_point( + [$x1, $y1, color => $color1, mark_size => $mark_size1], + [$x2, $y2, color => $color2, mark_size => $mark_size2], + ... + ); + + # Add a single open point. + $plot->add_point($x1, $y1, color => $color, mark => 'open_circle'); + =head2 STAMPS +Stamps and the C method are deprecated. DO NOT USE THEM. Use the +C or C methods instead. + Stamps are a single point with a mark drawn at the given point. Stamps can be added individually or multiple at once: @@ -513,24 +607,8 @@ =head2 STAMPS ... ); -Stamps are here for backwards compatibility with WWplot and GD output, and are -equivalent to creating a dataset with one point when not using GD output (with -the small difference that stamps are added after all other datasets have been added). - -=head2 FILL REGIONS - -Fill regions define a point which GD will fill with a color until it hits a boundary curve. -This is only here for backwards comparability with WWplot and GD output. This will not -work with TikZ output, instead using the fill methods mentioned above. - - # Add a single fill region. - $plot->add_fill_region($x1, $y1, $color); - # Add multiple fill regions. - $plot->add_fill_region( - [$x1, $y1, $color1], - [$x2, $y2, $color2], - ... - ); +Adding a stamp is equivalent to creating a dataset with one point with the +exception that stamps are added after all other datasets have been added. =head2 COLORS @@ -564,6 +642,45 @@ =head2 COLORS ... ); +Note that SVG colors can also be used directly by name without being defined via +the C method. See section 4.3 of the +L +documentation for a list of available color names. + +=head2 EXTRA CODE + +Additional JavaScript and TikZ code may be added to draw elements that are not +provided for by this macro and its underlying modules. To add JavaScript code +set the C key on the C<$plot> object, and to add TikZ code set +the C key on the C<$plot> object. The JavaScript code will have +access to the C object via the variable C, and will be +inserted after all of the other code generated by this macro, and before the +C call is executed. The TikZ code will be inserted +after all of the other code generated by this macro, and before the pgfplots +C environment is ended. + +Note that if one of these is used, then both should be used to ensure that both +the JavaScript plot image (used in HTML) and the TikZ plot image (used in +hardcopy) are the same (or at least as close as possible). + +For example, + + $plot = Plot(); + + $plot->{extra_js_code} = << 'END_JS_CODE'; + board.create( + 'line', + [[0, 0], [1, 1]], + { straightLast: false, straightFirst: false, color: 'blue' } + ); + END_JS_CODE + + $plot->{extra_tikz_code} = "\draw[line width = 2pt, blue] (axis cs: 0, 0) -- (axis cs: 1, 1);"; + +Note that the above code is not an actual example that should be used as those +lines could be created using this macro directly. It is only included here to +demonstrate how to use these options. + =head1 TIKZ DEBUGGING When using Tikz output, the pgfplots code used to create the plot is stored in C<< $plot->{tikzCode} >>, From 3bab1de1976a1de16c5cace3d0c7ac633dd477a1 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 4 Nov 2025 19:57:17 -0600 Subject: [PATCH 086/111] Optimize the TikZ format when a draw and fill occur separately. This is accomplished using the spath3 TikZ library. To make this work all paths need to be named, and if a draw and fill are done separately, then the fill just uses the path from the draw so it does not need to be recomputed. Additionally refactor multipaths to make the TikZ format much more efficient, as well as to make multipaths more versatile. Both the TikZ and JSXGraph formats are done differently now. They both join the paths in a completely different way that does not use the x transform (so the `function_string` x transform code is now never used in fact). Instead in TikZ the spath3 TikZ library is used and the paths are drawn individually, and then concatenated. For the JSXGraph format the paths are created individually and their data points concatenated to form a single curve. The result allows for more versatility since now paths do not need to end where the next path starts. The paths are connected by a line segment if needed. For JSXGraph this just works with the concatenation approach. For TikZ this has to be added. Note this actually happened before with the previous TikZ implementation, but not for the JSXGraph implementation. The most important thing is that with this implementation the time that it takes for TeX to run for multipaths is greatly reduced. For the example in the POD and the current TikZ code it takes about 3 seconds for TikZ to run, and the CPU usage is quite high. If the fill and draw are on different layers so that the fill and draw occur separately, it takes even longer. With the new code it takes about 1 second for either case and the CPU usage is much less. One reason for this is that the number of steps needed with the new approach (which is now per curve) is much less. Previously 500 steps were used (by default) for the entire curve. Now 30 (by default) are used for each curve. Note that this can now be optimized and steps set per curve. There is also a new `cycle` option for multipaths. If `cycly => 1` is set for a `multipath`, then a line segment will be inserted from the end of the last path to the start of the first in the case that the last path does not end at the start of the first, thus closing the path. --- lib/Plots/JSXGraph.pm | 74 +++++++++++++++++-------- lib/Plots/Plot.pm | 6 +- lib/Plots/Tikz.pm | 125 +++++++++++++++++++++++++++--------------- macros/graph/plots.pl | 55 +++++++++++++++---- 4 files changed, 180 insertions(+), 80 deletions(-) diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index af1248a664..0d0296e3f7 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -390,37 +390,65 @@ sub add_multipath { my ($self, $data) = @_; my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); my $var = $data->{function}{var}; my $curve_name = $data->style('name'); warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' if $curve_name && $self->{names}{$curve_name}; $self->{names}{$curve_name} = 1 if $curve_name; my ($plotOptions, $fillOptions) = $self->get_options($data); - my $jsFunctionx = 'function (x){'; - my $jsFunctiony = 'function (x){'; + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + + $count = 0; + ++$count while ($self->{names}{"${curve_name}_$count"}); + my $curve_parts_name = "${curve_name}_$count"; + $self->{names}{$curve_parts_name} = 1; + + $self->{JS} .= "const $curve_parts_name = [\n"; + + my $cycle = $data->style('cycle'); + my ($start_x, $start_y) = ('', ''); for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'js', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'js', $var, undef, $t); - $jsFunctionx .= "if(x<=$b){return $xfunction;}"; - $jsFunctiony .= "if(x<=$b){return $yfunction;}"; + + ($start_x, $start_y) = + (', ' . $path->{Fx}->eval($var => $path->{tmin}), ', ' . $path->{Fy}->eval($var => $path->{tmin})) + if $cycle && $_ == 0; + + my $xfunction = $data->function_string($path->{Fx}, 'js', $var); + my $yfunction = $data->function_string($path->{Fy}, 'js', $var); + + $self->{JS} .= + "board.create('curve', " + . "[(x) => $xfunction, (x) => $yfunction, $path->{tmin}, $path->{tmax}], { visible: false }),\n"; } - $jsFunctionx .= 'return 0;}'; - $jsFunctiony .= 'return 0;}'; - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $plotOptions);" if $plotOptions; - $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $fillOptions);" if $fillOptions; + $self->{JS} .= "];\n"; + + if ($plotOptions) { + $self->{JS} .= <<~ "END_JS"; + const curve_$curve_name = board.create('curve', [[], []], $plotOptions); + curve_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))$start_x); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))$start_y); + }; + END_JS + } + if ($fillOptions) { + $self->{JS} .= <<~ "END_JS"; + const fill_$curve_name = board.create('curve', [[], []], $fillOptions); + fill_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))); + }; + END_JS + } return; } @@ -530,8 +558,8 @@ sub add_circle { my $r = $data->style('radius'); my ($circleOptions, $fillOptions) = $self->get_options($data); - $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);"; - $self->{JS} .= "board.create('circle', [[$x, $y], $r], $fillOptions);" if $fillOptions; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);" if $circleOptions; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $fillOptions);" if $fillOptions; return; } @@ -547,7 +575,7 @@ sub add_arc { radiusPoint => { visible => 0 }, ); - $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);"; + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);" if $arcOptions; $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $fillOptions);" if $fillOptions; return; } diff --git a/lib/Plots/Plot.pm b/lib/Plots/Plot.pm index 775a06365e..85bbab0d8e 100644 --- a/lib/Plots/Plot.pm +++ b/lib/Plots/Plot.pm @@ -294,15 +294,15 @@ sub add_function { sub add_multipath { my ($self, $paths, $var, %options) = @_; my $data = Plots::Data->new(name => 'multipath'); - my $steps = 100 * @$paths; # Steps set high to help Tikz deal with boundaries of paths. - $steps = delete $options{steps} if $options{steps}; + my $steps = (delete $options{steps}) || 30; $data->{context} = $self->context; $data->{paths} = [ map { { Fx => $data->get_math_object($_->[0], $var), Fy => $data->get_math_object($_->[1], $var), tmin => $data->str_to_real($_->[2]), - tmax => $data->str_to_real($_->[3]) + tmax => $data->str_to_real($_->[3]), + @$_[ 4 .. $#$_ ] } } @$paths ]; $data->{function} = { var => $var, steps => $steps }; diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 02cedace32..a3276f197e 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -19,7 +19,7 @@ sub new { $image->svgMethod(eval('$main::envir{latexImageSVGMethod}') // 'dvisvgm'); $image->convertOptions(eval('$main::envir{latexImageConvertOptions}') // { input => {}, output => {} }); $image->ext($plots->ext); - $image->tikzLibraries('arrows.meta,plotmarks,calc'); + $image->tikzLibraries('arrows.meta,plotmarks,calc,spath3'); $image->texPackages(['pgfplots']); # Set the pgfplots compatibility, add the pgfplots fillbetween library, define a save @@ -458,18 +458,8 @@ sub get_options { my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; my $fill_opacity = $data->style('fill_opacity') || 0.5; push(@drawOptions, "fill=$fill_color", "fill opacity=$fill_opacity"); - } - - my $name = $data->style('name'); - if ($name) { - warn 'Duplicate plot name detected. This will most likely cause issues. ' - . 'Make sure that all names used are unique.' - if $self->{names}{$name}; - $self->{names}{$name} = 1; - # This forces the curve to be inserted invisibly if it has been named, - # but the curve would otherwise not be drawn. - push(@drawOptions, 'draw=none') if !@drawOptions; - push(@drawOptions, "name path=$name"); + } elsif (!@drawOptions) { + push(@drawOptions, 'draw=none'); } push(@drawOptions, $tikz_options) if $tikz_options; @@ -539,6 +529,19 @@ sub draw { next; } + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. ' + . 'Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + my $fill = $data->style('fill') || 'none'; my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; $tikzCode .= $self->get_color($fill_color) unless $fill eq 'none'; @@ -549,11 +552,12 @@ sub draw { my $x = $data->x(0); my $y = $data->y(0); my $r = $data->style('radius'); - $tikzCode .= $self->draw_on_layer("\\fill[$fill_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", - $fill_options->[1]) - if $fill_options; - $tikzCode .= $self->draw_on_layer("\\draw[$draw_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", + $tikzCode .= $self->draw_on_layer( + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", $draw_options->[1]); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } if ($data->name eq 'arc') { @@ -566,20 +570,19 @@ sub draw { $theta1 += 360 if $theta1 < 0; $theta2 += 360 if $theta2 < 0; $tikzCode .= $self->draw_on_layer( - "\\fill[$fill_options->[0]] (axis cs:$x2,$y2) " - . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", - $fill_options->[1] - ) if $fill_options; - $tikzCode .= $self->draw_on_layer( - "\\draw[$draw_options->[0]] (axis cs:$x2,$y2) " + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x2,$y2) " . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", $draw_options->[1] ); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } my $plot; my $plot_options = ''; + if ($data->name eq 'function') { my $f = $data->{function}; if (ref($f->{Fx}) ne 'CODE' && $f->{xvar} eq $f->{Fx}->string) { @@ -599,43 +602,79 @@ sub draw { $plot = "({$xfunction}, {$yfunction})"; } } - } - if ($data->name eq 'multipath') { + } elsif ($data->name eq 'multipath') { my $var = $data->{function}{var}; my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); my @tikzFunctionx; my @tikzFunctiony; + + # This saves the internal path names and the endpoints of the paths. The endpoints are used to determine if + # the paths meet at the endpoints. If the end of one path is not at the same place that the next path + # starts, then the line segment from the first path end to the next path start is inserted. + my @pathData; + + my $count = 0; + for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var, undef, $t); - my $last = $_ == $#paths ? '=' : ''; - push(@tikzFunctionx, "(x>=$a)*(x<$last$b)*($xfunction)"); - push(@tikzFunctiony, "(x>=$a)*(x<$last$b)*($yfunction)"); + + my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var); + my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var); + + ++$count while $self->{names}{"${curve_name}_$count"}; + push( + @pathData, + [ + "${curve_name}_$count", + $path->{Fx}->eval($var => $path->{tmin}), + $path->{Fy}->eval($var => $path->{tmin}), + $path->{Fx}->eval($var => $path->{tmax}), + $path->{Fy}->eval($var => $path->{tmax}) + ] + ); + $self->{names}{ $pathData[-1][0] } = 1; + + my $steps = $path->{steps} // $data->{function}{steps}; + + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none, domain=$path->{tmin}:$path->{tmax}, " + . "samples=$steps] ({$xfunction}, {$yfunction});\n"; } - $plot_options .= ", mark=none, domain=0:1, samples=$data->{function}{steps}"; - $plot = "\n({" . join("\n+", @tikzFunctionx) . "},\n{" . join("\n+", @tikzFunctiony) . '})'; + + $tikzCode .= "\\path[name path=$curve_name] " . join( + ' ', + map { + ( + $_ == 0 || ($pathData[ $_ - 1 ][3] == $pathData[$_][1] + && $pathData[ $_ - 1 ][4] == $pathData[$_][2]) + ? '' + : "-- (spath cs:$pathData[$_ - 1][0] 1) -- (spath cs:$pathData[$_][0] 0) " + ) + . "[spath/append no move=$pathData[$_][0]]" + } 0 .. $#pathData + ) . ($data->style('cycle') ? '-- cycle' : '') . ";\n"; + + $plot = 'skip'; + $tikzCode .= + $self->draw_on_layer("\\draw[$draw_options->[0], spath/use=$curve_name];\n", $draw_options->[1]); } + unless ($plot) { $data->gen_data; $plot = 'coordinates {' . join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $data->size - 1)) . '}'; } - $tikzCode .= $self->draw_on_layer("\\addplot[$fill_options->[0]$plot_options] $plot;\n", $fill_options->[1]) + + # 'skip' is a special value of $plot for a multipath which has already been drawn. + $tikzCode .= $self->draw_on_layer("\\addplot[name path=$curve_name, $draw_options->[0]$plot_options] $plot;\n", + $draw_options->[1]) + unless $plot eq 'skip'; + $tikzCode .= $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) if $fill_options; - $tikzCode .= $self->draw_on_layer("\\addplot[$draw_options->[0]$plot_options] $plot;\n", $draw_options->[1]); unless ($fill eq 'none' || $fill eq 'self') { if ($self->{names}{$fill}) { + # Make sure this is the name from the data style attribute, and not an internal name. my $name = $data->style('name'); if ($name) { my $opacity = $data->style('fill_opacity') || 0.5; diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index a41f2e6c52..6beeef99fc 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -197,26 +197,34 @@ =head2 PLOT FUNCTIONS =head2 PLOT MULTIPATH FUNCTIONS -A multipath function is defined using multiple parametric paths pieced together into into a single -curve, whose primary use is to create a closed region to be filled using multiple boundaries. -This is done by providing a list of parametric functions, the name of the parameter, and a list -of options. +A multipath function is defined using multiple parametric paths pieced together +into into a single curve, whose primary use is to create a closed region to be +filled using multiple boundaries. This is done by providing a list of +parametric functions, the name of the parameter, and a list of options. $plot->add_multipath( [ - [ $function_x1, $function_y1, $min1, $max1 ], - [ $function_x2, $function_y2, $min2, $max2 ], + [ $function_x1, $function_y1, $min1, $max1, %path_options ], + [ $function_x2, $function_y2, $min2, $max2, %path_options ], ... ], $variable, %options ); -The paths have to be listed in the order they are followed, but the minimum/maximum values -of the parameter can match the parametrization. The following example creates a sector of -radius 5 between pi/4 and 3pi/4, by first drawing the line (0,0) to (5sqrt(2),5/sqrt(2)), -then the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the final line from -(-5sqrt(2), 5sqrt(2)) back to the origin. +Note that C<%path_options> can be specified for each path. At this point, the +only supported individual path option is C, if specified, then that +number of steps will be used for that path in the TikZ format. If not specified +the number of steps for the multipath will be used. That defaults to 30, but can +be changed by passing the C option in the general C<%options> for the +multipath. + +The paths have to be listed in the order they are followed, but the +minimum/maximum values of the parameter can match the parametrization. The +following example creates a sector of radius 5 between pi/4 and 3pi/4, by first +drawing the line from (0,0) to (5sqrt(2),5/sqrt(2)), then the arc of the circle +of radius 5 from pi/4 to 3pi/4, followed by the final line from (-5sqrt(2), +5sqrt(2)) back to the origin. $plot->add_multipath( [ @@ -229,6 +237,31 @@ =head2 PLOT MULTIPATH FUNCTIONS fill => 'self', ); +Note that the ending point of one path does not need to be the same as the +starting point of the next. In this case a line segment will connect the end of +the first path to the start of the next. Additionally, if C<< cycle => 1 >> is +added to the C<%options> for the multipath, and the last path does not end where +the first path starts, then a line segment will connect the end of the last path +to the start of the first path. For example, the following path draws the top +half of a circle of radius two centered at the point (0, 2), followed by the +line segment from (-2, 0) to (2, 0). The line segment from (-2, 2) to (-2, 0) is +implicitly added to connect the end of the first path to the beginning of the +second path. The cycle option is added to close the path with the line segment +from (2, 0) to (2, 2). Note that drawing of the line is optimized by using only +2 steps, and the fill region is drawn on the "axis background" layer. + + $plot->add_multipath( + [ + [ '2 cos(t) + 5', '2 sin(t) - 5', '0', 'pi' ], + [ 't', '-8', '3', '7', steps => 2 ] + ], + 't', + color => 'green', + fill => 'self', + fill_layer => 'axis background', + cycle => 1 + ); + =head2 PLOT CIRCLES Circles can be added to the plot by specifying its center and radius using the From 90b8cce4c3aa5ee8b89623b2ef8e9c3ca071692b Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 6 Nov 2025 07:05:37 -0600 Subject: [PATCH 087/111] Implement the `continue`, `continue_left`, and `continue_right` options in the TikZ format. This was an inconsistency between the two formats. Basically, the JSXGraph format did not honor the function max if `continue` or `continue_right` was set, but the TikZ format did. This could result in a function graph continuing to the right in the JSXGraph format, but not in the TikZ format (assuming the board bounds go further to the right). This just makes the TikZ output use the axes max instead of the function max in this case. Of course if `continue` and `continue_left` are set then the axes min is used instead of the function min. --- lib/Plots/Tikz.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index a3276f197e..6da6314397 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -589,8 +589,11 @@ sub draw { my $function = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($function ne '') { $data->update_min_max; + my ($axes_xmin, undef, $axes_xmax) = $plots->axes->bounds; + my $min = $data->style('continue') || $data->style('continue_left') ? $axes_xmin : $f->{xmin}; + my $max = $data->style('continue') || $data->style('continue_right') ? $axes_xmax : $f->{xmax}; $plot_options .= ", data cs=polar" if $data->style('polar'); - $plot_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + $plot_options .= ", domain=$min:$max, samples=$f->{xsteps}"; $plot = "{$function}"; } } else { From 7842d1d636d87ba91460e6a55d66ff3eca056792 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 11 Nov 2025 06:56:22 -0600 Subject: [PATCH 088/111] Fix issues with arcs. Adding 360 degrees to the computed theta1 if theta1 is negative and independently doing the same for theta2 is incorrect. For example, if theta1 is negative, but theta2 is positive, then that can result in theta1 being greater than theta2 and give an unintended result, and is inconsistent with the JSXGraph result in this case. What should happen is that 360 be added to theta2 if theta2 is less than or equal to theta1, and theta1 should never be modified. This gives consistent results with the JSXGraph arc in all cases (except the case below, and that is made consistent with the JSXGraph change in this commit). JSXGraph is not capable of drawing a 360 degree arc (i.e., the case that an arc starts and ends at the same point). So if the start and end point are the same, then move the end point back around the circle a tiny amount so that JSXGraph will draw the entire circle. --- lib/Plots/JSXGraph.pm | 8 ++++++++ lib/Plots/Tikz.pm | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 0d0296e3f7..a57450e775 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -575,6 +575,14 @@ sub add_arc { radiusPoint => { visible => 0 }, ); + # JSXGraph arcs cannot make a 360 degree revolution. So in the case that the start and end point are the same, + # move the end point back around the circle a tiny amount. + if ($x2 == $x3 && $y2 == $y3) { + my $theta = atan2($y2 - $y1, $x2 - $x1) + 2 * 3.14159265358979 - 0.0001; + $x3 = $x1 + cos($theta); + $y3 = $y1 + sin($theta); + } + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);" if $arcOptions; $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $fillOptions);" if $fillOptions; return; diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 6da6314397..00379d42ee 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -567,8 +567,7 @@ sub draw { my $r = sqrt(($x2 - $x1)**2 + ($y2 - $y1)**2); my $theta1 = 180 * atan2($y2 - $y1, $x2 - $x1) / 3.14159265358979; my $theta2 = 180 * atan2($y3 - $y1, $x3 - $x1) / 3.14159265358979; - $theta1 += 360 if $theta1 < 0; - $theta2 += 360 if $theta2 < 0; + $theta2 += 360 if $theta2 <= $theta1; $tikzCode .= $self->draw_on_layer( "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x2,$y2) " . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", From 6789b0916418a56e459858a83ab6323d94ebc50d Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 13 Nov 2025 06:19:11 -0600 Subject: [PATCH 089/111] Some label positioning improvements. There are two new options for positioning labels, `anchor` and `padding`. The `padding` option makes it possible for the author to easily set the padding for the label. This is css padding in pixels for the JSXGraph format, and the value of the node `inner sep` in points for the TikZ format. The `anchor` option is an alternate positioning approach to the current `h_align` and `v_align` approach. See the POD documentation for a (hopefully) good explanation of this option. Basically, in the TikZ format this is the value for the node `anchor` option in degrees. Since JSXGraph doesn't provide such an option, this had to be implemented using a transformation. This is useful for positioning labels for angles. --- htdocs/js/Plots/plots.js | 190 ++++++++++++++++++++++++--------------- lib/Plots/JSXGraph.pm | 18 ++-- lib/Plots/Tikz.pm | 8 +- macros/graph/plots.pl | 15 ++++ 4 files changed, 150 insertions(+), 81 deletions(-) diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js index df2624b035..46cdb16934 100644 --- a/htdocs/js/Plots/plots.js +++ b/htdocs/js/Plots/plots.js @@ -53,83 +53,129 @@ const PGplots = { board.containerObj.after(descriptionSpan); board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); - // Convert a decimal number into a fraction or mixed number. This is basically the JXG.toFraction method - // except that the "mixed" parameter is added, and it returns an improper fraction if mixed is false. - const toFraction = (x, useTeX, mixed, order) => { - const arr = JXG.Math.decToFraction(x, order); - - if (arr[1] === 0 && arr[2] === 0) { - return '0'; - } else { - let str = ''; - // Sign - if (arr[0] < 0) str += '-'; - if (arr[2] === 0) { - // Integer - str += arr[1]; - } else if (!(arr[2] === 1 && arr[3] === 1)) { - // Proper fraction - if (mixed) { - if (arr[1] !== 0) str += arr[1] + ' '; - if (useTeX) str += `\\frac{${arr[2]}}{${arr[3]}}`; - else str += `${arr[2]}/${arr[3]}`; + // This object is passed to the plotContents method as its second argument and exposes these methods (and + // potentially other things in the future) to the code that is called in that method. So the JavaScript code + // generated in JSXGraph.pm can use these methods. + const plot = { + // Convert a decimal number into a fraction or mixed number. This is basically the JXG.toFraction method + // except that the "mixed" parameter is added, and it returns an improper fraction if mixed is false. + toFraction(x, useTeX, mixed, order) { + const arr = JXG.Math.decToFraction(x, order); + + if (arr[1] === 0 && arr[2] === 0) { + return '0'; + } else { + let str = ''; + // Sign + if (arr[0] < 0) str += '-'; + if (arr[2] === 0) { + // Integer + str += arr[1]; + } else if (!(arr[2] === 1 && arr[3] === 1)) { + // Proper fraction + if (mixed) { + if (arr[1] !== 0) str += arr[1] + ' '; + if (useTeX) str += `\\frac{${arr[2]}}{${arr[3]}}`; + else str += `${arr[2]}/${arr[3]}`; + } else { + if (useTeX) str += `\\frac{${arr[3] * arr[1] + arr[2]}}{${arr[3]}}`; + else str += `${arr[3] * arr[1] + arr[2]}/${arr[3]}`; + } + } + return str; + } + }, + + // Override the default axis generateLabelText method so that 0 is displayed + // using MathJax if the axis is configured to show tick labels using MathJax. + generateLabelText(tick, zero, value) { + if (JXG.exists(value)) return this.formatLabelText(value); + const distance = this.getDistanceFromZero(zero, tick); + return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale); + }, + + trimTrailingZeros(value) { + if (value.indexOf('.') > -1 && value.endsWith('0')) { + value = value.replace(/0+$/, ''); + // Remove the decimal if it is now at the end. + value = value.replace(/\.$/, ''); + } + return value; + }, + + // Override the formatLabelText method for the axes ticks so that + // better number formats can be used for tick labels. + formatLabelText(value) { + let labelText; + + if (JXG.isNumber(value)) { + if (this.visProp.label.format === 'fraction' || this.visProp.label.format === 'mixed') { + labelText = plot.toFraction( + value, + this.visProp.label.usemathjax, + this.visProp.label.format === 'mixed' + ); + } else if (this.visProp.label.format === 'scinot') { + const [mantissa, exponent] = value.toExponential(this.visProp.digits).toString().split('e'); + labelText = this.visProp.label.usemathjax + ? `${plot.trimTrailingZeros(mantissa)}\\cdot 10^{${exponent}}` + : `${plot.trimTrailingZeros(mantissa)} x 10^${exponent}`; } else { - if (useTeX) str += `\\frac{${arr[3] * arr[1] + arr[2]}}{${arr[3]}}`; - else str += `${arr[3] * arr[1] + arr[2]}/${arr[3]}`; + labelText = plot.trimTrailingZeros(value.toFixed(this.visProp.digits).toString()); } + } else { + labelText = value.toString(); } - return str; - } - }; - // Override the default axis generateLabelText method so that 0 is displayed - // using MathJax if the axis is configured to show tick labels using MathJax. - const generateLabelText = function (tick, zero, value) { - if (JXG.exists(value)) return this.formatLabelText(value); - const distance = this.getDistanceFromZero(zero, tick); - return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale); - }; + if (this.visProp.scalesymbol.length > 0) { + if (labelText === '1') labelText = this.visProp.scalesymbol; + else if (labelText === '-1') labelText = `-${this.visProp.scalesymbol}`; + else if (labelText !== '0') labelText = labelText + this.visProp.scalesymbol; + } - const trimTrailingZeros = (value) => { - if (value.indexOf('.') > -1 && value.endsWith('0')) { - value = value.replace(/0+$/, ''); - // Remove the decimal if it is now at the end. - value = value.replace(/\.$/, ''); - } - return value; - }; + return this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + }, + + createLabel(x, y, text, options) { + const anchor = options.angleAnchor; + delete options.angleAnchor; + const rotate = options.rotate; + delete options.rotate; - // Override the formatLabelText method for the axes ticks so that - // better number formats can be used for tick labels. - const formatLabelText = function (value) { - let labelText; - - if (JXG.isNumber(value)) { - if (this.visProp.label.format === 'fraction' || this.visProp.label.format === 'mixed') { - labelText = toFraction( - value, - this.visProp.label.usemathjax, - this.visProp.label.format === 'mixed' + const textElement = board.create('text', [x, y, text], options); + + if (typeof anchor !== 'undefined') { + const cosA = Math.cos((anchor * Math.PI) / 180); + const sinA = Math.sin((anchor * Math.PI) / 180); + + const transform = board.create( + 'transform', + [ + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? (-h / 2 / Math.abs(sinA)) * cosA + : ((cosA < 0 ? 1 : -1) * w) / 2) / board.unitX + ); + }, + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? ((sinA < 0 ? 1 : -1) * h) / 2 + : (-w / 2 / Math.abs(cosA)) * sinA) / board.unitY + ); + } + ], + { type: 'translate' } ); - } else if (this.visProp.label.format === 'scinot') { - const [mantissa, exponent] = value.toExponential(this.visProp.digits).toString().split('e'); - labelText = this.visProp.label.usemathjax - ? `${trimTrailingZeros(mantissa)}\\cdot 10^{${exponent}}` - : `${trimTrailingZeros(mantissa)} x 10^${exponent}`; - } else { - labelText = trimTrailingZeros(value.toFixed(this.visProp.digits).toString()); + transform.bindTo(textElement); } - } else { - labelText = value.toString(); - } + if (rotate) textElement.addRotation(rotate); - if (this.visProp.scalesymbol.length > 0) { - if (labelText === '1') labelText = this.visProp.scalesymbol; - else if (labelText === '-1') labelText = `-${this.visProp.scalesymbol}`; - else if (labelText !== '0') labelText = labelText + this.visProp.scalesymbol; + return textElement; } - - return this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; }; board.suspendUpdate(); @@ -329,8 +375,8 @@ const PGplots = { options.xAxis.overrideOptions ?? {} ) ); - xAxis.defaultTicks.generateLabelText = generateLabelText; - xAxis.defaultTicks.formatLabelText = formatLabelText; + xAxis.defaultTicks.generateLabelText = plot.generateLabelText; + xAxis.defaultTicks.formatLabelText = plot.formatLabelText; if (options.xAxis.location !== 'middle') { board.create( @@ -424,8 +470,8 @@ const PGplots = { options.yAxis.overrideOptions ?? {} ) ); - yAxis.defaultTicks.generateLabelText = generateLabelText; - yAxis.defaultTicks.formatLabelText = formatLabelText; + yAxis.defaultTicks.generateLabelText = plot.generateLabelText; + yAxis.defaultTicks.formatLabelText = plot.formatLabelText; if (options.yAxis.location !== 'center') { board.create( @@ -448,7 +494,7 @@ const PGplots = { } } - plotContents(board); + plotContents(board, plot); board.unsuspendUpdate(); diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index a57450e775..2a68f7d3c1 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -149,7 +149,7 @@ sub HTML { (async () => { const id = 'jsxgraph-plot-$self->{name}'; const options = ${\(Mojo::JSON::encode_json($options))}; - const plotContents = (board) => { $self->{JS}$plots->{extra_js_code} }; + const plotContents = (board, plot) => { $self->{JS}$plots->{extra_js_code} }; if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', async () => { await PGplots.plot(id, plotContents, options); }); @@ -633,6 +633,9 @@ sub draw { my $fontsize = $label->style('fontsize') || 'normalsize'; my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; + my $anchor = $label->style('anchor'); + my $rotate = $label->style('rotate'); + my $padding = $label->style('padding') || 4; my $textOptions = Mojo::JSON::encode_json({ fontSize => { tiny => 8, @@ -646,17 +649,18 @@ sub draw { huge => 20, Huge => 23 }->{$fontsize}, - $label->style('rotate') ? (rotate => $label->style('rotate')) : (), strokeColor => $self->get_color($label->style('color')), - anchorX => $h_align eq 'center' ? 'middle' : $h_align, - anchorY => $v_align, - cssStyle => 'padding: 3px 5px;', - useMathJax => 1, + $anchor ne '' + ? (angleAnchor => $anchor, anchorX => 'middle', anchorY => 'middle') + : (anchorX => $h_align eq 'center' ? 'middle' : $h_align, anchorY => $v_align), + $rotate ? (rotate => $rotate) : (), + cssStyle => "line-height: 1; padding: ${padding}px;", + useMathJax => 1, }); $textOptions = "JXG.merge($textOptions, " . Mojo::JSON::encode_json($label->style('jsx_options')) . ')' if $label->style('jsx_options'); - $self->{JS} .= "board.create('text', [$x, $y, '$str'], $textOptions);"; + $self->{JS} .= "plot.createLabel($x, $y, '$str', $textOptions);"; } # JSXGraph only produces HTML graphs and uses TikZ for hadrcopy. diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 00379d42ee..101bf4d7a7 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -728,9 +728,12 @@ sub draw { my $tikz_options = $label->style('tikz_options'); my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = join(' ', + my $anchor = $label->style('anchor'); + $anchor = join(' ', $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : (), - $h_align eq 'left' ? 'west' : $h_align eq 'right' ? 'east' : ()); + $h_align eq 'left' ? 'west' : $h_align eq 'right' ? 'east' : ()) + if $anchor eq ''; + my $padding = $label->style('padding') || 4; $str = { tiny => '\tiny ', small => '\small ', @@ -746,6 +749,7 @@ sub draw { $tikz_options = $tikz_options ? "$color, $tikz_options" : $color; $tikz_options = "anchor=$anchor, $tikz_options" if $anchor; $tikz_options = "rotate=$rotate, $tikz_options" if $rotate; + $tikz_options = "inner sep=${padding}pt, $tikz_options"; $tikzCode .= $self->get_color($color) . "\\node[$tikz_options] at (axis cs: $x,$y) {$str};\n"; } diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 6beeef99fc..9674eedd7b 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -588,6 +588,21 @@ =head2 LABELS that states which end of the label is placed at the label's position. Can be one of 'top', 'middle', or 'bottom'. Default: 'middle' +=item anchor + +The angle in degrees of the label anchor relative to the center of the text. In +other words, the text will be positioned relative to the point on the rectangle +encompassing the label text (including C) where a ray shot from the +text center with the given angle hits the rectangle. This is an alternate method +for positioning the text relative to the label position. If this is set, then +C and C are not used. This is particularly useful for +positioning text when labeling angles. Default: '' + +=item padding + +This is the horizontal and vertical padding applied to the text of the label (in +pixels for the JSXGraph format, and in points for the TikZ format). Default: 4 + =item jsx_options An hash reference of options to pass to JSXGraph text object. From 5816b2ca528b5c37170e067429826b92aeb588c6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 11 Nov 2025 05:05:02 -0600 Subject: [PATCH 090/111] Make multipath allow points. --- lib/Plots/JSXGraph.pm | 6 ++++++ lib/Plots/Plot.pm | 18 +++++++++++------- lib/Plots/Tikz.pm | 30 ++++++++++++++++++------------ macros/graph/plots.pl | 31 +++++++++++++++++++------------ 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 2a68f7d3c1..c21761f528 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -417,6 +417,12 @@ sub add_multipath { for (0 .. $#paths) { my $path = $paths[$_]; + if (ref $path eq 'ARRAY') { + ($start_x, $start_y) = (', ' . $path->[0], ', ' . $path->[1]) if $cycle && $_ == 0; + $self->{JS} .= "board.create('curve', [[$path->[0]], [$path->[1]]], { visible: false }),\n"; + next; + } + ($start_x, $start_y) = (', ' . $path->{Fx}->eval($var => $path->{tmin}), ', ' . $path->{Fy}->eval($var => $path->{tmin})) if $cycle && $_ == 0; diff --git a/lib/Plots/Plot.pm b/lib/Plots/Plot.pm index 85bbab0d8e..b03262739f 100644 --- a/lib/Plots/Plot.pm +++ b/lib/Plots/Plot.pm @@ -297,13 +297,17 @@ sub add_multipath { my $steps = (delete $options{steps}) || 30; $data->{context} = $self->context; $data->{paths} = [ - map { { - Fx => $data->get_math_object($_->[0], $var), - Fy => $data->get_math_object($_->[1], $var), - tmin => $data->str_to_real($_->[2]), - tmax => $data->str_to_real($_->[3]), - @$_[ 4 .. $#$_ ] - } } @$paths + map { + @$_ == 2 + ? [@$_] + : { + Fx => $data->get_math_object($_->[0], $var), + Fy => $data->get_math_object($_->[1], $var), + tmin => $data->str_to_real($_->[2]), + tmax => $data->str_to_real($_->[3]), + @$_[ 4 .. $#$_ ] + } + } @$paths ]; $data->{function} = { var => $var, steps => $steps }; $data->style(color => 'default_color', width => 2, mark_size => 2, %options); diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 101bf4d7a7..05f538e95f 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -620,21 +620,27 @@ sub draw { for (0 .. $#paths) { my $path = $paths[$_]; - my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var); - my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var); - ++$count while $self->{names}{"${curve_name}_$count"}; + push(@pathData, ["${curve_name}_$count"]); + $self->{names}{ $pathData[-1][0] } = 1; + + if (ref $path eq 'ARRAY') { + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none] coordinates {($path->[0], $path->[1])};\n"; + push(@{ $pathData[-1] }, @$path, @$path); + next; + } + push( - @pathData, - [ - "${curve_name}_$count", - $path->{Fx}->eval($var => $path->{tmin}), - $path->{Fy}->eval($var => $path->{tmin}), - $path->{Fx}->eval($var => $path->{tmax}), - $path->{Fy}->eval($var => $path->{tmax}) - ] + @{ $pathData[-1] }, + $path->{Fx}->eval($var => $path->{tmin}), + $path->{Fy}->eval($var => $path->{tmin}), + $path->{Fx}->eval($var => $path->{tmax}), + $path->{Fy}->eval($var => $path->{tmax}) ); - $self->{names}{ $pathData[-1][0] } = 1; + + my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var); + my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var); my $steps = $path->{steps} // $data->{function}{steps}; diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 9674eedd7b..9f44be4526 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -197,15 +197,22 @@ =head2 PLOT FUNCTIONS =head2 PLOT MULTIPATH FUNCTIONS -A multipath function is defined using multiple parametric paths pieced together -into into a single curve, whose primary use is to create a closed region to be -filled using multiple boundaries. This is done by providing a list of -parametric functions, the name of the parameter, and a list of options. +A multipath function is defined using multiple parametric paths and points +pieced together into into a single curve. This is done by providing a list of +parametric functions and points, the name of the parameter, and a list of +options. A parametric function is specified by a reference to an array +containing an x function, a y function, the minimum value for the parameter, and +the maximum value for the parameter, followed by options. A point is specified +by a reference to an array containing the coordinates of the point. One reason +for creating a multipath is to create a closed region to be filled using +multiple boundaries. $plot->add_multipath( [ - [ $function_x1, $function_y1, $min1, $max1, %path_options ], - [ $function_x2, $function_y2, $min2, $max2, %path_options ], + [ $function_x1, $function_y1, $min1, $max1, %path_options1 ], + [ $function_x2, $function_y2, $min2, $max2, %path_options2 ], + [ $point_x1, $point_x2 ] + [ $function_x3, $function_y3, $min3, $max3, %path_options3 ], ... ], $variable, @@ -222,19 +229,19 @@ =head2 PLOT MULTIPATH FUNCTIONS The paths have to be listed in the order they are followed, but the minimum/maximum values of the parameter can match the parametrization. The following example creates a sector of radius 5 between pi/4 and 3pi/4, by first -drawing the line from (0,0) to (5sqrt(2),5/sqrt(2)), then the arc of the circle -of radius 5 from pi/4 to 3pi/4, followed by the final line from (-5sqrt(2), -5sqrt(2)) back to the origin. +drawing the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the +line from (-5 sqrt(2), 5 sqrt(2)) to the origin, and then drawing the line from +the origin to (5 sqrt(2), 5 sqrt(2)). $plot->add_multipath( [ - [ 't', 't', 0, '5/sqrt(2)' ], - [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], - [ '-t', 't', '5/sqrt(2)', 0 ], + [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], + [ 0, 0 ], ], 't', color => 'green', fill => 'self', + cycle => 1 ); Note that the ending point of one path does not need to be the same as the From 16c7b70e85e38f6d4fa463485250c24042d5729a Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 20 Nov 2025 11:53:42 -0600 Subject: [PATCH 091/111] Add fill_min_y and fill_max_y options. These options allow restricting the fill region in the y range when filling between curves. Furthermore, these options can be combined with the fill_min and fill_max options to restrict the fill region to a rectangle. --- lib/Plots/JSXGraph.pm | 14 ++++++++++++++ lib/Plots/Tikz.pm | 19 ++++++++++++++----- macros/graph/plots.pl | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index c21761f528..2d7045f3a8 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -326,6 +326,8 @@ sub add_curve { if ($curve_name) { my $fill_min = $data->str_to_real($data->style('fill_min')); my $fill_max = $data->str_to_real($data->style('fill_max')); + my $fill_min_y = $data->str_to_real($data->style('fill_min_y')); + my $fill_max_y = $data->str_to_real($data->style('fill_max_y')); my $fill_layer = $self->get_layer($data, 1) // $self->get_layer($data); my $fillOptions = Mojo::JSON::encode_json({ strokeWidth => 0, @@ -361,12 +363,24 @@ sub add_curve { ".filter(p => {" . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } $self->{JS} .= ";const points2 = curve_${fill}.points"; if ($fill_min ne '' && $fill_max ne '') { $self->{JS} .= ".filter(p => {" . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } $self->{JS} .= ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" . "points2.map( p => p.usrCoords[1] ).reverse());" diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 05f538e95f..591dfe2851 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -685,18 +685,27 @@ sub draw { # Make sure this is the name from the data style attribute, and not an internal name. my $name = $data->style('name'); if ($name) { - my $opacity = $data->style('fill_opacity') || 0.5; - my $fill_min = $data->style('fill_min'); - my $fill_max = $data->style('fill_max'); + my $opacity = $data->style('fill_opacity') || 0.5; + my $fill_min = $data->style('fill_min'); + my $fill_max = $data->style('fill_max'); + my $fill_min_y = $data->style('fill_min_y'); + my $fill_max_y = $data->style('fill_max_y'); + my $fill_reverse = $data->style('fill_reverse'); my $fill_range = - $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" : ''; + $fill_min ne '' && $fill_max ne '' && $fill_min_y ne '' && $fill_max_y ne '' + ? ", soft clip={($fill_min, $fill_min_y) rectangle ($fill_max, $fill_max_y)}" + : $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" + : $fill_min_y ne '' && $fill_max_y ne '' ? ", soft clip={domain y=$fill_min_y:$fill_max_y}" + : ''; my $fill_layer = $data->style('fill_layer') || $layer; + my $reverse = $fill_reverse eq '' ? '' : $fill_reverse ? ', reverse' : 'reverse=false'; $tikzCode .= "\\begin{scope}[/tikz/fill between/on layer=$fill_layer]\\begin{pgfonlayer}{$fill_layer}" . "\\clip\\axisclippath;\n" if $fill_layer; $tikzCode .= - "\\addplot[$fill_color, fill opacity=$opacity] fill between[of=$name and $fill$fill_range];\n"; + "\\addplot[$fill_color, fill opacity=$opacity] " + . "fill between[of=$name and $fill$fill_range$reverse];\n"; $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $fill_layer; } else { warn q{Unable to create fill. Missing 'name' attribute.}; diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 9f44be4526..de245bd660 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -473,6 +473,23 @@ =head2 DATASET OPTIONS fill_opacity => 0.2, ); +The following fills the area between the two curves x = 4 - y^2 and x = y^2 - 4, +and only fills in the area between y=-2 and y=2: + + $plot->add_function(['4 - y^2', 'y'], 'y', -3, 3, + color => 'blue', + name => 'A' + ); + $plot->add_function(['y^2 - 4', 'y'] 'y', -3, 3, + color => 'blue', + name => 'B', + fill => 'A', + fill_min_y => -2, + fill_max_y => 2, + fill_color => 'green', + fill_opacity => 0.2, + ); + =item fill_color The color used when filling the region. Default: C @@ -487,6 +504,30 @@ =head2 DATASET OPTIONS not defined, then the fill will use the full domain of the function. Default: undefined +=item fill_min_y, fill_max_y + +The minimum and maximum y-values to fill between. If either of these are +not defined, then the fill will use the full y range of the function. +Default: undefined + +Note that fill_min, fill_max, fill_min_y, and fill_max_y can be defined, and +this will result in the region filled being restricted to the rectangle defined +by those ranges. + +=item fill_reverse + +This should only be used if the TikZ output is filling the wrong region when +filling between two curves. By default the pgfplots fillbetween library +attempts to autodetect if one of the curves needs to be reversed in order to +obtain the fill region between the curves. However, the heuristic used for the +autodetection is sometimes wrong. Particularly when filling between two +functions x of y using the C and C options. In that case +set this option to either 0 or 1. If set to 1, then a reversal of one of the +curves is forced. If set to 0, then no reversal will occur. If this is unset, +then the pgfplots fillbetween library will attempt to autodetect if a reversal +is needed. If the correct region is being filled as compared to the JSXGraph +output, then leave this unset. Default: undefined + =item layer The layer to draw on. Available layers are "axis background", "axis grid", From 4f9f7dc3d9fea63be0a804de44590f984aca74db Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sat, 22 Nov 2025 06:12:09 -0600 Subject: [PATCH 092/111] Make the axes arrows configurable per axis. There are times that you don't want the arrows going in both directions on both axes. For instance, if the x-axis doesn't go to the left, but the y-axis does go both up and down. --- htdocs/js/Plots/plots.js | 4 ++-- lib/Plots/Axes.pm | 20 ++++++++++---------- lib/Plots/JSXGraph.pm | 7 +++---- lib/Plots/Tikz.pm | 11 ++++++----- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js index 46cdb16934..ca1d5532ae 100644 --- a/htdocs/js/Plots/plots.js +++ b/htdocs/js/Plots/plots.js @@ -326,7 +326,7 @@ const PGplots = { ? 'sticky' : 'static' : 'fixed', - firstArrow: options.axesArrowsBoth ? { size: 7 } : false, + firstArrow: options.xAxis.arrowsBoth ? { size: 7 } : false, lastArrow: { size: 7 }, highlight: false, straightFirst: options.board?.showNavigation ? true : false, @@ -420,7 +420,7 @@ const PGplots = { ? 'sticky' : 'static' : 'fixed', - firstArrow: options.axesArrowsBoth ? { size: 7 } : false, + firstArrow: options.yAxis.arrowsBoth ? { size: 7 } : false, lastArrow: { size: 7 }, highlight: false, straightFirst: options.board?.showNavigation ? true : false, diff --git a/lib/Plots/Axes.pm b/lib/Plots/Axes.pm index 9f9f094b5c..5141a255ee 100644 --- a/lib/Plots/Axes.pm +++ b/lib/Plots/Axes.pm @@ -177,6 +177,14 @@ Default 'middle' or 'center'. The position in terms of the appropriate variable to draw the axis if the location is set to 'middle' or 'center'. Default is 0. +=item arrows_both + +Configures if arrows should be drawn in both directions (1) or only in the +positive direction (0) at the axis ends. In other words, this is a choice +between the convention that arrows are meant to indicate that the axis lines +continue forever, or the convention that arrows are meant to indicate the +positive direction of the axis only. Default: 0 + =item jsx_options A hash reference of options to be passed to the JSXGraph axis object. @@ -238,14 +246,6 @@ usually not desirable. A better way is to use the "axis background" C to only place the fill on the "axis background" layer, and leave everything else on top of the axis. -=item axes_arrows_both - -Configures if arrows should be drawn in both directions (1) or only in the -positive direction (0) at the axes ends. In other words, this is a choice -between the convention that arrows are meant to indicate that the axes lines -continue forever, or the convention that arrows are meant to indicate the -positive direction of the axes only. Default: 0 - =item mathjax_tick_labels If this is 1, then tick labels will be displayed using MathJax. If this is 0, @@ -291,7 +291,6 @@ sub new { grid_alpha => 40, show_grid => 1, axis_on_top => 0, - axes_arrows_both => 0, mathjax_tick_labels => 1, }, @options @@ -322,7 +321,8 @@ sub axis_defaults { tick_num => 5, major => 1, minor => 3, - minor_grids => 1 + minor_grids => 1, + arrows_both => 0, ); } diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 2d7045f3a8..5a9e19ff57 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -116,10 +116,7 @@ sub HTML { } } - if ($xvisible || $yvisible) { - $options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels'); - $options->{axesArrowsBoth} = $axes->style('axes_arrows_both'); - } + $options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels') if $xvisible || $yvisible; if ($xvisible) { $options->{xAxis}{name} = $axes->xaxis('label'); @@ -128,6 +125,7 @@ sub HTML { $options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format'); $options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits'); $options->{xAxis}{ticks}{scaleSymbol} = $axes->xaxis('tick_scale_symbol'); + $options->{xAxis}{arrowsBoth} = $axes->xaxis('arrows_both'); $options->{xAxis}{overrideOptions} = $axes->xaxis('jsx_options') if $axes->xaxis('jsx_options'); } if ($yvisible) { @@ -137,6 +135,7 @@ sub HTML { $options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format'); $options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits'); $options->{yAxis}{ticks}{scaleSymbol} = $axes->yaxis('tick_scale_symbol'); + $options->{yAxis}{arrowsBoth} = $axes->yaxis('arrows_both'); $options->{yAxis}{overrideOptions} = $axes->yaxis('jsx_options') if $axes->yaxis('jsx_options'); } diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 591dfe2851..611273f297 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -188,15 +188,16 @@ sub generate_axes { my $xaxis_location = $axes->xaxis('location'); my $xaxis_pos = $xaxis_location eq 'middle' ? $axes->xaxis('position') : 0; my $yaxis_location = $axes->yaxis('location'); - my $yaxis_pos = $yaxis_location eq 'center' ? $axes->yaxis('position') : 0; - my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; - my $negativeArrow = $axes->style('axes_arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $yaxis_pos = $yaxis_location eq 'center' ? $axes->yaxis('position') : 0; + my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; + my $xNegativeArrow = $axes->xaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $yNegativeArrow = $axes->yaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; my $tikz_options = $axes->style('tikz_options') // ''; my $xlabel = $xvisible ? $axes->xaxis('label') : ''; my $xaxis_style = $xvisible - ? ",\nx axis line style={$negativeArrow-Latex[{round,scale=1.6}]}" + ? ",\nx axis line style={$xNegativeArrow-Latex[{round,scale=1.6}]}" : ",\nx axis line style={draw=none},\nextra y ticks={0}"; my $xtick_style = $xvisible && $axes->xaxis('show_ticks') ? ",\nx tick style={line width=0.6pt}" : ",\nx tick style={draw=none}"; @@ -204,7 +205,7 @@ sub generate_axes { my $ylabel = $yvisible ? $axes->yaxis('label') : ''; my $yaxis_style = $yvisible - ? ",\ny axis line style={$negativeArrow-Latex[{round,scale=1.6}]}" + ? ",\ny axis line style={$yNegativeArrow-Latex[{round,scale=1.6}]}" : ",\ny axis line style={draw=none},\nextra x ticks={0}"; my $ytick_style = $yvisible && $axes->yaxis('show_ticks') ? ",\ny tick style={line width=0.6pt}" : ",\ny tick style={draw=none}"; From f5e9042e7c3c4d2cfa4a1ac9e24b0e1e6d4dc9db Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 23 Nov 2025 17:22:09 -0600 Subject: [PATCH 093/111] Rework the extra space allocation for axes that are on the edges. This still allocates space initially as it did before, but that is merely to let the JavaScript know where to make adjustments and so that there is space for the axes and tick labels to render. Then the JavaScript adjusts the space after the initial render. This ensures that there is enough space for the labels as well as make the spacing nicer when the board is resized in the imageview dialog. --- htdocs/js/ImageView/imageview.js | 4 +- htdocs/js/Plots/plots.js | 134 +++++++++++++++++++++++-------- lib/Plots/JSXGraph.pm | 9 ++- 3 files changed, 107 insertions(+), 40 deletions(-) diff --git a/htdocs/js/ImageView/imageview.js b/htdocs/js/ImageView/imageview.js index 2d99e2b985..ef59fbab5c 100644 --- a/htdocs/js/ImageView/imageview.js +++ b/htdocs/js/ImageView/imageview.js @@ -219,7 +219,7 @@ if (graphDiv) { graphDiv.style.width = width + 'px'; graphDiv.style.height = height + 'px'; - this.dispatchEvent(new Event('resized.imageview')); + graphDiv.dispatchEvent(new Event('resized.imageview')); } // Re-position the modal. @@ -312,7 +312,7 @@ backdrop.style.opacity = '0.2'; }); modal.addEventListener('hidden.bs.modal', () => { - if (imgType == 'div') this.dispatchEvent(new Event('hidden.imageview')); + if (graphDiv) graphDiv.dispatchEvent(new Event('hidden.imageview')); bsModal.dispose(); modal.remove(); window.removeEventListener('resize', onWinResize); diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js index ca1d5532ae..7e1c0416a8 100644 --- a/htdocs/js/Plots/plots.js +++ b/htdocs/js/Plots/plots.js @@ -182,7 +182,7 @@ const PGplots = { // This axis provides the vertical grid lines. if (options.grid?.x) { - board.create( + plot.xGrid = board.create( 'axis', [ [options.xAxis?.min ?? -5, options.xAxis?.position ?? 0], @@ -246,7 +246,7 @@ const PGplots = { // This axis provides the horizontal grid lines. if (options.grid?.y) { - board.create( + plot.yGrid = board.create( 'axis', [ [options.yAxis?.position ?? 0, options.yAxis?.min ?? -5], @@ -305,7 +305,7 @@ const PGplots = { } if (options.xAxis?.visible) { - const xAxis = board.create( + const xAxis = (plot.xAxis = board.create( 'axis', [ [options.xAxis.min ?? -5, options.xAxis.position ?? 0], @@ -374,16 +374,19 @@ const PGplots = { }, options.xAxis.overrideOptions ?? {} ) - ); + )); xAxis.defaultTicks.generateLabelText = plot.generateLabelText; xAxis.defaultTicks.formatLabelText = plot.formatLabelText; - if (options.xAxis.location !== 'middle') { - board.create( + if (options.xAxis.location !== 'middle' && options.xAxis.name !== '') { + plot.xLabel = board.create( 'text', [ - (xAxis.point1.X() + xAxis.point2.X()) / 2, - options.xAxis.location === 'top' ? board.getBoundingBox()[1] : board.getBoundingBox()[3], + () => (xAxis.point1.X() + xAxis.point2.X()) / 2, + () => + options.xAxis.location === 'top' + ? board.getBoundingBox()[1] - 2 / board.unitY + : board.getBoundingBox()[3] + 2 / board.unitY, options.xAxis.name ?? '\\(x\\)' ], { @@ -392,14 +395,15 @@ const PGplots = { highlight: false, color: 'black', fixed: true, - useMathJax: true + useMathJax: true, + cssStyle: 'line-height: 1;' } ); } } if (options.yAxis?.visible) { - const yAxis = board.create( + const yAxis = (plot.yAxis = board.create( 'axis', [ [options.yAxis.position ?? 0, options.yAxis.min ?? -5], @@ -469,28 +473,33 @@ const PGplots = { }, options.yAxis.overrideOptions ?? {} ) - ); + )); yAxis.defaultTicks.generateLabelText = plot.generateLabelText; yAxis.defaultTicks.formatLabelText = plot.formatLabelText; - if (options.yAxis.location !== 'center') { - board.create( - 'text', + if (options.yAxis.location !== 'center' && options.yAxis.name !== '') { + plot.yLabel = board.create('text', [0, 0, options.yAxis.name ?? '\\(y\\)'], { + anchorX: 'middle', + anchorY: options.yAxis.location === 'right' ? 'bottom' : 'top', + rotate: 90, + highlight: 0, + color: 'black', + fixed: 1, + useMathJax: 1, + cssStyle: 'line-height: 1;' + }); + const transform = board.create( + 'transform', [ - options.yAxis.location === 'right' ? boundingBox[2] : boundingBox[0], - (yAxis.point1.Y() + yAxis.point2.Y()) / 2, - options.yAxis.name ?? '\\(y\\)' + () => + options.yAxis.location === 'right' + ? board.getBoundingBox()[2] - 2 / board.unitX + : board.getBoundingBox()[0] + 2 / board.unitX, + () => (yAxis.point1.Y() + yAxis.point2.Y()) / 2 ], - { - anchorX: 'middle', - anchorY: options.yAxis.location === 'right' ? 'bottom' : 'top', - rotate: 90, - highlight: 0, - color: 'black', - fixed: 1, - useMathJax: 1 - } + { type: 'translate' } ); + transform.bindTo(plot.yLabel); } } @@ -498,6 +507,69 @@ const PGplots = { board.unsuspendUpdate(); + plot.updateBoundingBox = () => { + if (options.board?.showNavigation || (!plot.xAxis && !plot.yAxis)) return; + + const adjustLeft = options.yAxis?.visible && boundingBox[0] < (options.xAxis?.min ?? -5); + const adjustRight = options.yAxis?.visible && boundingBox[2] > (options.xAxis?.max ?? 5); + const adjustBottom = options.xAxis?.visible && boundingBox[3] < (options.yAxis?.min ?? 5); + const adjustTop = options.xAxis?.visible && boundingBox[1] > (options.yAxis?.max ?? -5); + if (!adjustLeft && !adjustRight && !adjustTop && !adjustBottom) return; + + let width = 0; + if (plot.yAxis) { + for (const label of plot.yAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.width > width) width = rect.width; + } + if (plot.yLabel) width += plot.yLabel.rendNode.getBoundingClientRect().width + 4; + width += 12; + } + + let height = 0; + if (plot.xAxis) { + for (const label of plot.xAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.height > height) height = rect.height; + } + if (plot.xLabel) height += plot.xLabel.rendNode.getBoundingClientRect().height + 4; + height += 8; + } + + const currentBoundingBox = board.getBoundingBox(); + board.setBoundingBox([ + adjustLeft ? options.xAxis.min - width / board.unitX : currentBoundingBox[0], + adjustTop ? options.yAxis.max + height / board.unitY : currentBoundingBox[1], + adjustRight ? options.xAxis.max + width / board.unitX : currentBoundingBox[2], + adjustBottom ? options.yAxis.min - height / board.unitY : currentBoundingBox[3] + ]); + + if (options.yAxis.location !== 'center' && (adjustLeft || adjustRight)) { + const anchorDist = adjustLeft + ? (options.xAxis?.min ?? -5) - board.getBoundingBox()[0] + : board.getBoundingBox()[2] - (options.xAxis?.max ?? -5); + plot.yAxis?.setAttribute({ anchorDist }); + plot.yGrid?.setAttribute({ anchorDist }); + } + if (options.xAxis.location !== 'center' && (adjustBottom || adjustTop)) { + const anchorDist = adjustBottom + ? (options.yAxis?.min ?? -5) - board.getBoundingBox()[3] + : board.getBoundingBox()[1] - (options.yAxis?.max ?? -5); + plot.xAxis?.setAttribute({ anchorDist }); + plot.xGrid?.setAttribute({ anchorDist }); + } + }; + + if (id.startsWith('magnified-')) { + board.containerObj.addEventListener('resized.imageview', () => { + board.resizeContainer(board.containerObj.clientWidth, board.containerObj.clientHeight, true); + setTimeout(plot.updateBoundingBox); + }); + board.containerObj.addEventListener('hidden.imageview', () => JXG.JSXGraph.freeBoard(board)); + } else { + setTimeout(plot.updateBoundingBox); + } + return board; }; @@ -515,19 +587,11 @@ const PGplots = { await drawPromise(boardContainerId); - let jsxBoard = null; container.addEventListener('shown.imageview', async () => { document .getElementById(`magnified-${boardContainerId}`) ?.classList.add(...Array.from(container.classList).filter((c) => c !== 'image-view-elt')); - jsxBoard = await drawPromise(`magnified-${boardContainerId}`); - }); - container.addEventListener('resized.imageview', () => { - jsxBoard?.resizeContainer(jsxBoard.containerObj.clientWidth, jsxBoard.containerObj.clientHeight, true); - }); - container.addEventListener('hidden.imageview', () => { - if (jsxBoard) JXG.JSXGraph.freeBoard(jsxBoard); - jsxBoard = null; + await drawPromise(`magnified-${boardContainerId}`); }); } }; diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index 5a9e19ff57..cc65c8d42a 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -65,7 +65,10 @@ sub HTML { $options->{board}{showNavigation} = $axes->style('jsx_navigation') ? 1 : 0; $options->{board}{overrideOptions} = $axes->style('jsx_options') if $axes->style('jsx_options'); - # Set the bounding box. Add padding for the axes at the edge of graph if needed. + # Set the bounding box. Add padding for the axes at the edge of graph if needed. Note that the padding set here is + # not the final padding used in the end result. The plots.js JavaScript adjusts the padding to fit the axis label + # content. This just needs to add enough padding so that the label content has enough room to render, and so that + # the JavaScript knows where the adjustments are needed. $options->{board}{boundingBox} = [ $xmin - ( $yvisible @@ -80,8 +83,8 @@ sub HTML { ]; $options->{xAxis}{visible} = $xvisible; + ($options->{xAxis}{min}, $options->{xAxis}{max}) = ($xmin, $xmax); if ($xvisible || ($show_grid && $grid->{xmajor})) { - ($options->{xAxis}{min}, $options->{xAxis}{max}) = ($xmin, $xmax); $options->{xAxis}{position} = $xaxis_pos; $options->{xAxis}{location} = $xaxis_loc; $options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale'); @@ -90,8 +93,8 @@ sub HTML { } $options->{yAxis}{visible} = $yvisible; + ($options->{yAxis}{min}, $options->{yAxis}{max}) = ($ymin, $ymax); if ($yvisible || ($show_grid && $grid->{ymajor})) { - ($options->{yAxis}{min}, $options->{yAxis}{max}) = ($ymin, $ymax); $options->{yAxis}{position} = $yaxis_pos; $options->{yAxis}{location} = $yaxis_loc; $options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale'); From 8501ec5f0fb4ba0826d544bb2b83e5b6c50b1ab1 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 1 Jan 2026 05:36:54 -0600 Subject: [PATCH 094/111] Use the `add_point` method instead of the `add_stamp` method in the `SimpleGraph.pl` macro. --- macros/math/SimpleGraph.pl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index 7874eb6263..d498a735a9 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -793,7 +793,7 @@ sub image { for my $i (0 .. $self->lastVertexIndex) { my $iVertex = [ cos($i * $gap), sin($i * $gap) ]; - $plot->add_stamp(@$iVertex, color => 'blue'); + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); $plot->add_label( 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], @@ -865,7 +865,7 @@ sub gridLayoutImage { for my $j (0 .. $self->{gridLayout}[1] - 1) { my $x = $gridGap * $j; my $y = $gridGap * ($self->{gridLayout}[0] - $i - 1); - $plot->add_stamp($x, $y, color => 'blue'); + $plot->add_point($x, $y, color => 'blue', mark_size => 3); $plot->add_label( $x - $labelShift, $y + 2 * $labelShift, label => "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", @@ -967,7 +967,7 @@ sub bipartiteLayoutImage { ); for my $i (0 .. $#$top) { - $plot->add_stamp($i * $width + $shift[0], $high, color => 'blue'); + $plot->add_point($i * $width + $shift[0], $high, color => 'blue', mark_size => 3); $plot->add_label( $i * $width + $shift[0], $high + 2 / 3, label => "\\\\($self->{labels}[$top->[$i]]\\\\)", @@ -977,7 +977,7 @@ sub bipartiteLayoutImage { ) if $graphOptions{showLabels}; } for my $j (0 .. $#$bottom) { - $plot->add_stamp($j * $width + $shift[1], $low, color => 'blue'); + $plot->add_point($j * $width + $shift[1], $low, color => 'blue', mark_size => 3); $plot->add_label( $j * $width + $shift[1], $low - 2 / 3, label => "\\\\($self->{labels}[$bottom->[$j]]\\\\)", @@ -1037,7 +1037,7 @@ sub wheelLayoutImage { my $gap = 2 * $main::PI / ($self->lastVertexIndex || 1); - $plot->add_stamp(0, 0, color => 'blue'); + $plot->add_point(0, 0, color => 'blue', mark_size => 3); $plot->add_label( 0.1, 0.2, label => "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", @@ -1052,7 +1052,7 @@ sub wheelLayoutImage { my $iRel = $i > $self->{wheelLayout} ? $i - 1 : $i; my $iVertex = [ cos($iRel * $gap), sin($iRel * $gap) ]; - $plot->add_stamp(@$iVertex, color => 'blue'); + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); $plot->add_label( 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], From 7cb9cea37beb640a0ffe7b029bf7400d3e111a52 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 28 Oct 2025 15:15:08 -0500 Subject: [PATCH 095/111] Implement several options for the GraphTool in hardcopy that should have been implemented before. If the `scaleX`, `scaleSymbolX`, `scaleY`, `scaleSymbolY`, or `coordinateHintsType` options are used then the axis ticks and labels are adjusted in the JSXGraph HTML output for the GraphTool, but not in hardcopy. This was an oversight that I didn't do this when these options were implemented. I also added minor ticks to the hardcopy. Those were also never implemented. The documentation for the `scaleX` and `scaleY` options was also corrected. I misstated the way that this is handled by JSXGraph. --- macros/graph/parserGraphTool.pl | 120 +++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/macros/graph/parserGraphTool.pl b/macros/graph/parserGraphTool.pl index 0b05d1e3ec..ee27badee3 100644 --- a/macros/graph/parserGraphTool.pl +++ b/macros/graph/parserGraphTool.pl @@ -331,8 +331,12 @@ =head1 OPTIONS =item scaleX, scaleY (Default: C<< scaleX => 1, scaleY => 1 >>) -These are the scale of the ticks on the x and y axes. That is the distance between two -successive ticks on the axis (including both major and minor ticks). +These are the scale of the tick distances on the x and y axes. That is the distance between two +successive major ticks on the axis will be the product of the ticks distance and the scale. +This is usually used in conjunction with the C and C options. For +example, if C is 2, C is 3, and C is 'a', then the first +positive major x axis tick will occur 6 units to the right and be labeled '2a', and the next +major x axis tick will occur 12 units to the right and be labeled '4a'. =item scaleSymbolX, scaleSymbolY (Default: C<< scaleSymbolX => '', scaleSymbolY => '' >>) @@ -1195,6 +1199,47 @@ sub generateHTMLAnswerGraph { END_SCRIPT } +# This is essentially copied from contextFraction.pl. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $coordinateHintsType = $self->{"coordinateHintsType$axis"} // $self->{coordinateHintsType}; + if ($coordinateHintsType eq 'fraction' || $coordinateHintsType eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($coordinateHintsType eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } + my $scaleSymbol = $self->{"scaleSymbol$axis"} // ''; + return + $value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value; +} + sub generateTeXGraph { my ($self, %options) = @_; @@ -1203,7 +1248,7 @@ sub generateTeXGraph { return &{ $self->{printGraph} } if ref($self->{printGraph}) eq 'CODE'; - my @size = $self->{numberLine} ? (500, 100) : (500, 500); + my @size = $self->{numberLine} ? (500, 110) : (500, 500); my $graph = main::createTikZImage(); $graph->tikzLibraries('arrows.meta'); @@ -1264,32 +1309,71 @@ sub generateTeXGraph { } # Horizontal axis ticks and labels - my @xTicks = grep { $_ < $self->{bBox}[2] } - map { $_ * $self->{ticksDistanceX} } (1 .. $self->{bBox}[2] / $self->{ticksDistanceX}); - push(@xTicks, + my @xTicks = grep { $_ > $self->{bBox}[0] } - map { -$_ * $self->{ticksDistanceX} } (1 .. -$self->{bBox}[0] / $self->{ticksDistanceX})); + map { -$_ * $self->{ticksDistanceX} * $self->{scaleX} } + reverse(1 .. -$self->{bBox}[0] / ($self->{ticksDistanceX} * $self->{scaleX})); + my $numNegative = @xTicks; # Add zero if this is a number line and 0 is in the given range. - push(@xTicks, 0) if ($self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0); + push(@xTicks, 0) if $self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0; + push(@xTicks, + grep { $_ < $self->{bBox}[2] } + map { $_ * $self->{ticksDistanceX} * $self->{scaleX} } + (1 .. $self->{bBox}[2] / ($self->{ticksDistanceX} * $self->{scaleX}))); my $tickSize = $self->{numberLine} ? '9' : '5'; $tikz .= - "\\foreach \\x in {" - . join(',', @xTicks) - . "}{\\draw[thin] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) node[below]{\\(\\x\\)};}\n" + "\\foreach \\x/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleX}, 'X') } @xTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) " + . "node[baseline, yshift = -15pt, opacity = 1]{\\(\\label\\)};}\n" if (@xTicks); + # Add horizontal axis minor ticks. + splice(@xTicks, $numNegative, 0, 0) if !$self->{numberLine} || ($self->{bBox}[0] <= 0 && $self->{bBox}[2] >= 0); + unshift(@xTicks, $xTicks[0] - $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[0] < 0; + push(@xTicks, $xTicks[-1] + $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[2] > 0; + my @xMinorTicks; + my $xMinorTickDelta = $self->{ticksDistanceX} * $self->{scaleX} / ($self->{minorTicksX} + 1); + for my $tickIndex (0 .. $#xTicks - 1) { + push(@xMinorTicks, map { $xTicks[$tickIndex] + $_ * $xMinorTickDelta } 1 .. $self->{minorTicksX}); + } + $tikz .= + "\\foreach \\x in {" + . join(',', @xMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,0) -- (\\x,-${tickSize}pt);}\n" + if (@xMinorTicks); + # Vertical axis ticks and labels unless ($self->{numberLine}) { - my @yTicks = grep { $_ < $self->{bBox}[1] } - map { $_ * $self->{ticksDistanceY} } (1 .. $self->{bBox}[1] / $self->{ticksDistanceY}); - push(@yTicks, + my @yTicks = grep { $_ > $self->{bBox}[3] } - map { -$_ * $self->{ticksDistanceY} } (1 .. -$self->{bBox}[3] / $self->{ticksDistanceY})); + map { -$_ * $self->{ticksDistanceY} * $self->{scaleY} } + reverse(1 .. -$self->{bBox}[3] / ($self->{ticksDistanceY} * $self->{scaleY})); + my $numNegative = @yTicks; + push(@yTicks, + grep { $_ < $self->{bBox}[1] } + map { $_ * $self->{ticksDistanceY} * $self->{scaleY} } + (1 .. $self->{bBox}[1] / ($self->{ticksDistanceY} * $self->{scaleY}))); $tikz .= - "\\foreach \\y in {" - . join(',', @yTicks) - . "}{\\draw[thin] (5pt,\\y) -- (-5pt,\\y) node[left]{\$\\y\$};}\n" + "\\foreach \\y/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleY}, 'Y') } @yTicks) + . "}{\\draw[thin, opacity = 0.5] (5pt,\\y) -- (-5pt,\\y) node[left, opacity = 1]{\$\\label\$};}\n" if (@yTicks); + + # Add vertical axis minor ticks. + splice(@yTicks, $numNegative, 0, 0); + unshift(@yTicks, $yTicks[0] - $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[3] < 0; + push(@yTicks, $yTicks[-1] + $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[1] > 0; + my @yMinorTicks; + my $yMinorTickDelta = $self->{ticksDistanceY} * $self->{scaleY} / ($self->{minorTicksY} + 1); + for my $tickIndex (0 .. $#yTicks - 1) { + push(@yMinorTicks, map { $yTicks[$tickIndex] + $_ * $yMinorTickDelta } 1 .. $self->{minorTicksY}); + } + $tikz .= + "\\foreach \\y in {" + . join(',', @yMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (0, \\y) -- (-5pt, \\y);}\n" + if @yMinorTicks; } # Border box From e67bc66733ad154f21098b43cad7466d8ac2f20b Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 2 Sep 2025 20:11:46 -0500 Subject: [PATCH 096/111] Add a workflow to generate and publish PG POD and sample problem documentation. This is intended to replace the openwebwork/pg-docs repository. The approach in that repository has some issues. * The workflows must be manually triggered in sequence. * It isn't tied to the PG repository and so can become and in fact is now out of date. * It unnecessarily commits the generated documentation files to a repository as well as publishing them on GitHub pages. * It uses the `bin/dev_script/generate-ww-pg-pod.pl` script from the webwork2 repository to generate the PG and webwork2 POD, and the webwork2 POD should not be on a site that is intended for PG documentation. Furthermore, the generated html POD site does not fit into the PG documentation site well. For, example there is no link to get back to the main PG documentation index page, and it is labeled as WeBorK documentation, not PG documentation. The single workflow added in this pull request runs anytime anything is merged into the main branch. It can also be manually triggered but that generally won't be needed. This means that anytime a new version of PG is released the documentation will automatically be updated for that release. The workflow generates the PG POD and sample problem documentation and publishes the documentation to the PG GitHub pages site at https://openwebwork.github.io/pg. The generated documentation is not committed to a repository. It is published to the pages site, and that is enough. A new `bin/generate-pg-pod.pl` script is added that generates only the PG POD. The generated html is tailored for the PG documentation on GitHub pages. There is some minor redundancy as there are some files that are now in both the webwork2 and PG repositories (needed for both the `generate-ww-pg-pod.pl` and `generate-pg-pod.pl` scripts). However, it certainly would not be a good idea to have the PG repository depend on the webwork2 repository for the `generate-ww-pg-pod.pl` script, and as mentioned that script doesn't really generated the right HTML to begin with. Move the `SampleProblemParser` module into the `WeBWorK::PG` namespace. All of the other modules in the root namespace in the PG repository are directly for problem rendering except this one. It is out of place. So it is now the `WeBWorK::PG::SampleProblemParser` module. Change the options for the `parser-problem-doc.pl` script and the `WeBWorK::PG::SampleProblemParser` module. Using underscores for options is annoying. Underscores require an extra keyboard button press (the shift key), and underscore are not standard for command line options. Also the `pod_root` and `pg_doc_home` options are badly named. The `pod_root` option name seems to indicate it should be the directory location for the POD, but it is a URL. So make that clear by renaming it to `pod-base-url`. The `pg_doc_home` is not the URL for the PG documentation home, but for the sample problem base URL. So rename that to `sample-problem-base-url` (and its shortcut from "h" to "s"). Also change the corresponding variable names in the `SampleProblemParser.pm` module. Copy the PODParser.pm and PODtoHTML.pm modules from the webwork2 repository to lib/WeBWorK/Utils where webwork2 can also use them. Those modules will be deleted from the webwork2 repository. Copy podviewer.css and podviewer.js from the webwork2 repository into htdocs/js/PODViewer, also where webwork2 can use them. The files will be deleted from the webwork2 repository. Copy the bin/dev_scripts/pod-templates category-index.mt and pod.mt files into assets/pod-templates here, and make changes to the files so that they will work for both webwork2 and pg. The files will also be deleted from the webwork2 repository. Add the sample problem and macro POD search to PG docs. The sample problem and macro POD search data is generated by the `WeBWorK::PG::SampleProblemParser::getSearchData` method. This is used by webwork2 and the `bin/generate-search-data.pl` script. The script is very simple. It just calls the method passing in the file name to save the data to. The workflow runs the script and copies the resulting file to the PG github pages site. The `assets/stop-words-en.txt` file is moved here from the webwork2 repository since the `WeBWorK::PG::SampleProblemParser::getSearchData` needs it. The `htdocs/js/SampleProblemViewer/documentation-search.js` is moved from the webwork2 repository (webwork2 uses it from here now), and this is also copied to the PG github pages site in the workflow. It is slightly modified to work for both webwork2 and on the PG github pages site. --- .../workflows/generate-and-publish-docs.yml | 76 + assets/pod-templates/category-index.mt | 94 ++ assets/pod-templates/pod.mt | 55 + assets/stop-words-en.txt | 1320 +++++++++++++++++ bin/generate-pg-pod.pl | 80 + bin/generate-search-data.pl | 42 + bin/parse-problem-doc.pl | 64 +- htdocs/js/PODViewer/podviewer.css | 66 + htdocs/js/PODViewer/podviewer.js | 8 + .../documentation-search.js | 78 + lib/AnswerHash.pm | 0 lib/PGcore.pm | 0 lib/SampleProblemParser.pm | 253 ---- lib/WeBWorK/PG/SampleProblemParser.pm | 474 ++++++ lib/WeBWorK/Utils/PODParser.pm | 66 + lib/WeBWorK/Utils/PODtoHTML.pm | 212 +++ tutorial/sample-problems/README.md | 12 +- tutorial/templates/general-layout.mt | 4 +- tutorial/templates/index.html | 139 ++ tutorial/templates/problem-template.mt | 19 +- 20 files changed, 2771 insertions(+), 291 deletions(-) create mode 100644 .github/workflows/generate-and-publish-docs.yml create mode 100644 assets/pod-templates/category-index.mt create mode 100644 assets/pod-templates/pod.mt create mode 100644 assets/stop-words-en.txt create mode 100755 bin/generate-pg-pod.pl create mode 100755 bin/generate-search-data.pl create mode 100644 htdocs/js/PODViewer/podviewer.css create mode 100644 htdocs/js/PODViewer/podviewer.js create mode 100644 htdocs/js/SampleProblemViewer/documentation-search.js mode change 100755 => 100644 lib/AnswerHash.pm mode change 100755 => 100644 lib/PGcore.pm delete mode 100644 lib/SampleProblemParser.pm create mode 100644 lib/WeBWorK/PG/SampleProblemParser.pm create mode 100644 lib/WeBWorK/Utils/PODParser.pm create mode 100644 lib/WeBWorK/Utils/PODtoHTML.pm create mode 100644 tutorial/templates/index.html diff --git a/.github/workflows/generate-and-publish-docs.yml b/.github/workflows/generate-and-publish-docs.yml new file mode 100644 index 0000000000..07dfcc7263 --- /dev/null +++ b/.github/workflows/generate-and-publish-docs.yml @@ -0,0 +1,76 @@ +name: Generate and Publish PG POD and Sample Problem Documentation + +on: + # Execute the workflow anytime something is merged into or pushed to main. + push: + branches: + - main + + # This allows this workflow to be triggered manually from the actions tab. + workflow_dispatch: + +jobs: + generate-documentation: + runs-on: ubuntu-24.04 + + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends --no-install-suggests \ + pandoc \ + libmojolicious-perl \ + libpandoc-wrapper-perl \ + libpod-parser-perl + + - name: Checkout pg code + uses: actions/checkout@v4 + with: + path: pg + + - name: Create output directory + run: mkdir /home/runner/work/pg/pg/documentation + + - name: Generate sample problem documentation + run: | + perl pg/bin/parse-problem-doc.pl \ + --problem-dir=/home/runner/work/pg/pg/pg/tutorial/sample-problems \ + --out-dir=/home/runner/work/pg/pg/documentation/sampleproblems \ + --pod-base-url=/pg/pod \ + --sample-problem-base-url=/pg/sampleproblems + + - name: Generate POD + run: perl pg/bin/generate-pg-pod.pl --output-dir=documentation/pod --base-url=/pg/pod/ --home-url=/pg + + - name: Generate search data + run: perl pg/bin/generate-search-data.pl --out-file=documentation/sample-problem-search-data.json + + - name: Copy assets + run: | + cp /home/runner/work/pg/pg/pg/tutorial/templates/index.html \ + /home/runner/work/pg/pg/documentation/ + cp /home/runner/work/pg/pg/pg/htdocs/js/SampleProblemViewer/documentation-search.js \ + /home/runner/work/pg/pg/documentation/ + + - name: Upload documentation html + uses: actions/upload-pages-artifact@v3 + with: + path: documentation + + publish-documentation: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Set the permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages. + permissions: + pages: write + id-token: write + + runs-on: ubuntu-24.04 + needs: generate-documentation + + steps: + - name: Publish to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/assets/pod-templates/category-index.mt b/assets/pod-templates/category-index.mt new file mode 100644 index 0000000000..61fe3b450c --- /dev/null +++ b/assets/pod-templates/category-index.mt @@ -0,0 +1,94 @@ + + +% + + + + <%= $title %> + + + + + +% + + + % + % my ($index, $macro_index, $content, $macro_content) = ('', '', '', ''); + % for my $macro (@$macros_order) { + % my $new_index = begin + <%= $macro_names->{$macro} // $macro %> + % end + % $macro_index .= $new_index->(); + % my $new_content = begin +

    <%= $macro_names->{$macro} // $macro %>

    +
    + % for my $file (sort { $a->[1] cmp $b->[1] } @{ $macros->{$macro} }) { + <%= $file->[1] %> + % } +
    + % end + % $macro_content .= $new_content->(); + % } + % for my $section (@$section_order) { + % next unless defined $pod_index->{$section}; + % my $new_index = begin + <%= $sections->{$section} %> + % if ($section eq 'macros') { + + % } + % end + % $index .= $new_index->(); + % my $new_content = begin +

    <%= $sections->{$section} %>

    +
    + % if ($section eq 'macros') { + <%= $macro_content =%> + % } else { + % for my $file (sort { $a->[1] cmp $b->[1] } @{ $pod_index->{$section} }) { + + <%= $file->[1] %> + + % } + % } +
    + % end + % $content .= $new_content->(); + % } + % + +
    +
    + <%= $content =%> +

    Generated <%= $date %>

    +
    +
    + +% + diff --git a/assets/pod-templates/pod.mt b/assets/pod-templates/pod.mt new file mode 100644 index 0000000000..53cc4a4898 --- /dev/null +++ b/assets/pod-templates/pod.mt @@ -0,0 +1,55 @@ + + +% + + + + <%= $title %> + + + + + +% + + + +
    +
    + <%= $content =%> +
    +
    + +% + diff --git a/assets/stop-words-en.txt b/assets/stop-words-en.txt new file mode 100644 index 0000000000..cc09be2ec7 --- /dev/null +++ b/assets/stop-words-en.txt @@ -0,0 +1,1320 @@ +# Stop words from https://github.com/Alir3z4/stop-words. + +'ll +'tis +'twas +'ve +a +a's +able +ableabout +about +above +abroad +abst +accordance +according +accordingly +across +act +actually +ad +added +adj +adopted +ae +af +affected +affecting +affects +after +afterwards +ag +again +against +ago +ah +ahead +ai +ain't +aint +al +all +allow +allows +almost +alone +along +alongside +already +also +although +always +am +amid +amidst +among +amongst +amoungst +amount +an +and +announce +another +any +anybody +anyhow +anymore +anyone +anything +anyway +anyways +anywhere +ao +apart +apparently +appear +appreciate +appropriate +approximately +aq +ar +are +area +areas +aren +aren't +arent +arise +around +arpa +as +aside +ask +asked +asking +asks +associated +at +au +auth +available +aw +away +awfully +az +b +ba +back +backed +backing +backs +backward +backwards +bb +bd +be +became +because +become +becomes +becoming +been +before +beforehand +began +begin +beginning +beginnings +begins +behind +being +beings +believe +below +beside +besides +best +better +between +beyond +bf +bg +bh +bi +big +bill +billion +biol +bj +bm +bn +bo +both +bottom +br +brief +briefly +bs +bt +but +buy +bv +bw +by +bz +c +c'mon +c's +ca +call +came +can +can't +cannot +cant +caption +case +cases +cause +causes +cc +cd +certain +certainly +cf +cg +ch +changes +ci +ck +cl +clear +clearly +click +cm +cmon +cn +co +co. +com +come +comes +computer +con +concerning +consequently +consider +considering +contain +containing +contains +copy +corresponding +could +could've +couldn +couldn't +couldnt +course +cr +cry +cs +cu +currently +cv +cx +cy +cz +d +dare +daren't +darent +date +de +dear +definitely +describe +described +despite +detail +did +didn +didn't +didnt +differ +different +differently +directly +dj +dk +dm +do +does +doesn +doesn't +doesnt +doing +don +don't +done +dont +doubtful +down +downed +downing +downs +downwards +due +during +dz +e +each +early +ec +ed +edu +ee +effect +eg +eh +eight +eighty +either +eleven +else +elsewhere +empty +end +ended +ending +ends +enough +entirely +er +es +especially +et +et-al +etc +even +evenly +ever +evermore +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +f +face +faces +fact +facts +fairly +far +farther +felt +few +fewer +ff +fi +fifteen +fifth +fifty +fify +fill +find +finds +fire +first +five +fix +fj +fk +fm +fo +followed +following +follows +for +forever +former +formerly +forth +forty +forward +found +four +fr +free +from +front +full +fully +further +furthered +furthering +furthermore +furthers +fx +g +ga +gave +gb +gd +ge +general +generally +get +gets +getting +gf +gg +gh +gi +give +given +gives +giving +gl +gm +gmt +gn +go +goes +going +gone +good +goods +got +gotten +gov +gp +gq +gr +great +greater +greatest +greetings +group +grouped +grouping +groups +gs +gt +gu +gw +gy +h +had +hadn't +hadnt +half +happens +hardly +has +hasn +hasn't +hasnt +have +haven +haven't +havent +having +he +he'd +he'll +he's +hed +hell +hello +help +hence +her +here +here's +hereafter +hereby +herein +heres +hereupon +hers +herself +herse” +hes +hi +hid +high +higher +highest +him +himself +himse” +his +hither +hk +hm +hn +home +homepage +hopefully +how +how'd +how'll +how's +howbeit +however +hr +ht +htm +html +http +hu +hundred +i +i'd +i'll +i'm +i've +i.e. +id +ie +if +ignored +ii +il +ill +im +immediate +immediately +importance +important +in +inasmuch +inc +inc. +indeed +index +indicate +indicated +indicates +information +inner +inside +insofar +instead +int +interest +interested +interesting +interests +into +invention +inward +io +iq +ir +is +isn +isn't +isnt +it +it'd +it'll +it's +itd +itll +its +itself +itse” +ive +j +je +jm +jo +join +jp +just +k +ke +keep +keeps +kept +keys +kg +kh +ki +kind +km +kn +knew +know +known +knows +kp +kr +kw +ky +kz +l +la +large +largely +last +lately +later +latest +latter +latterly +lb +lc +least +length +less +lest +let +let's +lets +li +like +liked +likely +likewise +line +little +lk +ll +long +longer +longest +look +looking +looks +low +lower +lr +ls +lt +ltd +lu +lv +ly +m +ma +made +mainly +make +makes +making +man +many +may +maybe +mayn't +maynt +mc +md +me +mean +means +meantime +meanwhile +member +members +men +merely +mg +mh +microsoft +might +might've +mightn't +mightnt +mil +mill +million +mine +minus +miss +mk +ml +mm +mn +mo +more +moreover +most +mostly +move +mp +mq +mr +mrs +ms +msie +mt +mu +much +mug +must +must've +mustn't +mustnt +mv +mw +mx +my +myself +myse” +mz +n +na +name +namely +nay +nc +nd +ne +near +nearly +necessarily +necessary +need +needed +needing +needn't +neednt +needs +neither +net +netscape +never +neverf +neverless +nevertheless +new +newer +newest +next +nf +ng +ni +nine +ninety +nl +no +no-one +nobody +non +none +nonetheless +noone +nor +normally +nos +not +noted +nothing +notwithstanding +novel +now +nowhere +np +nr +nu +null +number +numbers +nz +o +obtain +obtained +obviously +of +off +often +oh +ok +okay +old +older +oldest +om +omitted +on +once +one +one's +ones +only +onto +open +opened +opening +opens +opposite +or +ord +order +ordered +ordering +orders +org +other +others +otherwise +ought +oughtn't +oughtnt +our +ours +ourselves +out +outside +over +overall +owing +own +p +pa +page +pages +part +parted +particular +particularly +parting +parts +past +pe +per +perhaps +pf +pg +ph +pk +pl +place +placed +places +please +plus +pm +pmid +pn +point +pointed +pointing +points +poorly +possible +possibly +potentially +pp +pr +predominantly +present +presented +presenting +presents +presumably +previously +primarily +probably +problem +problems +promptly +proud +provided +provides +pt +put +puts +pw +py +q +qa +que +quickly +quite +qv +r +ran +rather +rd +re +readily +really +reasonably +recent +recently +ref +refs +regarding +regardless +regards +related +relatively +research +reserved +respectively +resulted +resulting +results +right +ring +ro +room +rooms +round +ru +run +rw +s +sa +said +same +saw +say +saying +says +sb +sc +sd +se +sec +second +secondly +seconds +section +see +seeing +seem +seemed +seeming +seems +seen +sees +self +selves +sensible +sent +serious +seriously +seven +seventy +several +sg +sh +shall +shan't +shant +she +she'd +she'll +she's +shed +shell +shes +should +should've +shouldn +shouldn't +shouldnt +show +showed +showing +shown +showns +shows +si +side +sides +significant +significantly +similar +similarly +since +sincere +site +six +sixty +sj +sk +sl +slightly +sm +small +smaller +smallest +sn +so +some +somebody +someday +somehow +someone +somethan +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specifically +specified +specify +specifying +sr +st +state +states +still +stop +strongly +su +sub +substantially +successfully +such +sufficiently +suggest +sup +sure +sv +sy +system +sz +t +t's +take +taken +taking +tc +td +tell +ten +tends +test +text +tf +tg +th +than +thank +thanks +thanx +that +that'll +that's +that've +thatll +thats +thatve +the +their +theirs +them +themselves +then +thence +there +there'd +there'll +there're +there's +there've +thereafter +thereby +thered +therefore +therein +therell +thereof +therere +theres +thereto +thereupon +thereve +these +they +they'd +they'll +they're +they've +theyd +theyll +theyre +theyve +thick +thin +thing +things +think +thinks +third +thirty +this +thorough +thoroughly +those +thou +though +thoughh +thought +thoughts +thousand +three +throug +through +throughout +thru +thus +til +till +tip +tis +tj +tk +tm +tn +to +today +together +too +took +top +toward +towards +tp +tr +tried +tries +trillion +truly +try +trying +ts +tt +turn +turned +turning +turns +tv +tw +twas +twelve +twenty +twice +two +tz +u +ua +ug +uk +um +un +under +underneath +undoing +unfortunately +unless +unlike +unlikely +until +unto +up +upon +ups +upwards +us +use +used +useful +usefully +usefulness +uses +using +usually +uucp +uy +uz +v +va +value +various +vc +ve +versus +very +vg +vi +via +viz +vn +vol +vols +vs +vu +w +want +wanted +wanting +wants +was +wasn +wasn't +wasnt +way +ways +we +we'd +we'll +we're +we've +web +webpage +website +wed +welcome +well +wells +went +were +weren +weren't +werent +weve +wf +what +what'd +what'll +what's +what've +whatever +whatll +whats +whatve +when +when'd +when'll +when's +whence +whenever +where +where'd +where'll +where's +whereafter +whereas +whereby +wherein +wheres +whereupon +wherever +whether +which +whichever +while +whilst +whim +whither +who +who'd +who'll +who's +whod +whoever +whole +wholl +whom +whomever +whos +whose +why +why'd +why'll +why's +widely +width +will +willing +wish +with +within +without +won +won't +wonder +wont +words +work +worked +working +works +world +would +would've +wouldn +wouldn't +wouldnt +ws +www +x +y +ye +year +years +yes +yet +you +you'd +you'll +you're +you've +youd +youll +young +younger +youngest +your +youre +yours +yourself +yourselves +youve +yt +yu +z +za +zero +zm +zr + +# Additional specific stop words specific to POD and sample problem documentation. +constructor +description +error +errors +macro +macros +pod +podlink +problink +synopsis +usage +funciton +functions +method +methods +option +options +todo +fixme +_ diff --git a/bin/generate-pg-pod.pl b/bin/generate-pg-pod.pl new file mode 100755 index 0000000000..98f73a3458 --- /dev/null +++ b/bin/generate-pg-pod.pl @@ -0,0 +1,80 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-pg-pod.pl - Convert PG POD into HTML form. + +=head1 SYNOPSIS + +generate-pg-pod.pl [options] + + Options: + -o|--output-dir Directory to save the output files to. (required) + -b|--base-url Base url location used on server. (default: /) + This is needed for internal POD links to work correctly. + -h|--home-url Home page url on the server. (default: /) + -v|--verbose Increase the verbosity of the output. + (Use multiple times for more verbosity.) + +=head1 DESCRIPTION + +Convert PG POD into HTML form. + +=cut + +use strict; +use warnings; + +use Getopt::Long qw(:config bundling); +use Pod::Usage; + +my ($output_dir, $base_url, $home_url); +my $verbose = 0; +GetOptions( + 'o|output-dir=s' => \$output_dir, + 'b|base-url=s' => \$base_url, + 'h|home-url=s' => \$home_url, + 'v|verbose+' => \$verbose +); + +pod2usage(2) unless $output_dir; + +$base_url = "/" if !$base_url; +$home_url = "/" if !$home_url; + +use Mojo::Template; +use IO::File; +use File::Copy; +use File::Path qw(make_path remove_tree); +use File::Basename qw(dirname); +use Cwd qw(abs_path); + +use lib abs_path(dirname(dirname(__FILE__))) . '/lib'; + +use WeBWorK::Utils::PODtoHTML; + +my $pg_root = abs_path(dirname(dirname(__FILE__))); + +print "Reading: $pg_root\n" if $verbose; + +remove_tree($output_dir); +make_path($output_dir); + +my $htmldocs = WeBWorK::Utils::PODtoHTML->new( + source_root => $pg_root, + dest_root => $output_dir, + template_dir => "$pg_root/assets/pod-templates", + dest_url => $base_url, + home_url => $home_url, + home_url_link_name => 'PG Documentation Home', + verbose => $verbose +); +$htmldocs->convert_pods; + +make_path("$output_dir/assets"); +copy("$pg_root/htdocs/js/PODViewer/podviewer.css", "$output_dir/assets/podviewer.css"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.css\n" if $verbose; +copy("$pg_root/htdocs/js/PODViewer/podviewer.js", "$output_dir/assets/podviewer.js"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.js\n" if $verbose; + +1; diff --git a/bin/generate-search-data.pl b/bin/generate-search-data.pl new file mode 100755 index 0000000000..c3ab227eec --- /dev/null +++ b/bin/generate-search-data.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-search-data.pl - Generate search data for macro and sample problem +documentation. + +=head1 SYNOPSIS + +generate-search-data.pl [options] + + Options: + -o|--out-file File to save the search data to. (required) + +=head1 DESCRIPTION + +Generate search data for macro and sample problem documentation. + +=cut + +use strict; +use warnings; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname; } + +use lib "$pgRoot/lib"; + +use Getopt::Long; +use Pod::Usage; + +use WeBWorK::PG::SampleProblemParser qw(getSearchData); + +my $outFile; +GetOptions("o|out-file=s" => \$outFile); +pod2usage(2) unless $outFile; + +getSearchData($outFile); + +1; diff --git a/bin/parse-problem-doc.pl b/bin/parse-problem-doc.pl index b2274b198a..818416093d 100755 --- a/bin/parse-problem-doc.pl +++ b/bin/parse-problem-doc.pl @@ -1,5 +1,29 @@ #!/usr/bin/env perl +=head1 NAME + +parse-problem-doc.pl - Parse sample problem documentation. + +=head1 SYNOPSIS + +parse-problem-doc.pl [options] + + Options: + -d|--problem-dir Directory containing sample problems to be parsed. + This defaults to the tutorial/sample-problems directory + in the PG root directory if not given. + -o|--out-dir Directory to save the output files to. (required) + -p|--pod-base-url Base URL location for POD on server. (required) + -s|--sample-problem-base-url + Base URL location for sample problems on server. (required) + -v|--verbose Give verbose feedback. + +=head1 DESCRIPTION + +Parse sample problem documentation. + +=cut + use strict; use warnings; use experimental 'signatures'; @@ -17,26 +41,26 @@ BEGIN use Mojo::Template; use File::Basename qw(basename); use Getopt::Long; +use Pod::Usage; use File::Copy qw(copy); use Pod::Simple::Search; -use SampleProblemParser qw(parseSampleProblem generateMetadata); +use WeBWorK::PG::SampleProblemParser qw(parseSampleProblem generateMetadata); my $problem_dir = "$pg_root/tutorial/sample-problems"; -my ($out_dir, $pod_root, $pg_doc_home); +my ($out_dir, $pod_base_url, $sample_problem_base_url); my $verbose = 0; GetOptions( - "d|problem_dir=s" => \$problem_dir, - "o|out_dir=s" => \$out_dir, - "v|verbose" => \$verbose, - "p|pod_root=s" => \$pod_root, - "h|pg_doc_home=s" => \$pg_doc_home, + "d|problem-dir=s" => \$problem_dir, + "o|out-dir=s" => \$out_dir, + "p|pod-base-url=s" => \$pod_base_url, + "s|sample-problem-base-url=s" => \$sample_problem_base_url, + "v|verbose" => \$verbose ); -die "out_dir, pod_root, and pg_doc_home must be provided.\n" - unless $out_dir && $pod_root && $pg_doc_home; +pod2usage(2) unless $out_dir && $pod_base_url && $sample_problem_base_url; my $mt = Mojo::Template->new(vars => 1); my $template_dir = "$pg_root/tutorial/templates"; @@ -46,7 +70,7 @@ BEGIN my @problem_types = qw(sample technique snippet); -$pod_root .= '/pg/macros'; +$pod_base_url .= '/macros'; mkdir $out_dir unless -d $out_dir; # Build a hash of all PG files for linking. @@ -55,16 +79,16 @@ BEGIN for (keys %$index_table) { renderSampleProblem( $_ =~ s/.pg$//r, - metadata => $index_table, - macro_locations => $macro_locations, - pod_root => $pod_root, - pg_doc_home => $pg_doc_home, - url_extension => '.html', - problem_dir => $problem_dir, - out_dir => $out_dir, - template_dir => $template_dir, - mt => $mt, - verbose => $verbose + metadata => $index_table, + macro_locations => $macro_locations, + pod_base_url => $pod_base_url, + sample_problem_base_url => $sample_problem_base_url, + url_extension => '.html', + problem_dir => $problem_dir, + out_dir => $out_dir, + template_dir => $template_dir, + mt => $mt, + verbose => $verbose ); } diff --git a/htdocs/js/PODViewer/podviewer.css b/htdocs/js/PODViewer/podviewer.css new file mode 100644 index 0000000000..e4f17811d2 --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.css @@ -0,0 +1,66 @@ +.main-index-header, +.pod-header { + height: 65px; + top: 0; + left: 0; + right: 0; + z-index: 2; +} + +#sidebar { + --bs-offcanvas-width: 300px; + overflow-y: auto; +} + +#sidebar ul.nav ul.nav li { + border-left: 1px solid #e1e4e8; + padding-left: 10px; +} + +#sidebar ul.nav ul.nav li:hover { + border-left: 6px solid #e1e4e8; + padding-left: 5px; +} + +.main-index-container, +.pod-page-container { + margin-top: 65px; +} + +@media only screen and (min-width: 768px) { + #sidebar { + height: calc(100vh - 65px); + width: 300px; + } + + .pod-page-container { + margin-left: 300px; + } +} + +#_podtop_ pre { + border: 1px solid #ccc; + border-radius: 5px; + background: #f6f6f6; + padding: 0.75rem; +} + +#_podtop_, +#_podtop_ *[id] { + scroll-margin-top: calc(65px + 1rem); +} + +@media only screen and (max-width: 768px) { + .pod-header { + height: 100px; + } + + .pod-page-container { + margin-top: 100px; + } + + #_podtop_, + #_podtop_ *[id] { + scroll-margin-top: calc(100px + 1rem); + } +} diff --git a/htdocs/js/PODViewer/podviewer.js b/htdocs/js/PODViewer/podviewer.js new file mode 100644 index 0000000000..795093205a --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.js @@ -0,0 +1,8 @@ +(() => { + const offcanvas = bootstrap.Offcanvas.getOrCreateInstance(document.getElementById('sidebar')); + for (const link of document.querySelectorAll('#sidebar .nav-link')) { + // The timeout is to workaround an issue in Chrome. If the offcanvas hides before the window scrolls to the + // fragment in the page, scrolling stops before it gets there. + link.addEventListener('click', () => setTimeout(() => offcanvas.hide(), 500)); + } +})(); diff --git a/htdocs/js/SampleProblemViewer/documentation-search.js b/htdocs/js/SampleProblemViewer/documentation-search.js new file mode 100644 index 0000000000..e43c878ef7 --- /dev/null +++ b/htdocs/js/SampleProblemViewer/documentation-search.js @@ -0,0 +1,78 @@ +(async () => { + const searchBox = document.getElementById('search-box'); + const resultList = document.getElementById('result-list'); + if (!resultList || !searchBox) return; + + const rootURL = window.pgDocConfig?.rootURL ?? '.'; + const htmlSuffixMutation = window.pgDocConfig?.htmlSuffixMutation ?? [/\.p[gl]$/, '.html']; + const searchDataURL = window.pgDocConfig?.searchDataURL ?? 'sample-problem-search-data.json'; + + let searchData; + try { + const result = await fetch(searchDataURL); + searchData = await result.json(); + } catch (e) { + console.log(e); + return; + } + + const miniSearch = new MiniSearch({ + fields: ['filename', 'name', 'description', 'terms', 'macros', 'subjects'], + storeFields: ['type', 'filename', 'dir', 'description'] + }); + miniSearch.addAll(searchData); + + const searchMacrosCheck = document.getElementById('search-macros'); + const searchSampleProblemsCheck = document.getElementById('search-sample-problems'); + + document.getElementById('clear-search-button')?.addEventListener('click', () => { + searchBox.value = ''; + while (resultList.firstChild) resultList.firstChild.remove(); + }); + + const searchDocumentation = () => { + const searchMacros = searchMacrosCheck?.checked; + const searchSampleProblems = searchSampleProblemsCheck?.checked; + + while (resultList.firstChild) resultList.firstChild.remove(); + + if (!searchBox.value) return; + + for (const result of miniSearch.search(searchBox.value, { prefix: true })) { + if ( + (searchSampleProblems && result.type === 'sample problem') || + (searchMacros && result.type === 'macro') + ) { + const link = document.createElement('a'); + link.classList.add('list-group-item', 'list-group-item-action'); + link.href = `${rootURL}/${ + result.type === 'sample problem' ? 'sampleproblems' : result.type === 'macro' ? 'pod' : '' + }/${result.dir}/${result.filename.replace(...htmlSuffixMutation)}`; + + const linkText = document.createElement('span'); + linkText.classList.add('h4'); + linkText.textContent = `${result.filename} (${result.type})`; + link.append(linkText); + + if (result.description) { + const summary = document.createElement('div'); + summary.textContent = result.description; + link.append(summary); + } + + resultList.append(link); + } + } + + if (resultList.children.length == 0) { + const item = document.createElement('div'); + item.classList.add('alert', 'alert-info'); + item.innerHTML = 'No results found'; + resultList.append(item); + } + }; + + searchBox.addEventListener('keyup', searchDocumentation); + searchMacrosCheck?.addEventListener('change', searchDocumentation); + searchSampleProblemsCheck?.addEventListener('change', searchDocumentation); +})(); diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm old mode 100755 new mode 100644 diff --git a/lib/PGcore.pm b/lib/PGcore.pm old mode 100755 new mode 100644 diff --git a/lib/SampleProblemParser.pm b/lib/SampleProblemParser.pm deleted file mode 100644 index 30b5f8398b..0000000000 --- a/lib/SampleProblemParser.pm +++ /dev/null @@ -1,253 +0,0 @@ -package SampleProblemParser; -use parent qw(Exporter); - -use strict; -use warnings; -use experimental 'signatures'; -use feature 'say'; - -use File::Basename qw(dirname basename); -use File::Find qw(find); -use Pandoc; - -our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode); - -=head1 NAME - -SampleProblemParser - Parse the documentation in a sample problem in the /doc -directory. - -=head2 C - -Parse a PG file with extra documentation comments. The input is the file and a -hash of global variables: - -=over - -=item C: A reference to a hash which has information (name, directory, -types, subjects, categories) of every sample problem file. - -=item C: A reference to a hash of macros to include as links -within a problem. - -=item C: The root directory of the POD. - -=item C: The url of the pg_doc home. - -=item C: The html url extension (including the dot) to use for pg -doc links. The default is the empty string. - -=back - -=cut - -sub parseSampleProblem ($file, %global) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my (@blocks, @doc_rows, @code_rows, @description); - my (%options, $descr, $type, $name); - - $global{url_extension} //= ''; - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # skip this, already parsed. - } elsif ($row =~ /^#:%\s*(.*)?/) { - # The row has the form #:% section = NAME. - # This should parse the previous named section and then reset @doc_rows and @code_rows. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ) if %options; - %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); - @doc_rows = (); - @code_rows = (); - } elsif ($row =~ /^#:/) { - # This section is documentation to be parsed. - $row = $row =~ s/^#:\s?//r; - - # Parse any LINK/PODLINK/PROBLINK commands in the documentation. - if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { - my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; - my $url = - defined($1) - ? $1 eq 'POD' - ? "$global{pod_root}/" . $global{macro_locations}{ $4 // $2 } - : "$global{pg_doc_home}/$global{metadata}{$2}{dir}/" . ($2 =~ s/.pg$/$global{url_extension}/r) - : $4; - $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; - } - - push(@doc_rows, $row); - } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { - $descr = $1 ? 0 : 1; - } elsif ($row =~ /^##/ && $descr) { - push(@description, $row =~ s/^##\s*//r); - push(@code_rows, $row); - } else { - push(@code_rows, $row); - } - } - - # The last @doc_rows must be parsed then added to the @blocks. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ); - - return { - name => $global{metadata}{$filename}{name}, - blocks => \@blocks, - code => join("\n", map { $_->{code} } @blocks), - description => join("\n", @description) - }; -} - -=head2 C - -Build a hash of metadata for all PG files in the given directory. A reference -to the hash that is built is returned. - -=cut - -sub generateMetadata ($problem_dir, %options) { - my $index_table = {}; - - find( - { - wanted => sub { - say "Reading file: $File::Find::name" if $options{verbose}; - - if ($File::Find::name =~ /\.pg$/) { - my $metadata = parseMetadata($File::Find::name, $problem_dir); - unless (@{ $metadata->{types} }) { - warn "The type of sample problem is missing for $File::Find::name."; - return; - } - unless ($metadata->{name}) { - warn "The name attribute is missing for $File::Find::name."; - return; - } - $index_table->{ basename($File::Find::name) } = $metadata; - } - } - }, - $problem_dir - ); - - return $index_table; -} - -my @macros_to_skip = qw( - PGML.pl - PGcourse.pl - PGstandard.pl -); - -sub parseMetadata ($path, $problem_dir) { - open(my $FH, '<:encoding(UTF-8)', $path) or do { - warn qq{Could not open file "$path": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my @problem_types = qw(sample technique snippet); - - my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; - - while (my $row = shift @file_contents) { - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # The row has the form #:% categories = [cat1, cat2, ...]. - my $label = lc($1); - my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); - if ($label =~ /types?/) { - for my $opt (@opts) { - warn "The type of problem must be one of @problem_types" - unless grep { lc($opt) eq $_ } @problem_types; - } - $metadata->{types} = [ map { lc($_) } @opts ]; - } elsif ($label =~ /^categor/) { - $metadata->{categories} = \@opts; - } elsif ($label =~ /^subject/) { - $metadata->{subjects} = [ map { lc($_) } @opts ]; - } elsif ($label eq 'name') { - $metadata->{name} = $opts[0]; - } elsif ($label eq 'see_also') { - $metadata->{related} = \@opts; - } - } elsif ($row =~ /loadMacros\(/) { - chomp($row); - # Parse the macros, which may be on multiple rows. - my $macros = $row; - while ($row && $row !~ /\);\s*$/) { - $row = shift @file_contents; - chomp($row); - $macros .= $row; - } - # Split by commas and pull out the quotes. - my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); - $metadata->{macros} = []; - for my $macro (@macros) { - push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; - } - } - } - - return $metadata; -} - -=head2 C - -Parse a PG file with extra documentation comments and strip that all out -returning the clean problem code. This returns the same code that the -C returns, except at much less expense as it does not parse -the documentation, it does not require that the metadata be parsed first, and it -does not need macro POD information. - -=cut - -sub getSampleProblemCode ($file) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return ''; - }; - my @file_contents = <$FH>; - close $FH; - - my (@code_rows, $inCode); - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:(.*)?/) { - # This is documentation so skip it. - } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { - $inCode = $1 ? 0 : 1; - push(@code_rows, $row); - } elsif ($inCode) { - push(@code_rows, $row); - } - } - - return join("\n", @code_rows); -} - -1; diff --git a/lib/WeBWorK/PG/SampleProblemParser.pm b/lib/WeBWorK/PG/SampleProblemParser.pm new file mode 100644 index 0000000000..42fd73ffc3 --- /dev/null +++ b/lib/WeBWorK/PG/SampleProblemParser.pm @@ -0,0 +1,474 @@ +package WeBWorK::PG::SampleProblemParser; +use parent qw(Exporter); + +use strict; +use warnings; +use experimental 'signatures'; +use feature 'say'; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname->dirname->dirname; } + +use File::Basename qw(dirname basename); +use File::Find qw(find); +use Mojo::File qw(path); +use Mojo::JSON qw(decode_json encode_json); +use Pandoc; +use Pod::Simple::Search; +use Pod::Simple::SimpleTree; + +our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode getSearchData); + +=head1 NAME + +WeBWorK::PG::SampleProblemParser - Parse sample problems and extract metadata, +documentation, and code. + +=head2 parseSampleProblem + +Parse a PG file with extra documentation comments. The input is the file and a +hash of global variables: + +=over + +=item * + +C: A reference to a hash which has information (name, directory, +types, subjects, categories) of every sample problem file. + +=item * + +C: A reference to a hash of macros to include as links within a +problem. + +=item * + +C: The base URL for the POD HTML files. + +=item * + +C: The base URL for the sample problem HTML files. + +=item * + +C: The html url extension (including the dot) to use for pg doc +links. The default is the empty string. + +=back + +=cut + +sub parseSampleProblem ($file, %global) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my (@blocks, @doc_rows, @code_rows, @description); + my (%options, $descr, $type, $name); + + $global{url_extension} //= ''; + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # skip this, already parsed. + } elsif ($row =~ /^#:%\s*(.*)?/) { + # The row has the form #:% section = NAME. + # This should parse the previous named section and then reset @doc_rows and @code_rows. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ) if %options; + %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); + @doc_rows = (); + @code_rows = (); + } elsif ($row =~ /^#:/) { + # This section is documentation to be parsed. + $row = $row =~ s/^#:\s?//r; + + # Parse any LINK/PODLINK/PROBLINK commands in the documentation. + if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { + my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; + my $url = + defined($1) + ? $1 eq 'POD' + ? "$global{pod_base_url}/" . $global{macro_locations}{ $4 // $2 } + : "$global{sample_problem_base_url}/$global{metadata}{$2}{dir}/" + . ($2 =~ s/.pg$/$global{url_extension}/r) + : $4; + $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; + } + + push(@doc_rows, $row); + } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { + $descr = $1 ? 0 : 1; + } elsif ($row =~ /^##/ && $descr) { + push(@description, $row =~ s/^##\s*//r); + push(@code_rows, $row); + } else { + push(@code_rows, $row); + } + } + + # The last @doc_rows must be parsed then added to the @blocks. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ); + + return { + name => $global{metadata}{$filename}{name}, + blocks => \@blocks, + code => join("\n", map { $_->{code} } @blocks), + description => join("\n", @description) + }; +} + +=head2 generateMetadata + +Build a hash of metadata for all PG files in the given directory. A reference +to the hash that is built is returned. + +=cut + +sub generateMetadata ($problem_dir, %options) { + my $index_table = {}; + + find( + { + wanted => sub { + say "Reading file: $File::Find::name" if $options{verbose}; + + if ($File::Find::name =~ /\.pg$/) { + my $metadata = parseMetadata($File::Find::name, $problem_dir); + unless (@{ $metadata->{types} }) { + warn "The type of sample problem is missing for $File::Find::name."; + return; + } + unless ($metadata->{name}) { + warn "The name attribute is missing for $File::Find::name."; + return; + } + $index_table->{ basename($File::Find::name) } = $metadata; + } + } + }, + $problem_dir + ); + + return $index_table; +} + +my @macros_to_skip = qw( + PGML.pl + PGcourse.pl + PGstandard.pl +); + +sub parseMetadata ($path, $problem_dir) { + open(my $FH, '<:encoding(UTF-8)', $path) or do { + warn qq{Could not open file "$path": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my @problem_types = qw(sample technique snippet); + + my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; + + while (my $row = shift @file_contents) { + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # The row has the form #:% categories = [cat1, cat2, ...]. + my $label = lc($1); + my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); + if ($label =~ /types?/) { + for my $opt (@opts) { + warn "The type of problem must be one of @problem_types" + unless grep { lc($opt) eq $_ } @problem_types; + } + $metadata->{types} = [ map { lc($_) } @opts ]; + } elsif ($label =~ /^categor/) { + $metadata->{categories} = \@opts; + } elsif ($label =~ /^subject/) { + $metadata->{subjects} = [ map { lc($_) } @opts ]; + } elsif ($label eq 'name') { + $metadata->{name} = $opts[0]; + } elsif ($label eq 'see_also') { + $metadata->{related} = \@opts; + } + } elsif ($row =~ /loadMacros\(/) { + chomp($row); + # Parse the macros, which may be on multiple rows. + my $macros = $row; + while ($row && $row !~ /\);\s*$/) { + $row = shift @file_contents; + chomp($row); + $macros .= $row; + } + # Split by commas and pull out the quotes. + my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + $metadata->{macros} = []; + for my $macro (@macros) { + push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; + } + } + } + + return $metadata; +} + +=head2 getSampleProblemCode + +Parse a PG file with extra documentation comments and strip that all out +returning the clean problem code. This returns the same code that +C returns, except at much less expense as it does not parse +the documentation, it does not require that the metadata be parsed first, and it +does not need macro POD information. + +=cut + +sub getSampleProblemCode ($file) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return ''; + }; + my @file_contents = <$FH>; + close $FH; + + my (@code_rows, $inCode); + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:(.*)?/) { + # This is documentation so skip it. + } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { + $inCode = $1 ? 0 : 1; + push(@code_rows, $row); + } elsif ($inCode) { + push(@code_rows, $row); + } + } + + return join("\n", @code_rows); +} + +=head2 getSearchData + +Generate search data for sample problem files and macro POD. The only argument +is required and should be a file name to write the search data to. If the file +does not exist, then a new file containing the generated search data will be +written. If the file exists and contains search data from previously using this +method, then the data will be updated based on file modification times of the +sample problem files and macros. In any case an array reference containing the +generated search data will be returned. + +=cut + +my $stopWordsCache; + +sub getSearchData ($searchDataFileName) { + my $searchDataFile = path($searchDataFileName); + my %files = map { $_->{filename} => $_ } @{ (eval { decode_json($searchDataFile->slurp('UTF-8')) } // []) }; + my @updatedFiles; + + my $stopWords = sub ($word) { + return $stopWordsCache->{$word} if $stopWordsCache; + $stopWordsCache = {}; + + my $contents = eval { path("$pgRoot/assets/stop-words-en.txt")->slurp('UTF-8') }; + return $stopWordsCache if $@; + + for my $line (split("\n", $contents)) { + chomp $line; + next if $line =~ /^#/ || !$line; + $stopWordsCache->{$line} = 1; + } + + return $stopWordsCache->{$word}; + }; + + my $processLine = sub ($line) { + my %words; + + # Extract linked macros and problems. + my @linkedFiles = $line =~ /(?:PODLINK|PROBLINK)\('([\w.]+)'\)/g; + $words{$_} = 1 for @linkedFiles; + + # Replace any non-word characters with spaces. + $line =~ s/\W/ /g; + + for my $word (split(/\s+/, $line)) { + next if $word =~ /^\d*$/; + $word = lc($word); + $words{$word} = 1 if !$stopWords->($word); + } + return keys %words; + }; + + # Extract the text for a section from the given POD with a section header title. + my $extractHeadText = sub ($root, $title) { + my @index = grep { ref($root->[$_]) eq 'ARRAY' && $root->[$_][2] eq $title } 0 .. $#$root; + return unless @index == 1; + + my $node = $root->[ $index[0] + 1 ]; + my $str = ''; + for (2 .. $#$node) { + $str .= ref($node->[$_]) eq 'ARRAY' ? $node->[$_][2] : $node->[$_]; + } + return $str; + }; + + # Extract terms form POD headers. + my $extractHeaders = sub ($root) { + my %terms = + map { $_ => 1 } + grep { $_ && !$stopWords->($_) } + map { split(/\s+/, $_) } + map { lc($_) =~ s/\W/ /gr } + map { + grep { !ref($_) } + @$_[ 2 .. $#$_ ] + } grep { ref($_) eq 'ARRAY' && $_->[0] =~ /^head\d+$/ } @$root; + return [ keys %terms ]; + }; + + # Process the sample problems in the sample problem directory. + find( + { + wanted => sub { + return unless $_ =~ /\.pg$/; + + my $file = path($File::Find::name); + my $lastModified = $file->stat->mtime; + + if ($files{$_}) { + push(@updatedFiles, $files{$_}); + return if $files{$_}{lastModified} >= $lastModified; + } + + my @fileContents = eval { split("\n", $file->slurp('UTF-8')) }; + return if $@; + + if (!$files{$_}) { + $files{$_} = { + type => 'sample problem', + filename => $_, + dir => $file->dirname->basename + }; + push(@updatedFiles, $files{$_}); + } + $files{$_}{lastModified} = $lastModified; + + my (%words, @kw, @macros, @subjects, $description); + + while (@fileContents) { + my $line = shift @fileContents; + if ($line =~ /^#:%\s*(\w+)\s*=\s*(.*)\s*$/) { + # Store the name and subjects. + $files{$_}{name} = $2 if $1 eq 'name'; + if ($1 eq 'subject') { + @subjects = split(',\s*', $2 =~ s/\[(.*)\]/$1/r); + } + } elsif ($line =~ /^#:\s*(.*)?/) { + my @newWords = $processLine->($1); + @words{@newWords} = (1) x @newWords if @newWords; + } elsif ($line =~ /loadMacros\(/) { + my $macros = $line; + while ($line && $line !~ /\);\s*$/) { + $line = shift @fileContents; + $macros .= $line; + } + my @usedMacros = + map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + + # Get the macros other than PGML.pl, PGstandard.pl, and PGcourse.pl. + for my $m (@usedMacros) { + push(@macros, $m) unless $m =~ /^(PGML|PGstandard|PGcourse)\.pl$/; + } + } elsif ($line =~ /##\s*KEYWORDS\((.*)\)/) { + @kw = map {s/^'(.*)'$/$1/r} split(/,\s*/, $1); + } elsif ($line =~ /^##\s*DESCRIPTION/) { + $line = shift(@fileContents); + while ($line && $line !~ /^##\s*ENDDESCRIPTION/) { + $description .= ($line =~ s/^##\s+//r) . ' '; + $line = shift(@fileContents); + } + $description =~ s/\s+$//; + } + } + + $files{$_}{description} = $description; + $files{$_}{subjects} = \@subjects; + $files{$_}{terms} = [ keys %words ]; + $files{$_}{keywords} = \@kw; + $files{$_}{macros} = \@macros; + + return; + } + }, + "$pgRoot/tutorial/sample-problems" + ); + + # Process the POD in macros in the macros directory. + (undef, my $macroFiles) = Pod::Simple::Search->new->inc(0)->survey("$pgRoot/macros"); + for my $macroFile (sort keys %$macroFiles) { + next if $macroFile =~ /deprecated/; + + my $file = path($macroFile); + my $fileName = $file->basename; + my $lastModified = $file->stat->mtime; + + if ($files{$fileName}) { + push(@updatedFiles, $files{$fileName}); + next if $files{$fileName}{lastModified} >= $lastModified; + } + + if (!$files{$fileName}) { + $files{$fileName} = { + type => 'macro', + id => scalar(keys %files) + 1, + filename => $fileName, + dir => $file->dirname->to_rel($pgRoot)->to_string + }; + push(@updatedFiles, $files{$fileName}); + } + $files{$fileName}{lastModified} = $lastModified; + + my $root = Pod::Simple::SimpleTree->new->parse_file($file->to_string)->root; + + $files{$fileName}{terms} = $extractHeaders->($root); + + if (my $nameDescription = $extractHeadText->($root, 'NAME')) { + (undef, my $description) = split(/\s*-\s*/, $nameDescription, 2); + $files{$fileName}{description} = $description if $description; + } + } + + # Re-index in case files were added or removed. + my $count = 0; + $_->{id} = ++$count for @updatedFiles; + + $searchDataFile->spew(encode_json(\@updatedFiles), 'UTF-8'); + + return \@updatedFiles; +} + +1; diff --git a/lib/WeBWorK/Utils/PODParser.pm b/lib/WeBWorK/Utils/PODParser.pm new file mode 100644 index 0000000000..57950d46b0 --- /dev/null +++ b/lib/WeBWorK/Utils/PODParser.pm @@ -0,0 +1,66 @@ +package WeBWorK::Utils::PODParser; +use parent qw(Pod::Simple::XHTML); + +use strict; +use warnings; + +use Pod::Simple::XHTML; +use File::Basename qw(basename); + +# $podFiles must be provided in order for pod links to local files to work. It should be the +# first return value of the POD::Simple::Search survey method. +sub new { + my ($invocant, $podFiles) = @_; + my $class = ref $invocant || $invocant; + my $self = $class->SUPER::new(@_); + $self->perldoc_url_prefix('https://metacpan.org/pod/'); + $self->index(1); + $self->backlink(1); + $self->html_charset('UTF-8'); + $self->{podFiles} = $podFiles // {}; + return bless $self, $class; +} + +# Attempt to resolve links to local files. If a local file is not found, then +# let Pod::Simple::XHTML resolve to a cpan link. +sub resolve_pod_page_link { + my ($self, $target, $section) = @_; + + unless (defined $target) { + print "Using internal page link.\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); + } + + my $podFound; + for (keys %{ $self->{podFiles} }) { + if ($target eq $_ =~ s/lib:://r || $target eq basename($self->{podFiles}{$_}) =~ s/\.pod$//r) { + $podFound = + $self->{assert_html_ext} ? ($self->{podFiles}{$_} =~ s/\.(pm|pl|pod)$/.html/r) : $self->{podFiles}{$_}; + last; + } + } + + if ($podFound) { + my $pod_url = $self->encode_entities($podFound =~ s/^$self->{source_root}/$self->{base_url}/r) + . ($section ? '#' . $self->idify($self->encode_entities($section), 1) : ''); + print "Resolved local pod link for $target" . ($section ? "/$section" : '') . " to $pod_url\n" + if $self->{verbose} > 2; + return $pod_url; + } + + print "Using cpan pod link for $target" . ($section ? "/$section" : '') . "\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); +} + +# Trim spaces from the beginning of each line in code blocks. This attempts to +# trim spaces from all lines in the code block in the same amount as there are +# spaces at the beginning of the first line. Note that Pod::Simple::XHTML has +# already converted tab characters into 8 spaces. +sub handle_code { + my ($self, $code) = @_; + my $start_spaces = length(($code =~ /^( *)/)[0]) || ''; + $self->SUPER::handle_code($code =~ s/^( {1,$start_spaces})//gmr); + return; +} + +1; diff --git a/lib/WeBWorK/Utils/PODtoHTML.pm b/lib/WeBWorK/Utils/PODtoHTML.pm new file mode 100644 index 0000000000..19c30e7f50 --- /dev/null +++ b/lib/WeBWorK/Utils/PODtoHTML.pm @@ -0,0 +1,212 @@ +package WeBWorK::Utils::PODtoHTML; + +use strict; +use warnings; +use utf8; + +use Pod::Simple::Search; +use Mojo::Template; +use Mojo::DOM; +use Mojo::Collection qw(c); +use File::Path qw(make_path); +use File::Basename qw(dirname); +use IO::File; +use POSIX qw(strftime); + +use WeBWorK::Utils::PODParser; + +our @sections = ( + doc => 'Documentation', + bin => 'Scripts', + macros => 'Macros', + lib => 'Libraries', +); +our %macro_names = ( + answers => 'Answers', + contexts => 'Contexts', + core => 'Core', + deprecated => 'Deprecated', + graph => 'Graph', + math => 'Math', + misc => 'Miscellaneous', + parsers => 'Parsers', + ui => 'User Interface' +); + +sub new { + my ($invocant, %o) = @_; + my $class = ref $invocant || $invocant; + + my @section_list = ref($o{sections}) eq 'ARRAY' ? @{ $o{sections} } : @sections; + my $section_hash = {@section_list}; + my $section_order = [ map { $section_list[ 2 * $_ ] } 0 .. $#section_list / 2 ]; + delete $o{sections}; + + my $self = { + %o, + idx => {}, + section_hash => $section_hash, + section_order => $section_order, + macros_hash => {}, + }; + return bless $self, $class; +} + +sub convert_pods { + my $self = shift; + my $source_root = $self->{source_root}; + my $dest_root = $self->{dest_root}; + + my $regex = join('|', map {"^$_"} @{ $self->{section_order} }); + + my ($name2path, $path2name) = Pod::Simple::Search->new->inc(0)->limit_re(qr!$regex!)->survey($self->{source_root}); + for (keys %$path2name) { + print "Processing file: $_\n" if $self->{verbose} > 1; + $self->process_pod($_, $name2path); + } + + $self->write_index("$dest_root/index.html"); + + return; +} + +sub process_pod { + my ($self, $pod_path, $pod_files) = @_; + + my $pod_name; + + my ($subdir, $filename) = $pod_path =~ m|^$self->{source_root}/(?:(.*)/)?(.*)$|; + + my ($subdir_first, $subdir_rest) = ('', ''); + + if (defined $subdir) { + if ($subdir =~ m|/|) { + ($subdir_first, $subdir_rest) = $subdir =~ m|^([^/]*)/(.*)|; + } else { + $subdir_first = $subdir; + } + } + + $pod_name = (defined $subdir_rest ? "$subdir_rest/" : '') . $filename; + if ($filename =~ /\.pl$/) { + $filename =~ s/\.pl$/.html/; + } elsif ($filename =~ /\.pod$/) { + $pod_name =~ s/\.pod$//; + $filename =~ s/\.pod$/.html/; + } elsif ($filename =~ /\.pm$/) { + $pod_name =~ s/\.pm$//; + $pod_name =~ s|/+|::|g; + $filename =~ s/\.pm$/.html/; + } elsif ($filename !~ /\.html$/) { + $filename .= '.html'; + } + + $pod_name =~ s/^(\/|::)//; + + my $html_dir = $self->{dest_root} . (defined $subdir ? "/$subdir" : ''); + my $html_path = "$html_dir/$filename"; + my $html_rel_path = defined $subdir ? "$subdir/$filename" : $filename; + + $self->update_index($subdir, $html_rel_path, $pod_name); + make_path($html_dir); + my $html = $self->do_pod2html( + pod_path => $pod_path, + pod_name => $pod_name, + pod_files => $pod_files + ); + my $fh = IO::File->new($html_path, '>:encoding(UTF-8)') + or die "Failed to open file '$html_path' for writing: $!\n"; + print $fh $html; + + return; +} + +sub update_index { + my ($self, $subdir, $html_rel_path, $pod_name) = @_; + + $subdir =~ s|/.*$||; + my $idx = $self->{idx}; + my $sections = $self->{section_hash}; + if ($subdir eq 'macros') { + $idx->{macros} = []; + if ($pod_name =~ m!^(.+)/(.+)$!) { + push @{ $self->{macros_hash}{$1} }, [ $html_rel_path, $2 ]; + } else { + push @{ $idx->{doc} }, [ $html_rel_path, $pod_name ]; + } + } elsif (exists $sections->{$subdir}) { + push @{ $idx->{$subdir} }, [ $html_rel_path, $pod_name ]; + } else { + warn "no section for subdir '$subdir'\n"; + } + + return; +} + +sub write_index { + my ($self, $out_path) = @_; + + my $fh = IO::File->new($out_path, '>:encoding(UTF-8)') or die "Failed to open index '$out_path' for writing: $!\n"; + print $fh Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/category-index.mt", + { + title => 'POD for ' . ($self->{source_root} =~ s|^.*/||r), + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + pod_index => $self->{idx}, + sections => $self->{section_hash}, + section_order => $self->{section_order}, + macros => $self->{macros_hash}, + macros_order => [ sort keys %{ $self->{macros_hash} } ], + macro_names => \%macro_names, + date => strftime('%a %b %e %H:%M:%S %Z %Y', localtime) + } + ); + + return; +} + +sub do_pod2html { + my ($self, %o) = @_; + + my $psx = WeBWorK::Utils::PODParser->new($o{pod_files}); + $psx->{source_root} = $self->{source_root}; + $psx->{verbose} = $self->{verbose}; + $psx->{assert_html_ext} = 1; + $psx->{base_url} = $self->{page_url} // $self->{dest_url} // ''; + $psx->output_string(\my $html); + $psx->html_header(''); + $psx->html_footer(''); + $psx->parse_file($o{pod_path}); + + my $dom = Mojo::DOM->new($html); + my $podIndexUL = $dom->at('ul[id="index"]'); + my $podIndex = $podIndexUL ? $podIndexUL->find('ul[id="index"] > li') : c(); + for (@$podIndex) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + for (@{ $_->find('ul') }) { + $_->attr({ class => 'nav flex-column w-100' }); + } + for (@{ $_->find('li') }) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + } + } + my $podHTML = $podIndexUL ? $podIndexUL->remove : $html; + + return Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/pod.mt", + { + title => $o{pod_name}, + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + index => $podIndex, + content => $podHTML + } + ); +} + +1; diff --git a/tutorial/sample-problems/README.md b/tutorial/sample-problems/README.md index 4b8b56ce11..3d2cbd2ed7 100644 --- a/tutorial/sample-problems/README.md +++ b/tutorial/sample-problems/README.md @@ -73,16 +73,16 @@ All lines following the documentation lines are considered code until the next ` ## Generate the documentation -The documentation is generated with the `parse-prob-doc.pl` script in the `bin` +The documentation is generated with the `parse-problem-doc.pl` script in the `bin` directory of pg. There are the following options (and many are required): -- `problem_dir` or `d`: The directory where the sample problems are. This defaults to +- `problem-dir` or `d`: The directory where the sample problems are. This defaults to `PG_ROOT/tutorial/sample-problems` if not passed in. -- `out_dir` or `o`: The directory where the resulting documentation files (HTML) +- `out-dir` or `o`: The directory where the resulting documentation files (HTML) will be located. -- `pod_root` or `p`: The URL where the POD is located. This is needed to +- `pod-base-url` or `p`: The URL where the POD is located. This is needed to correctly link POD from the sample problems. -- `pg_doc_home` or `h`: The URL of the directory for `out_dir`. This is needed +- `sample-problem-base-url` or `s`: The URL of the directory for `out-dir`. This is needed for correct linking. - `verbose` or `v`: verbose mode. @@ -94,7 +94,7 @@ produce four different ways of categorizing the problems. - an html file with the documented PG file - a pg file with the documentation removed. There is a link to this in the html file. -The script `parse-prob-doc.pl` parses each pg file and uses the `problem-template.mt` +The script `parse-problem-doc.pl` parses each pg file and uses the `problem-template.mt` template file to generate the html. This template is processed using the `Mojo::Template` Perl module. See the [Mojo::Template documentation](https://docs.mojolicious.org/Mojo/Template) for more information. diff --git a/tutorial/templates/general-layout.mt b/tutorial/templates/general-layout.mt index 3f50146abd..b7d9fac40d 100644 --- a/tutorial/templates/general-layout.mt +++ b/tutorial/templates/general-layout.mt @@ -5,7 +5,7 @@ PG Sample Problems - + - + diff --git a/tutorial/templates/index.html b/tutorial/templates/index.html new file mode 100644 index 0000000000..aa271047f4 --- /dev/null +++ b/tutorial/templates/index.html @@ -0,0 +1,139 @@ + + + + PG Documentation + + + + + + + + +
    +
    +

    PG Documentation

    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +

    + This is the documentation for PG, the problem authoring language for WeBWorK. The links below + include sample problems demonstrating problem authoring techniques and POD (Plain Old + Documentation), explaining how to use PG macros and related modules. +

    +
    +
    + +
    + + diff --git a/tutorial/templates/problem-template.mt b/tutorial/templates/problem-template.mt index dfd9d3b798..f31700139c 100644 --- a/tutorial/templates/problem-template.mt +++ b/tutorial/templates/problem-template.mt @@ -5,12 +5,12 @@ <%= $filename %> - - + + + - % # Default explanations @@ -30,7 +30,7 @@

    <%= $description %>

    @@ -44,7 +44,9 @@
      % for my $macro (@{$metadata->{$filename}{macros}}) { % if ($macro_locations->{$macro}) { -
    • <%= $macro =%>
    • +
    • + <%= $macro =%> +
    • % } else {
    • <%= $macro %>
    • % } @@ -58,7 +60,7 @@
        % for (@{$metadata->{$filename}{related}}) {
      • - + <%= $metadata->{$_}{name} =%>
      • @@ -76,10 +78,7 @@
        <%== $_->{code} %>
        From ca97ffa9e759273df0666d87c7d86d1c229d98c2 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sun, 21 Dec 2025 19:55:48 -0600 Subject: [PATCH 097/111] Upgrade JSXGraph to the latest version and fix the graphtool for that. The latest version of JXSGraph is 1.12.2. However, that version (and all versions after 1.11.1) have an issue with tab order when keyboard events are enabled. Basically, it is impossible to use shift-tab to progress in reverse in the tab order. See https://github.com/jsxgraph/jsxgraph/issues/773. So to work around that I had to override the board's `keyDownListener` method with one that does not call `preventDefault` on a keydown event that comes from a tab key being used. This override can be removed after upgrading to the next version of JSXGraph after 1.12.2 as this has been fixed in their develop branch and that fix will be in the next release. Another thing that is a bit annoying with versions 1.11.1 and later is that you now have to set the tabindex on elements that are not fixed yourself. By default they set the tabindex to -1, which means they are not in the tab order. So `gt.definingPointAttributes` is now a function, and if it is called with `gt.isStatic` true, a tabindex of -1 is used, but if `gt.isStatic` is false, then a tabindex of 0 is used (and so those points will be keyboard focusable). --- htdocs/js/GraphTool/circletool.js | 2 +- htdocs/js/GraphTool/cubictool.js | 2 +- htdocs/js/GraphTool/graphtool.js | 116 ++++++++++++++++++++++++++- htdocs/js/GraphTool/intervaltools.js | 3 +- htdocs/js/GraphTool/linetool.js | 2 +- htdocs/js/GraphTool/parabolatool.js | 2 +- htdocs/js/GraphTool/pointtool.js | 3 +- htdocs/js/GraphTool/quadratictool.js | 2 +- htdocs/js/GraphTool/quadrilateral.js | 2 +- htdocs/js/GraphTool/sinewavetool.js | 2 +- htdocs/js/GraphTool/triangle.js | 2 +- htdocs/package-lock.json | 14 ++-- htdocs/package.json | 2 +- 13 files changed, 132 insertions(+), 22 deletions(-) diff --git a/htdocs/js/GraphTool/circletool.js b/htdocs/js/GraphTool/circletool.js index 50b9c663fc..af51d27acf 100644 --- a/htdocs/js/GraphTool/circletool.js +++ b/htdocs/js/GraphTool/circletool.js @@ -188,7 +188,7 @@ const center = this.center; delete this.center; - center.setAttribute(gt.definingPointAttributes); + center.setAttribute(gt.definingPointAttributes()); center.on('down', () => gt.onPointDown(center)); center.on('up', () => gt.onPointUp(center)); diff --git a/htdocs/js/GraphTool/cubictool.js b/htdocs/js/GraphTool/cubictool.js index c65983a3f4..46b4e23b27 100644 --- a/htdocs/js/GraphTool/cubictool.js +++ b/htdocs/js/GraphTool/cubictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index c674837746..0991e15fee 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -39,7 +39,7 @@ window.graphTool = (containerId, options) => { underConstructionFixed: JXG.palette.red // defined to be '#d55e00' }; - gt.definingPointAttributes = { + gt.definingPointAttributes = () => ({ size: 3, fixed: false, highlight: true, @@ -49,8 +49,9 @@ window.graphTool = (containerId, options) => { fillColor: gt.color.point, highlightStrokeWidth: 1, highlightStrokeColor: gt.color.focusCurve, - highlightFillColor: gt.color.pointHighlight - }; + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 + }); gt.options = options; gt.snapSizeX = options.snapSizeX ? options.snapSizeX : 1; @@ -262,6 +263,113 @@ window.graphTool = (containerId, options) => { gt.board.highlightInfobox = (_x, _y, el) => gt.board.highlightCustomInfobox('', el); if (!gt.isStatic) { + // FIXME: Delete this after upgrading to the next version of JSXGraph as this has been fixed in their + // develop branch and will be in that release. + // This is a mess to work around an issue with JSXGraph versions 1.11.1 or later. Their keyDownListener + // calls preventDefault on the keydown event when shift-tab is pressed. That prevents keyboard focus from + // moving backward in the tab order. So this removes the JSXGraph keyboard event handlers, then overrides + // the board's keyDownListener with essentially the same code with the exception that when the tab key is + // pressed, preventDefault is not called. Then the keyboard event listeners are added back, using this + // keydownListener. + gt.board.removeKeyboardEventHandlers(); + gt.board.keyDownListener = function (evt) { + const id_node = evt.target.id; + let done = true; + + if (!this.attr.keyboard.enabled || id_node === '' || evt.keyCode === 9) return false; + + const doc = this.containerObj.shadowRoot || document; + if (doc.activeElement) { + if (doc.activeElement.tagName === 'INPUT' || doc.activeElement.tagName === 'textarea') return false; + } + + const id = id_node.replace(this.containerObj.id + '_', ''); + const el = this.select(id); + + if ( + (JXG.evaluate(this.attr.keyboard.panshift) && evt.shiftKey) || + (JXG.evaluate(this.attr.keyboard.panctrl) && evt.ctrlKey) + ) { + const doZoom = JXG.evaluate(this.attr.zoom.enabled) === true; + if (evt.keyCode === 38) this.clickUpArrow(); + else if (evt.keyCode === 40) this.clickDownArrow(); + else if (evt.keyCode === 37) this.clickLeftArrow(); + else if (evt.keyCode === 39) this.clickRightArrow(); + else if (doZoom && evt.keyCode === 171) this.zoomIn(); + else if (doZoom && evt.keyCode === 173) this.zoomOut(); + else if (doZoom && evt.keyCode === 79) this.zoom100(); + else done = false; + } else if (!evt.shiftKey && !evt.ctrlKey) { + let dx = JXG.evaluate(this.attr.keyboard.dx) / this.unitX; + let dy = JXG.evaluate(this.attr.keyboard.dy) / this.unitY; + if (JXG.exists(el.visProp)) { + if ( + JXG.exists(el.visProp.snaptogrid) && + el.visProp.snaptogrid && + el.evalVisProp('snapsizex') && + el.evalVisProp('snapsizey') + ) { + const res = el.getSnapSizes(); + dx = res[0]; + dy = res[1]; + } else if ( + JXG.exists(el.visProp.attracttogrid) && + el.visProp.attracttogrid && + el.evalVisProp('attractordistance') && + el.evalVisProp('attractorunit') + ) { + let sX = 1.1 * el.evalVisProp('attractordistance'); + let sY = sX; + if (el.evalVisProp('attractorunit') === 'screen') { + sX /= this.unitX; + sY /= this.unitX; + } + dx = Math.max(sX, dx); + dy = Math.max(sY, dy); + } + } + + let dir; + if (evt.keyCode === 38) dir = [0, dy]; + else if (evt.keyCode === 40) dir = [0, -dy]; + else if (evt.keyCode === 37) dir = [-dx, 0]; + else if (evt.keyCode === 39) dir = [dx, 0]; + else done = false; + + if ( + dir && + el.isDraggable && + el.visPropCalc.visible && + ((this.geonextCompatibilityMode && + (JXG.isPoint(el) || el.elementClass === Const.OBJECT_CLASS_TEXT)) || + !this.geonextCompatibilityMode) && + !el.evalVisProp('fixed') + ) { + this.mode = this.BOARD_MODE_DRAG; + if (JXG.exists(el.coords)) { + const actPos = el.coords.usrCoords.slice(1); + dir[0] += actPos[0]; + dir[1] += actPos[1]; + el.setPosition(JXG.COORDS_BY_USER, dir); + this.updateInfobox(el); + } else { + this.displayInfobox(false); + el.setPositionDirectly(Const.COORDS_BY_USER, dir, [0, 0]); + } + + this.triggerEventHandlers(['keymove', 'move'], [evt, this.mode]); + el.triggerEventHandlers(['keydrag', 'drag'], [evt]); + this.mode = this.BOARD_MODE_NONE; + } + } + + this.update(); + + if (done && JXG.exists(evt.preventDefault)) evt.preventDefault(); + return done; + }; + gt.board.addKeyboardEventHandlers(); + gt.graphContainer.tabIndex = -1; gt.board.containerObj.tabIndex = -1; @@ -805,7 +913,7 @@ window.graphTool = (containerId, options) => { const point = gt.board.create('point', [gt.snapRound(x, gt.snapSizeX), gt.snapRound(y, gt.snapSizeY)], { snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - ...gt.definingPointAttributes + ...gt.definingPointAttributes() }); point.setAttribute({ snapToGrid: true }); if (!gt.isStatic) { diff --git a/htdocs/js/GraphTool/intervaltools.js b/htdocs/js/GraphTool/intervaltools.js index 8839e82dcb..8fa9a720cb 100644 --- a/htdocs/js/GraphTool/intervaltools.js +++ b/htdocs/js/GraphTool/intervaltools.js @@ -454,7 +454,8 @@ highlightStrokeWidth: 3, highlightStrokeColor: gt.color.pointHighlightDarker, // highlightFillColor is gt.color.pointHighlight if not included. - highlightFillColor: gt.color.pointHighlightDarker + highlightFillColor: gt.color.pointHighlightDarker, + tabindex: gt.isStatic ? -1 : 0 }; } diff --git a/htdocs/js/GraphTool/linetool.js b/htdocs/js/GraphTool/linetool.js index acf6cb03c1..9e97d1a3c3 100644 --- a/htdocs/js/GraphTool/linetool.js +++ b/htdocs/js/GraphTool/linetool.js @@ -188,7 +188,7 @@ const point1 = this.point1; delete this.point1; - point1.setAttribute(gt.definingPointAttributes); + point1.setAttribute(gt.definingPointAttributes()); point1.on('down', () => gt.onPointDown(point1)); point1.on('up', () => gt.onPointUp(point1)); diff --git a/htdocs/js/GraphTool/parabolatool.js b/htdocs/js/GraphTool/parabolatool.js index c1da9bb0da..e0d65cce5c 100644 --- a/htdocs/js/GraphTool/parabolatool.js +++ b/htdocs/js/GraphTool/parabolatool.js @@ -237,7 +237,7 @@ const vertex = this.vertex; delete this.vertex; - vertex.setAttribute(gt.definingPointAttributes); + vertex.setAttribute(gt.definingPointAttributes()); vertex.on('down', () => gt.onPointDown(vertex)); vertex.on('up', () => gt.onPointUp(vertex)); diff --git a/htdocs/js/GraphTool/pointtool.js b/htdocs/js/GraphTool/pointtool.js index 2420b41a24..eea92d4fde 100644 --- a/htdocs/js/GraphTool/pointtool.js +++ b/htdocs/js/GraphTool/pointtool.js @@ -22,7 +22,8 @@ strokeColor: gt.color.curve, fixed: gt.isStatic, highlightStrokeColor: gt.color.underConstruction, - highlightFillColor: gt.color.pointHighlight + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 }) ); diff --git a/htdocs/js/GraphTool/quadratictool.js b/htdocs/js/GraphTool/quadratictool.js index a5a8d94203..bef7eb2893 100644 --- a/htdocs/js/GraphTool/quadratictool.js +++ b/htdocs/js/GraphTool/quadratictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/quadrilateral.js b/htdocs/js/GraphTool/quadrilateral.js index 9ec263626b..b9211b9680 100644 --- a/htdocs/js/GraphTool/quadrilateral.js +++ b/htdocs/js/GraphTool/quadrilateral.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/sinewavetool.js b/htdocs/js/GraphTool/sinewavetool.js index 057559d9f5..a46e597a13 100644 --- a/htdocs/js/GraphTool/sinewavetool.js +++ b/htdocs/js/GraphTool/sinewavetool.js @@ -12,7 +12,7 @@ constructor(shiftPoint, periodPoint, amplitudePoint, solid) { for (const point of [shiftPoint, periodPoint, amplitudePoint]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/triangle.js b/htdocs/js/GraphTool/triangle.js index e68f15aea6..757b5eb665 100644 --- a/htdocs/js/GraphTool/triangle.js +++ b/htdocs/js/GraphTool/triangle.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 8e070015f2..9c40f83d17 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -8,7 +8,7 @@ "license": "GPL-2.0+", "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.11.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", "plotly.js-dist-min": "^3.1.0", @@ -1026,9 +1026,9 @@ "license": "MIT" }, "node_modules/jsxgraph": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", - "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "license": "(MIT OR LGPL-3.0-or-later)", "dependencies": { "jsxgraph": "^1.11.0-beta2" @@ -2622,9 +2622,9 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "jsxgraph": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", - "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "requires": { "jsxgraph": "^1.11.0-beta2" } diff --git a/htdocs/package.json b/htdocs/package.json index f4cfd4ec1d..6de2eed492 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.11.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", "plotly.js-dist-min": "^3.1.0", From fa56ca0734393e1ba408973502de5cfc97c233db Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Fri, 23 Jan 2026 18:42:12 -0600 Subject: [PATCH 098/111] Fix the enter key behavior for MathQuill inputs. The code looks for the `enter_key_submit` submit button in the DOM, and if that is found then it clicks it. However, it doesn't return after that, but continues on the the later code in the method none of which should occur if that button is found. The later code executes the "gateway quiz" fallback which clicks the submit button with the name "previewAnswers". However, there is also a button with that name in homework. The behavior is different in different browsers with this. In Firefox the first button clicked seems to be what is in effect. However, in Google Chrome the second button clicked is in effect. So the desired behavior occurs in Firefox, but not in Google Chrome. What needs to happen is that if the `enter_key_submit` input is found, then that should be clicked, and the method should return. There is no need for the fallback click of the submit button with the id `previewAnswers_id` otherwise. Instead just click the submit button with the name `previewAnswers` which is present in both homework sets and tests. I certainly thing that this should be a hotfix. --- htdocs/js/MathQuill/mqeditor.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 6103311c84..7d2de503fa 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -281,12 +281,17 @@ answerQuill.toolbar?.remove(); delete answerQuill.toolbar; - // For ww2 homework, depends on $pg{options}{enterKey} + // For ww2 homework if the enter_key_submit button is found, then use that. + // This Depends on $pg{options}{enterKey}. const enterKeySubmit = document.getElementById('enter_key_submit'); - if (enterKeySubmit) enterKeySubmit.click(); - else document.getElementById('previewAnswers_id')?.click(); - // For gateway quizzes, always the preview button + if (enterKeySubmit) { + enterKeySubmit.click(); + return; + } + // If the enter_key_submit button is not found (it will not be present in tests), + // then use the preview button. document.querySelector('input[name=previewAnswers]')?.click(); + // For ww3 const previewButtonId = answerQuill.textarea .closest('[name=problemMainForm]') From 5989b4aecc348fa49551afdcf7bb59d3c1c3e4eb Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 22 Jan 2026 14:58:03 -0600 Subject: [PATCH 099/111] Force some things to be displayed in the light color scheme. The image view and knowl dialogs need to be forced into light mode. These dialogs are injected outside of the problem content div in webwork2, and so by default will display in the mode of the page. So the data-bs-theme attribute needs to be set to "light" to prevent that. Many PG images have transparent backgrounds and so will not work with a dark background. Furthermore, the image view and knowl dialog styles themself are not set up to honor dark mode. Note that there is another modal dialog for the `quickMatrixEntry.pl` macro (in the `htdocs/js/QuickMatrixEntry/quickMatrixEntry.js` file), but for now I have left that and allowed it to go into dark mode. That dialog works fine with dark mode already. It could be forced to light mode for consistency though. Although, does anyone actually use this? Also ensure that the accordion buttons for scaffolds are dark so that they have sufficient contrast against the blue or green accordion header background color. Bootstrap wants to use the page text color for this, and if the primary color is light (for instance with the math4-yellow theme), that does not work. --- htdocs/js/ImageView/imageview.js | 6 ++++++ htdocs/js/Knowls/knowl.js | 4 ++++ htdocs/js/Scaffold/scaffold.scss | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/htdocs/js/ImageView/imageview.js b/htdocs/js/ImageView/imageview.js index ef59fbab5c..3e30ae4736 100644 --- a/htdocs/js/ImageView/imageview.js +++ b/htdocs/js/ImageView/imageview.js @@ -31,6 +31,12 @@ modal.setAttribute('aria-label', 'image view dialog'); modal.tabIndex = -1; + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is outside + // of the problem content. At least until PG is updated to honor dark mode. Further discussion on this will + // also be needed at that time since many images have transparent backgrounds that will not work with a dark + // background. + modal.dataset.bsTheme = 'light'; + const dialog = document.createElement('div'); dialog.classList.add('modal-dialog'); diff --git a/htdocs/js/Knowls/knowl.js b/htdocs/js/Knowls/knowl.js index 496b42a7f6..78a933827f 100644 --- a/htdocs/js/Knowls/knowl.js +++ b/htdocs/js/Knowls/knowl.js @@ -24,6 +24,10 @@ knowl.knowlModal.setAttribute('aria-labelledby', `${knowl.knowlModal.id}-title`); knowl.knowlModal.setAttribute('aria-hidden', 'true'); + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is + // outside of the problem content. At least until PG and the help files are updated to honor dark mode. + knowl.knowlModal.dataset.bsTheme = 'light'; + const knowlDialog = document.createElement('div'); knowlDialog.classList.add( 'knowl-dialog', diff --git a/htdocs/js/Scaffold/scaffold.scss b/htdocs/js/Scaffold/scaffold.scss index a4528e2323..5393d530b5 100644 --- a/htdocs/js/Scaffold/scaffold.scss +++ b/htdocs/js/Scaffold/scaffold.scss @@ -15,6 +15,13 @@ min-width: 1.25rem; font-weight: bold; } + + // Force the accordion button to a dark color so that it has sufficient contrast even if the page is in dark + // mode. Bootstrap wants to use the dark mode color for this if the page is in dark mode. + &::after { + --bs-accordion-btn-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23001436' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + } } &.cannotopen > button.accordion-button::after { From 09b1c46888882b83ca3c0715bc241d8788119562 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 18 Dec 2025 20:22:39 -0600 Subject: [PATCH 100/111] Remove everything CAPA. The CAPA problems have now been removed from the Contrib section of the OPL (see https://github.com/openwebwork/webwork-open-problem-library/pull/1277). So this is not needed anymore. Note that the files in the `macros/capa` directory were not used anyway. The webwork2 `specialPGEnvironmentVars` settings related to CAPA problems control where these files are used from, and those still were using the `Contrib/CAPA/macros` directories. We could update those, but there were files there that were not copied (they were .pg files that were actually macros and it was probably believed that they were problem files). So in short CAPA problems just will not work anymore even without this pull request. --- assets/tex/CAPA.tex | 15 --- assets/tex/pg.sty | 1 - conf/pg_config.dist.yml | 7 -- lib/PGloadfiles.pm | 2 +- lib/WeBWorK/PG/IO.pm | 10 +- macros/README.md | 3 +- macros/capa/PG_CAPAmacros.pl | 208 ----------------------------------- macros/capa/StdConst.pg | 36 ------ macros/capa/StdUnits.pg | 79 ------------- t/macros/load_macros.t | 3 +- 10 files changed, 7 insertions(+), 357 deletions(-) delete mode 100644 assets/tex/CAPA.tex delete mode 100644 macros/capa/PG_CAPAmacros.pl delete mode 100644 macros/capa/StdConst.pg delete mode 100644 macros/capa/StdUnits.pg diff --git a/assets/tex/CAPA.tex b/assets/tex/CAPA.tex deleted file mode 100644 index 61d1ffa208..0000000000 --- a/assets/tex/CAPA.tex +++ /dev/null @@ -1,15 +0,0 @@ -% capa tex macros - -\newcommand{\capa}{{\sl C\kern-.10em\raise-.00ex\hbox{\rm A}\kern-.22em% -{\sl P}\kern-.14em\kern-.01em{\rm A}}} - -\newenvironment{choicelist} -{\begin{list}{} - {\setlength{\rightmargin}{0in}\setlength{\leftmargin}{0.13in} - \setlength{\topsep}{0.05in}\setlength{\itemsep}{0.022in} - \setlength{\parsep}{0in}\setlength{\belowdisplayskip}{0.04in} - \setlength{\abovedisplayskip}{0.05in} - \setlength{\abovedisplayshortskip}{-0.04in} - \setlength{\belowdisplayshortskip}{0.04in}} - } -{\end{list}} diff --git a/assets/tex/pg.sty b/assets/tex/pg.sty index be1921fd80..0f895b87ff 100644 --- a/assets/tex/pg.sty +++ b/assets/tex/pg.sty @@ -22,7 +22,6 @@ \fi % PG macro collections -\input{CAPA.tex} \input{PGML.tex} % The macro alternatives for < should be used in math in PG problems to help diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml index 4335415b49..5224755077 100644 --- a/conf/pg_config.dist.yml +++ b/conf/pg_config.dist.yml @@ -42,7 +42,6 @@ directories: - . - $pg_root/macros - $pg_root/macros/answers - - $pg_root/macros/capa - $pg_root/macros/contexts - $pg_root/macros/core - $pg_root/macros/graph @@ -145,12 +144,6 @@ specialPGEnvironmentVars: #Rserve: # host: localhost - # Locations of CAPA resources. (Only necessary if you need to use converted CAPA problems.) - CAPA_Tools: $Contrib_dir/CAPA/macros/CAPA_Tools/ - CAPA_MCTools: $Contrib_dir/Contrib/CAPA/macros/CAPA_MCTools/ - CAPA_GraphicsDirectory: $Contrib_dir/Contrib/CAPA/CAPA_Graphics/ - CAPA_Graphics_URL: $pg_root_url/CAPA_Graphics/ - # Answer evaluatior defaults ansEvalDefaults: functAbsTolDefault: 0.001 diff --git a/lib/PGloadfiles.pm b/lib/PGloadfiles.pm index 65f4932d5b..9af41b7bde 100644 --- a/lib/PGloadfiles.pm +++ b/lib/PGloadfiles.pm @@ -135,7 +135,7 @@ sub loadMacros { } my $macro_file_name = $fileName; $macro_file_name =~ s/\.pl//; # trim off the extension - $macro_file_name =~ s/\.pg//; # sometimes the extension is .pg (e.g. CAPA files) + $macro_file_name =~ s/\.pg//; # sometimes the extension is .pg my $init_subroutine_name = "_${macro_file_name}_init"; $init_subroutine_name =~ s![^a-zA-Z0-9_]!_!g; # remove dangerous chars diff --git a/lib/WeBWorK/PG/IO.pm b/lib/WeBWorK/PG/IO.pm index e1134b1f9a..6e944db2ff 100644 --- a/lib/WeBWorK/PG/IO.pm +++ b/lib/WeBWorK/PG/IO.pm @@ -38,9 +38,8 @@ our $pg_envir; =head2 includePGtext -This is used in processing some of the sample CAPA files and in creating aliases -to redirect calls to duplicate problems so that they go to the original problem -instead. It is called by includePGproblem. +This is used in creating aliases to redirect calls to duplicate problems so that +they go to the original problem instead. It is called by includePGproblem. Usage: C @@ -77,9 +76,8 @@ Read the contents of a pg file. Usage: C Don't use for huge files. The file name will have .pg appended to it if it -doesn't already end in .pg. This is used in importing additional .pg files as -is done in the sample problems translated from CAPA. Returns a reference to a -string containing the contents of the file. +doesn't already end in .pg. This is used in importing additional .pg files. +Returns a reference to a string containing the contents of the file. =cut diff --git a/macros/README.md b/macros/README.md index e9211e5cc0..692d678835 100644 --- a/macros/README.md +++ b/macros/README.md @@ -3,7 +3,6 @@ This directory now has a subdirectory structure for clarification. - **answers** answer and evaluator macros -- **capa** any CAPA related macros - **contexts** context related macros - **core** macros related to core PG functionality - **deprecated** macros that have been labelled deprecated @@ -178,7 +177,7 @@ This directory now has a subdirectory structure for clarification. ## Macros fixed - `math/algebraMacros.pl` (removed all code, already in `customizeLaTeX.pl`) -- `parsers/parserUtils.pl` (removed loadMacros call due to errors. ) +- `parsers/parserUtils.pl` (removed loadMacros call due to errors.) - `contexts/contextFiniteSolutionSets.pl` (fixed warnings) - `contexts/contextLimitedRadicalComplex.pl` (fixed warnings) - `contexts/contextRationalExponent.pl` (fixed warnings) diff --git a/macros/capa/PG_CAPAmacros.pl b/macros/capa/PG_CAPAmacros.pl deleted file mode 100644 index f279fe5d1a..0000000000 --- a/macros/capa/PG_CAPAmacros.pl +++ /dev/null @@ -1,208 +0,0 @@ - -BEGIN { strict->import; } - -sub CAPA_ans { - my $ans = shift; - my %options = @_; - my $answer_evaluator = 0; - - #TEXT("answerCounter =". ++$problemCounter,"$BR"); # checking whether values are reinitialized - - # explicitlty delete options which are meaningless to WeBWorK - if (defined($options{'sig'})) { delete($options{'sig'}); } - if (defined($options{'wgt'})) { delete($options{'wgt'}); } - if (defined($options{'tries'})) { delete($options{'tries'}); } - - # $options{'allow_unknown_options'} = 1; ## if uncommented, this is a fast and possibly dangerous - ## way to prevent warning message about unknown options - - if (defined($options{'reltol'}) or defined($options{'tol'}) or defined($options{'unit'})) { - - if (defined($options{'unit'})) { - #$ans = "$ans $options{'unit'}"; - $answer_evaluator = num_cmp( - $ans, - 'format' => $options{format}, - reltol => (defined($options{reltol})) ? $options{reltol} : undef, - tol => (defined($options{tol})) ? $options{tol} : undef, - unit => $options{unit}, - ); - } else { # numerical compare: - if (defined($options{'reltol'})) { #relative tolerance is given with a percent sign - my $reltol = $options{'reltol'}; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $reltol, 'format' => $format); - } elsif (defined($options{'tol'})) { - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, tol => $options{'tol'}, 'format' => $format); - } else { - my $tol = $ans * $main::numRelPercentTolDefault; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $tol, 'format' => $format); - } - } - } else { - # string comparisons - if (defined($options{'str'}) and $options{'str'} =~ /CS/i) { - $answer_evaluator = str_cmp($ans, filters => ['compress_whitespace']); - } elsif (defined($options{'str'}) and $options{'str'} =~ /MC/i) { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case ignore_order )]); - } else { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case )]); - } - } - - $answer_evaluator; -} - -sub CAPA_import { - my $filePath = shift; - my %save_envir = %main::envir; - my $r_string = read_whole_problem_file($filePath); - - $main::envir{'probFileName'} = $filePath; - includePGtext($r_string); - %main::envir = %save_envir; -} - -sub CAPA_map { - my $seed = shift; - my $array_var_ref = shift; - my $PGrand = new PGrandom($seed); - local $main::array_values_ref = shift; # this must be local since it must be passed to PG_restricted_eval - my $size = @$main::array_values_ref; # get number of values - my @array = 0 .. ($size - 1); - my @slice = (); - while (@slice < $size) { - push(@slice, splice(@array, $PGrand->random(0, $#array), 1)); - } - my $string = ""; - my $var; - my $i = 0; - foreach $var (@$array_var_ref) { - $string .= "\$$var = \$\$main::array_values_ref[ $slice[$i++]]; "; - } - - # it is important that PG-restriced eval can accesss the $array_values_ref - my ($val, $PG_eval_errors, $PG_full_error_report) = PG_restricted_eval($string); - my $out = ''; - $string =~ s/\$/\\\$/g; # protect variables for error message - $out = "Error in MAP subroutine: $PG_eval_errors
        \n" . $string . "
        \n" if $PG_eval_errors; - $out; -} - -sub compare_units { - -} - -sub CAPA_hint { - my $hint = shift; - TEXT(hint(qq{ HINT: $hint $main::BR})); -} - -sub CAPA_explanation { - TEXT(solution(qq{ $main::BR$main::BR EXPLANATION: @_ $main::BR$main::BR})) if solution(@_); -} - -sub pow { - my ($base, $exponent) = @_; - $base**$exponent; -} - -sub CAPA_tex { - my $tex = shift; - # $tex =~ s|/*|\$|g; - #$tex =~ s/\\/\\\\/g; #protect backslashes??? - my $nontex = shift; - &M3($tex, $nontex, $nontex); -} - -sub CAPA_web { - my $text = shift; - my $tex = shift; - my $html = shift; - &M3($tex, "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub CAPA_html { - my $html = shift; - &M3("", "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub var_in_tex { - my ($tex) = $_[0]; - &M3("$tex", "$tex", ""); -} - -sub isNumberQ { # determine whether the input is a number - my $in = shift; - $in =~ /^[\d\.\+\-Ee]+$/; -} - -sub choose { - # my($in)=join(" ",@_); - my ($var) = $_[0]; - $_[$var]; -} - -sub problem { - $main::probNum; -} - -sub pin { - $main::psvn; -} - -sub section { - $main::sectionNumber; -} - -sub name { - $main::studentName; -} - -sub set { - $main::setNumber; -} - -sub question { - $main::probNum; -} - -sub due_date { - $main::formattedDueDate; -} - -sub answer_date { - $main::formattedAnswerDate; -} - -sub open_date { - $main::formattedOpenDate; -} - -sub to_string { - $_[0]; -} - -sub CAPA_EV { - - my $out = &EV3(@_); - $out =~ s/\n\n/\n/g; # hack to prevent introduction of paragraphs in TeX?? - # HACK TO DO THE RIGHT THING WITH DOLLAR SIGNS - $out = ev_substring($out, "/*/*", "/*/*", \&display_math_ev3); - $out = ev_substring($out, "/*", "/*", \&math_ev3); - # TEXT($main::BR, $main::BR, $out,$main::BR,$main::BR); - $out; -} - -# these are very commonly needed files -CAPA_import("${main::CAPA_Tools}StdMacros"); -CAPA_import("${main::CAPA_Tools}StdUnits"); -CAPA_import("${main::CAPA_Tools}StdConst"); -##################### - -$main::prob_val = ""; # gets rid of spurious errors. -$main::prob_try = ""; - -1; diff --git a/macros/capa/StdConst.pg b/macros/capa/StdConst.pg deleted file mode 100644 index f19fdae6e1..0000000000 --- a/macros/capa/StdConst.pg +++ /dev/null @@ -1,36 +0,0 @@ - -## -## macros for Fundamental Constants, in SI units! -## -## Gravitational constant CapG: Units-> m3/kgs2 or Nm2/kg2 -$CapG = 6.67e-11 ; -## -## Speed of light in m/s: -$smallc = 2.99792458e+8 ; -## -## Electron charge in C: -$smalle = 1.602177e-19 ; -## -## Planck constant, smallh: Units ->Js -$smallh = 6.626075e-34 ; -## -## Boltzmann constant, smallk: Units ->J/K -$smallk = 1.38066e-23 ; -## -## standardgrav. accel sea level, smallg: Units ->m/s2 -$smallg = 9.8 ; - -## -## Pi: -$pi = 3.1415926536 ; -## -## From degrees to radians: -$degrad = $pi / 180.0 ; -## -## From radians to degrees: -$raddeg = 180.0 / $pi ; - -1; -################################################# -## Processing time = 2 secs ( 1.96 usr 0.41 sys = 2.38 cpu) -################################################# diff --git a/macros/capa/StdUnits.pg b/macros/capa/StdUnits.pg deleted file mode 100644 index 80238f24d5..0000000000 --- a/macros/capa/StdUnits.pg +++ /dev/null @@ -1,79 +0,0 @@ - -## Define some macros for the units: -## Note: P means per in this notation, except for Pa =Pascal) -## u is the unit indication -## -$m_u = CAPA_tex( "/*m/*" , "m" ) ; # angle degree -$km_u = CAPA_tex( "/*km/*" , "km" ) ; -$cm_u = CAPA_tex( "/*cm/*" , "cm" ) ; -$mm_u = CAPA_tex( "/*mm/*" , "mm" ) ; -$um_u = CAPA_tex( "/*\micro m/*" , "um" ) ; -$nm_u = CAPA_tex( "/*nm/*" , "nm" ) ; -$fm_u = CAPA_tex( "/*fm/*" , "fm" ) ; -$m2_u = CAPA_tex( "/*m^2/*" , "m^2" ) ; -$m3_u = CAPA_tex( "/*m^3/*" , "m^3" ) ; -$cm2_u = CAPA_tex( "/*cm^2/*" , "cm^2" ) ; -$cm3_u = CAPA_tex( "/*cm^3/*" , "cm^3" ) ; -$mi_u = CAPA_tex( "/*mi/*" , "mi" ) ; -$ft_u = CAPA_tex( "/*ft/*" , "ft" ) ; -$in_u = CAPA_tex( "/*in./*" , "in." ) ; -$kg_u = CAPA_tex( "/*kg/*" , "kg" ) ; -$amu_u = CAPA_tex( "/*u/*" , "u" ) ; -$lb_u = CAPA_tex( "/*lb/*" , "lb" ) ; -$s_u = CAPA_tex( "/*s/*" , "s" ) ; -$min_u = CAPA_tex( "/*min/*" , "min" ) ; -$K_u = CAPA_tex( "/*K/*" , "K" ) ; -$degC_u = CAPA_tex( "/*^{\circ}\hspace*{-.015in}C/*" , "degC" ) ; -$degF_unit = CAPA_tex( "/*^{\circ}\hspace*{-.025in}F/*" , "degF" ) ; -$kmol_u = CAPA_tex( "/*kmol/*" , "kmol" ) ; -$Hz_u = CAPA_tex( "/*Hz/*" , "Hz" ) ; -$mPs_u = CAPA_tex( "/*m/s/*" , "m/s" ) ; -$cmPs_u = CAPA_tex( "/*cm/s/*" , "cm/s" ) ; -$kmPh_u = CAPA_tex( "/*km/h/*" , "km/h" ) ; -$miPh_u = CAPA_tex( "/*mi/h/*" , "mi/h" ) ; -$radPs_u = CAPA_tex( "/*rad/s/*" , "rad/s" ) ; -$Ps_u = CAPA_tex( "/*s^{-1}/*" , "s^-1" ) ; -$mPs2_u = CAPA_tex( "/*m/s^2/*" , "m/s2" ) ; -$cmPs2_u = CAPA_tex( "/*cm/s^2/*" , "cm/s2" ) ; -$radPs2_u = CAPA_tex( "/*rad/s^2/*" , "rad/s2" ) ; -$N_unit = CAPA_tex( "/*N/*" , "N" ) ; -$Pa_u = CAPA_tex( "/*Pa/*" , "Pa" ) ; -$atm_u = CAPA_tex( "/*atm/*" , "atm" ) ; -$NPm2_u = CAPA_tex( "/*N/m^2/*" , "N/m2" ) ; -$J_u = CAPA_tex( "/*J/*" , "J" ) ; -$hp_u = CAPA_tex( "/*hp/*" , "hp" ) ; -$eV_u = CAPA_tex( "/*eV/*" , "eV" ) ; -$cal_u = CAPA_tex( "/*cal/*" , "cal" ) ; -$kcal_u = CAPA_tex( "/*kcal/*" , "kcal" ) ; -$W_u = CAPA_tex( "/*W/*" , "W" ) ; -$kW_u = CAPA_tex( "/*kW/*" , "kW" ) ; -$Js_u = CAPA_tex( "/*J s/*" , "J s" ) ; -$C_u = CAPA_tex( "/*C/*" , "C" ) ; -$V_u = CAPA_tex( "/*V/*" , "V" ) ; -$ohm_u = CAPA_tex( "/*\Omega/*" , "ohm" ) ; -$F_u = CAPA_tex( "/*F/*" , "F" ) ; -$Wb_u = CAPA_tex( "/*Wb/*" , "Wb" ) ; -$H_u = CAPA_tex( "/*H/*" , "H" ) ; -$T_u = CAPA_tex( "/*T/*" , "T" ) ; -$N_u = CAPA_tex( "/*N/*" , "N" ) ; -$JPK_u = CAPA_tex( "/*J\over K/*" , "J/K" ) ; -$Nm2Pkg2_u = CAPA_tex( "/*Nm2\over kg^2/*" , "Nm^2/kg^2" ) ; -$kgPm3_u = CAPA_tex( "/*kg / m^3/*" , "kg/m3" ) ; -$gPm3_u = CAPA_tex( "/*g / m^3/*" , "g/m3" ) ; -$gPcm3_u = CAPA_tex( "/*g / cm^3/*" , "g/cm3" ) ; -$NPm_u = CAPA_tex( "/*N/m/*" , "N/m" ) ; -$kgm2_u = CAPA_tex( "/*kg\cdot m^2/*" , "kgm^2" ) ; -$deg_u = CAPA_tex( "/*^{\circ}/*" , "deg" ) ; -$rev_u = CAPA_tex( "/*rev/*" , "rev" ) ; -$rad_u = CAPA_tex( "/*rad/*" , "rad" ) ; -$G_u = CAPA_tex( "/*G/*" , "G" ) ; -$g_u = CAPA_tex( "/*g/*" , "g" ) ; -$h_u = CAPA_tex( "/*h/*" , "h" ) ; -$ton_u = CAPA_tex( "/*ton/*" , "ton" ) ; -$knots_u = CAPA_tex( "/*knots/*" , "knots" ) ; -$kgPs_u = CAPA_tex( "/*kg/s/*" , "kg/s" ) ; - -1; -################################################# -## Processing time = 5 secs ( 4.60 usr 0.37 sys = 4.97 cpu) -################################################# diff --git a/t/macros/load_macros.t b/t/macros/load_macros.t index 59d37ed799..c8ab7e6575 100644 --- a/t/macros/load_macros.t +++ b/t/macros/load_macros.t @@ -33,8 +33,7 @@ my %baseMacros = ( 'PGcommonFunctions.pl' => 1 ); -# PG_CAPAmacros.pl is not really broken, but it depends on files that are in the OPL and not in the PG repository. -my %brokenMacros = ('answerDiscussion.pl' => 1, 'PG_CAPAmacros.pl' => 1); +my %brokenMacros = ('answerDiscussion.pl' => 1); # Find all macro files inside the $ENV{PG_ROOT}/macros directory. my @macro_files; From c411b5d8cbdeae1a907439e9e8a1335b74ee725c Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 3 Feb 2026 21:13:04 -0700 Subject: [PATCH 101/111] Remove partial credit note in avg_problem_grader. This note isn't that useful and can be misleading as it only is about number of answer blanks. --- lib/WeBWorK/PG/Translator.pm | 3 --- macros/core/PGanswermacros.pl | 2 -- 2 files changed, 5 deletions(-) diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index ef66ad5a70..c4ba09bd0c 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -1077,9 +1077,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = eval('main::maketext("You can earn partial credit on this problem.")') - if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; diff --git a/macros/core/PGanswermacros.pl b/macros/core/PGanswermacros.pl index d858e432c1..5ff746d85b 100644 --- a/macros/core/PGanswermacros.pl +++ b/macros/core/PGanswermacros.pl @@ -1638,8 +1638,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = maketext('You can earn partial credit on this problem.') if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; From a1d218fbc8a0758e352aab63723f8f7a30c07fe5 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 4 Dec 2025 11:52:54 -0600 Subject: [PATCH 102/111] Switch applets from storing state via `RECORD_FORM_LABEL` to using the `PERSISTENCE_HASH`. The applet state is not a "kept extra answer" which is what the `RECORD_FORM_LABEL` approach is for. The applet state isn't an answer at all. It is just extra problem data that needs to persist. So exactly what the `PERSISTENCE_HASH` is for. In addition, webwork2 stores the kept extra answers in the `last_answer` column which is type `TEXT` and thus limited to 64KB. The data in the `PERSISTENCE_HASH` is stored in the `problem_data` column which is of type `MEDIUMTEXT` and so can hold up to 16MB of data. The applet state can become larger than 64KB as is evidenced by the issue posted in the forums at https://forums.openwebwork.org/mod/forum/discuss.php?d=8785. There is also a litle clean up of the `insertAll` method of the `AppletObjects.pl` macro. It can take advantage of the `tag` method. --- htdocs/js/AppletSupport/ww_applet_support.js | 1 - lib/Applet.pm | 4 +- macros/graph/AppletObjects.pl | 75 +++++++------------- 3 files changed, 27 insertions(+), 53 deletions(-) diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index 395969bf3f..c9446cc30b 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -95,7 +95,6 @@ class ww_applet { if (typeof newState === 'undefined') newState = 'restart_applet'; const stateInput = ww_applet_list[this.appletName].stateInput; getQE(stateInput).value = newState; - getQE(`previous_${stateInput}`).value = newState; } // STATE: diff --git a/lib/Applet.pm b/lib/Applet.pm index 78f7e9789b..12b42a4678 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -270,8 +270,8 @@ JavaScript "submitAction()" which then asks each of the applets on the page to p submit action which consists of -- If the applet is to be reinitialized (appletName_state contains - restart_applet) then the HTML elements appletName_state and - previous_appletName_state are set to restart_applet to be interpreted by the + restart_applet) then the HTML element appletName_state + is set to restart_applet to be interpreted by the next setState command. -- Otherwise getState() from the applet and save it to the HTML input element appletName_state. diff --git a/macros/graph/AppletObjects.pl b/macros/graph/AppletObjects.pl index e8ad53a762..d85047f76a 100644 --- a/macros/graph/AppletObjects.pl +++ b/macros/graph/AppletObjects.pl @@ -69,54 +69,23 @@ =head2 insertAll # Inserts both header text and object text. sub insertAll { - my $self = shift; - my %options = @_; - - my $includeAnswerBox = (defined($options{includeAnswerBox}) && $options{includeAnswerBox} == 1) ? 1 : 0; - - my $reset_button = $options{reinitialize_button} || 0; - - # Get data to be interpolated into the HTML code defined in this subroutine. - # This consists of the name of the applet and the names of the routines to get and set State - # of the applet (which is done every time the question page is refreshed and to get and set - # Config which is the initial configuration the applet is placed in when the question is - # first viewed. It is also the state which is returned to when the reset button is pressed. + my ($self, %options) = @_; # Prepare html code for storing state. my $appletName = $self->appletName; # The name of the hidden "answer" blank storing state. $self->{stateInput} = "$main::PG->{QUIZ_PREFIX}${appletName}_state"; - my $appletStateName = $self->{stateInput}; - - # Names of routines for this applet - my $getState = $self->getStateAlias; - my $setState = $self->setStateAlias; - my $getConfig = $self->getConfigAlias; - my $setConfig = $self->setConfigAlias; - my $base64_initialState = $self->base64_encode($self->initialState); # This insures that the state will be saved from one invocation to the next. - # FIXME -- with PGcore the persistant data mechanism can be used instead - main::RECORD_FORM_LABEL($appletStateName); - my $answer_value = ''; - - # Implement the sticky answer mechanism for maintaining the applet state when the question - # page is refreshed This is important for guest users for whom no permanent record of - # answers is recorded. - if (defined(${$main::inputs_ref}{$appletStateName}) && ${$main::inputs_ref}{$appletStateName} =~ /\S/) { - $answer_value = ${$main::inputs_ref}{$appletStateName}; - } elsif (defined($main::rh_sticky_answers->{$appletStateName})) { - $answer_value = shift(@{ $main::rh_sticky_answers->{$appletStateName} }); + my $answer_value = ${$main::inputs_ref}{ $self->{stateInput} } // ''; + if ($answer_value !~ /\S/ && defined(my $persistent_data = main::persistent_data($self->{stateInput}))) { + $answer_value = $persistent_data; } $answer_value =~ tr/\\$@`//d; # Make sure student answers cannot be interpolated by e.g. EV3 $answer_value =~ s/\s+/ /g; # Remove excessive whitespace from student answer # Regularize the applet's state which could be in either XML format or in XML format encoded by base64. - # In rare cases it might be simple string. Protect against that by putting xml tags around the state. - # The result: - # $base_64_encoded_answer_value -- a base64 encoded xml string - # $decoded_answer_value -- an xml string - + # In rare cases it might be a simple string. Protect against that by putting xml tags around the state. my $base_64_encoded_answer_value; my $decoded_answer_value; if ($answer_value =~ /<\??xml/i) { @@ -125,10 +94,8 @@ sub insertAll { } else { $decoded_answer_value = $self->base64_decode($answer_value); if ($decoded_answer_value =~ /<\??xml/i) { - # Great, we've decoded the answer to obtain an xml string $base_64_encoded_answer_value = $answer_value; } else { - #WTF?? apparently we don't have XML tags $answer_value = "$answer_value"; $base_64_encoded_answer_value = $self->base64_encode($answer_value); $decoded_answer_value = $answer_value; @@ -136,25 +103,33 @@ sub insertAll { } $base_64_encoded_answer_value =~ s/\r|\n//g; # Get rid of line returns + main::persistent_data($self->{stateInput} => $base_64_encoded_answer_value); + # Construct the reset button string (this is blank if the button is not to be displayed). - my $reset_button_str = $reset_button - ? qq!
        ! + my $reset_button_str = $options{reinitialize_button} + ? main::tag( + 'button', + type => 'button', + class => 'btn btn-primary applet-reset-btn mt-3', + 'data-applet-name' => $appletName, + 'Return this question to its initial state' + ) : ''; - # Combine the state_input_button and the reset button into one string. - my $state_storage_html_code = qq!! - . qq!! - . $reset_button_str; + # Construct the state storage hidden input. + my $state_storage_html_code = main::tag( + 'input', + type => 'hidden', + name => $self->{stateInput}, + id => $self->{stateInput}, + value => $base_64_encoded_answer_value + ); # Construct the answerBox (if it is requested). This is a default input box for interacting # with the applet. It is separate from maintaining state but it often contains similar # data. Additional answer boxes or buttons can be defined but they must be explicitly # connected to the applet with additional JavaScript commands. - my $answerBox_code = $includeAnswerBox - ? $answerBox_code = main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) - : ''; + my $answerBox_code = $options{includeAnswerBox} ? main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) : ''; # Insert header material main::HEADER_TEXT($self->insertHeader()); @@ -162,7 +137,7 @@ sub insertAll { # Return HTML or TeX strings to be included in the body of the page return main::MODES( TeX => ' {\bf ' . $self->{type} . ' applet } ', - HTML => $self->insertObject . $main::BR . $state_storage_html_code . $answerBox_code, + HTML => $self->insertObject . $state_storage_html_code . $reset_button_str . $answerBox_code, PTX => ' applet ' ); } From ef69427ec4035fa31434b8a4515914d857dcb579 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Thu, 4 Dec 2025 18:10:46 -0600 Subject: [PATCH 103/111] Switch ww_applet_support.js from using a submit handler to click handlers on the submit buttons. This makes applet problems work in the PG problem editor of webwork2. The PG problem editor uses click handlers on the submit buttons as well, but calls `preventDefault` on the event, and prevents the form submission from occuring. That prevents the current submit handler set in the ww_applet_support.js code from happening. Thus the answers for applets do not get submitted. By using the click handlers this gets in at the same point that the PG problem editor handlers are, and they still occur. All click handlers are executed even if one of them prevents default behavior. --- htdocs/js/AppletSupport/ww_applet_support.js | 14 ++++++++------ macros/graph/AppletObjects.pl | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index c9446cc30b..e9d599d8c3 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -274,12 +274,14 @@ class ww_applet { if (form.submitHandlerInitialized) return; form.submitHandlerInitialized = true; - // Connect the submit action handler to the form. - form.addEventListener('submit', () => { - for (const appletName in ww_applet_list) { - ww_applet_list[appletName].submitAction(); - } - }); + // Connect the submit action handler to the form submit buttons. + for (const button of form.querySelectorAll('input[type="submit"]')) { + button.addEventListener('click', () => { + for (const appletName in ww_applet_list) { + ww_applet_list[appletName].submitAction(); + } + }); + } }; // Initialize applet support and the applets. diff --git a/macros/graph/AppletObjects.pl b/macros/graph/AppletObjects.pl index d85047f76a..fc2409f4f8 100644 --- a/macros/graph/AppletObjects.pl +++ b/macros/graph/AppletObjects.pl @@ -112,7 +112,7 @@ sub insertAll { type => 'button', class => 'btn btn-primary applet-reset-btn mt-3', 'data-applet-name' => $appletName, - 'Return this question to its initial state' + $main::PG->maketext('Return this question to its initial state') ) : ''; From b3513d96a9ad3bccab424492067f81b1c5dd17c1 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 3 Feb 2026 20:29:31 -0600 Subject: [PATCH 104/111] Fix a possible infinite loop in the MathView initializion code. When a MathView element is initialized the `mvInitialized` data attribute is set on the element, and if that attribute is found for the element in the mutation observer code, then the it does not try to reinitialize MathView for the element. However, the code only checks the primary node in the mutations list, and does not also check for this data attribute on any element contained therein. As a result, those elements can be initialized more than once. Now that the problem grader for webwork2 is in a collapse, the element for instructor comments is not initialy visible in the page and is revealed later by the problem grader javascript. This causes the MathView initializer to trigger repeatedly and crashes the page. To test this set the "Assist with student entry process" to "MathView" for a course. Then open a problem when acting as a student. Make sure that a student has answered so that the comment box is available. With the develop branches for both webwork2 and pg, that will crash the page. With the develop branch of webwork2 and this pull request to pg that won't happen. --- htdocs/js/MathView/mathview.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/htdocs/js/MathView/mathview.js b/htdocs/js/MathView/mathview.js index 92e3acb10a..1162363876 100644 --- a/htdocs/js/MathView/mathview.js +++ b/htdocs/js/MathView/mathview.js @@ -410,7 +410,12 @@ if (node.dataset.mvInitialized === 'true') continue; if (node.classList.contains('codeshard')) new MathViewer(node); - else node.querySelectorAll('.codeshard').forEach((input) => new MathViewer(input)); + else { + for (const input of node.querySelectorAll('.codeshard')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input); + } + } if (node.classList.contains('latexentryfield')) new MathViewer(node, { renderingMode: 'LATEX', @@ -418,14 +423,14 @@ includeDelimiters: true }); else - node.querySelectorAll('.latexentryfield').forEach( - (input) => - new MathViewer(input, { - renderingMode: 'LATEX', - decoratedTextBoxAsInput: false, - includeDelimiters: true - }) - ); + for (const input of node.querySelectorAll('.latexentryfield')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input, { + renderingMode: 'LATEX', + decoratedTextBoxAsInput: false, + includeDelimiters: true + }); + } } } } From d344b51eebdc6a1baf7268548f83b7d3cac899a7 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Thu, 8 Jan 2026 07:17:53 -0700 Subject: [PATCH 105/111] Add PG::Critic policy for the deprecated weightedGrader.pl macros. --- .../PG/ProhibitDeprecatedWeightedGrader.pm | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm new file mode 100644 index 0000000000..77a0755ef2 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => 5; +use constant SAMPLE_PROBLEMS => [ [ 'Weighted Grader' => 'ProblemTechniques/WeightedGrader' ] ]; +use constant WEIGHTED_GRADER_METHODS => { + install_weighted_grader => 1, + WEIGHTED_ANS => 1, + NAMED_WEIGHTED_ANS => 1, + weight_ans => 1, + CREDIT_ANS => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless WEIGHTED_GRADER_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader - The +L functionality is now included in the default + L, and +this macros is no longer needed. + +=head1 DESCRIPTION + +The default L +includes all the functionality of the L, and use of +this macro should be removed. Remove calling the function +C. Instead of calling C or +C, pass C<< weight => n >> to the C method. +In PGML use the following: + + [_]{$answer}{ cmp_options => { weight => n } } + +Instead of calling C pass C<< credit => $answer1 >> or +C<< credit => [$answer1, $answer2, ...] >> to the C method. + +=cut From 7139b1935d4f4df75b2bb6883cbd48c8f7f27f70 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 10 Mar 2026 14:08:23 -0500 Subject: [PATCH 106/111] Fix npm security vulnerabilities. This is the result of running `npm audit fix`. --- htdocs/package-lock.json | 45 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 9c40f83d17..2ffe0a6ea6 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -971,9 +971,9 @@ "license": "MIT" }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -1789,11 +1789,14 @@ } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } }, "node_modules/setimmediate": { "version": "1.0.5", @@ -1912,9 +1915,9 @@ } }, "node_modules/svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, "license": "MIT", "dependencies": { @@ -1924,7 +1927,7 @@ "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.4.1" + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo.js" @@ -2582,9 +2585,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true }, "inherits": { @@ -3086,9 +3089,9 @@ } }, "sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", "dev": true }, "setimmediate": { @@ -3168,9 +3171,9 @@ } }, "svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, "requires": { "commander": "^11.1.0", @@ -3179,7 +3182,7 @@ "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.4.1" + "sax": "^1.5.0" } }, "terser": { From f0c94b3ff33517ec66e676a8473f20bca9126517 Mon Sep 17 00:00:00 2001 From: Jaimos Skriletz Date: Tue, 10 Mar 2026 15:56:56 -0600 Subject: [PATCH 107/111] License clarification. Update the license to match what is in the webwork2 repo. This includes clarifying we use artistic license 1.0, remove the fsf physical address and provide links to both the artistic license 1.0 and gpl 2.0. The old license and new license mention copies of the license should be provided in the "package", but they were not in the pg repo. This adds them to the doc directory to match what is done in webwork2. Remove the old README which was just a copyright statement. --- LICENSE | 20 +-- README | 11 -- doc/Artistic | 131 ++++++++++++++++++++ doc/Copying | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 485 insertions(+), 21 deletions(-) delete mode 100644 README create mode 100644 doc/Artistic create mode 100644 doc/Copying diff --git a/LICENSE b/LICENSE index f079e7c087..2367dd2c5a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ - WeBWorK - Online Homework Delivery System + WeBWorK PG Version 2.* Copyright 2000-2025, The WeBWorK Project + All rights reserved. This program is free software; you can redistribute it and/or modify @@ -12,19 +12,19 @@ Software Foundation; either version 2, or (at your option) any later version, or - b) the "Artistic License" which comes with this package. + b) the "Artistic License" 1.0 which comes with this package. This program 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 either the GNU General Public License or the Artistic License for more details. - You should have received a copy of the Artistic License with this - package, in the file named "Artistic". If not, we'll be glad to provide - one. + You should have received a copy of the Artistic License 1.0 with this + package, in the file named "Artistic" inside the `doc` folder. If not, + you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Artistic + or https://perlfoundation.org/artistic-license-10.html. You should also have received a copy of the GNU General Public License - along with this program in the file named "Copying". If not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307, USA or visit their web page on the internet at - http://www.gnu.org/copyleft/gpl.html. + along with this program in the file named "Copying" inside the `doc` folder. + If not, you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Copying + or https://www.gnu.org/licenses/old-licenses/gpl-2.0.html. diff --git a/README b/README deleted file mode 100644 index 48bfabd55d..0000000000 --- a/README +++ /dev/null @@ -1,11 +0,0 @@ - WeBWorK - Program Generation Language - Version 2.* - Branch: https://github.com/openwebwork - - - https://wiki.openwebwork.org/wiki/Category:Release_Notes - - Copyright 2000-2025, The WeBWorK Project - https://openwebwork.org - All rights reserved. diff --git a/doc/Artistic b/doc/Artistic new file mode 100644 index 0000000000..5f221241e8 --- /dev/null +++ b/doc/Artistic @@ -0,0 +1,131 @@ + + + + + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/doc/Copying b/doc/Copying new file mode 100644 index 0000000000..d6400a2c8a --- /dev/null +++ b/doc/Copying @@ -0,0 +1,344 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software + interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. From 9d2c4400621e9a8bdeaa85eb585f747db7004acf Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 17 Mar 2026 14:07:05 -0500 Subject: [PATCH 108/111] Update the `pg.pot` file. This is the result of executing the `bin/update-localization-files` script. --- lib/WeBWorK/PG/Localize/pg.pot | 114 ++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/lib/WeBWorK/PG/Localize/pg.pot b/lib/WeBWorK/PG/Localize/pg.pot index f7d4fdab2b..42d57b429e 100644 --- a/lib/WeBWorK/PG/Localize/pg.pot +++ b/lib/WeBWorK/PG/Localize/pg.pot @@ -15,60 +15,64 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1456 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1428 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1452 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1225 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1118 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 +#: /opt/webwork/pg/macros/PG.pl:1441 msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/PG.pl:1415 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1406 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1260 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1061 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1241 /opt/webwork/pg/macros/PG.pl:1317 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1110 msgid "Correct" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1323 msgid "Correct Answer" msgstr "" @@ -96,11 +100,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -108,7 +112,7 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1114 msgid "Incorrect" msgstr "" @@ -120,7 +124,7 @@ msgstr "" msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1286 msgid "Preview of Your Answer" msgstr "" @@ -128,7 +132,11 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1329 msgid "Reveal" msgstr "" @@ -136,11 +144,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -148,27 +156,31 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1376 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1394 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1365 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1375 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1221 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1385 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" @@ -176,11 +188,31 @@ msgstr "" msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1244 +msgid "This graph does not have two vertices of odd degree and all other vertices of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1313 /opt/webwork/pg/macros/math/SimpleGraph.pl:1320 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1218 /opt/webwork/pg/macros/math/SimpleGraph.pl:1231 /opt/webwork/pg/macros/math/SimpleGraph.pl:1314 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1268 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1280 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3100 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -196,14 +228,10 @@ msgstr "" msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 +#: /opt/webwork/pg/macros/PG.pl:1281 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "" - #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" @@ -212,7 +240,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3093 msgid "Your browser does not support the video tag." msgstr "" @@ -222,11 +250,11 @@ msgstr "" #. ($name) #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:634 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2978 msgid "end image description" msgstr "" @@ -237,43 +265,43 @@ msgstr "" msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2976 msgid "image description" msgstr "" #. (1) #. ($count) #. ($i + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" #. ($2 + 1) #. ($radioIndex) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:636 msgid "part %1 " msgstr "" #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:629 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:642 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:637 msgid "subpart %1 " msgstr "" From 995ba8e687cfc959aa69c23c9eb954f5ab153ff8 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 24 Mar 2026 11:42:03 -0500 Subject: [PATCH 109/111] Fix `drawZero` for JSXGraph output of the plots macro. Currently if the y axis location is 'center', 'left' (or 'box'), or 'right', then the determination that zero is drawn is made by checking that the y axis min or max value. That is incorrect. It should be checking the x axis min or max value. The point is that if the y axis is passing through x = 0, then zero should not be drawn, and otherwise it should be. Similarly, the y axis `drawZero` determination is fixed. You can test this with the following problem code: ``` DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); $graph = Plot( xmin => -2, xmax => 10, xtick_distance => 1, xminor => 0, xlocation => 'bottom', ymin => 0, ymax => 10, ytick_distance => 1, yminor => 0, ylocation => 'left' ); BEGIN_PGML [!graph!]{$graph}{500} END_PGML ENDDOCUMENT(); ``` With that example and the develop branch the zero on the x-axis is incorrectly not drawn in JSXGraph output. With this branch it is drawn as it should be. Note that in TikZ output the zero is drawn with this example. So this makes the two output formats consistent. --- htdocs/js/Plots/plots.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js index 7e1c0416a8..e53dc99fc9 100644 --- a/htdocs/js/Plots/plots.js +++ b/htdocs/js/Plots/plots.js @@ -347,8 +347,8 @@ const PGplots = { !options.yAxis?.visible || (options.yAxis.location === 'center' && (options.yAxis.position ?? 0) != 0) || ((options.yAxis.location === 'left' || options.yAxis.location === 'box') && - (options.yAxis.min ?? -5) != 0) || - (options.yAxis.location === 'right' && (options.yAxis.max ?? 5) != 0) + (options.xAxis.min ?? -5) != 0) || + (options.yAxis.location === 'right' && (options.xAxis.max ?? 5) != 0) ? true : false, insertTicks: false, @@ -446,8 +446,8 @@ const PGplots = { !options.xAxis?.visible || (options.xAxis.location === 'middle' && (options.xAxis.position ?? 0) != 0) || ((options.xAxis.location === 'bottom' || options.xAxis.location === 'box') && - (options.xAxis.min ?? -5) != 0) || - (options.xAxis.location === 'top' && (options.xAxis.max ?? 5) != 0) + (options.yAxis.min ?? -5) != 0) || + (options.xAxis.location === 'top' && (options.yAxis.max ?? 5) != 0) ? true : false, insertTicks: false, From 110d9dd074ef8636489896aa8532d129b92b6039 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 23 Mar 2026 07:16:22 -0500 Subject: [PATCH 110/111] Remove the internal debug messages. The only place this is used is for error messages in `lib/PGresponsegroup.pm`. That is now reworked to use the `PGcore::warning_message` method instead. The `internal_debug_messages` is just not needed anymore in addition to the `warning_message` and `debug_message` methods. Furthermore, it is inappropriately named. It has "debug" in the name, but is used for error messages, not debugging messages. --- lib/PGUtil.pm | 2 +- lib/PGanswergroup.pm | 22 +++++++++++----------- lib/PGcore.pm | 28 +--------------------------- lib/PGresponsegroup.pm | 23 +++++++++++++++-------- macros/PG.pl | 15 +++++++-------- 5 files changed, 35 insertions(+), 55 deletions(-) diff --git a/lib/PGUtil.pm b/lib/PGUtil.pm index c6a7c08cff..9338c29002 100644 --- a/lib/PGUtil.pm +++ b/lib/PGUtil.pm @@ -89,7 +89,7 @@ sub pretty_print_html { # provides html output -- NOT a method if (!$ref) { return $r_input =~ s/' + return '
        ' . ($ref eq 'HASH' ? '' : '
        warning_message( + qq{PGresponsegroup::append_response error: There is already an answer labeled "$response_label".}); } } else { - $self->internal_debug_message('PGresponsegroup::append_response error: undefined or empty response label'); + $self->warning_message('PGresponsegroup::append_response error: Undefined or empty response label.'); } return; } @@ -82,13 +83,14 @@ sub replace_response { sub extend_response { my ($self, $response_label, $new_value_key, $selected) = @_; - if (defined $self->{responses}{$response_label}) { + if (defined $response_label && defined $self->{responses}{$response_label}) { my $response_value = $self->{responses}{$response_label}; $response_value //= []; if (ref($response_value) !~ /^(HASH|ARRAY)$/) { - $self->internal_debug_message("PGresponsegroup::extend_response: error in extending response ", - ref($response_value), $response_value); + $self->warning_message('PGresponsegroup::extend_response error: Invalid value type "' + . (ref($response_value) || 'scalar') + . qq{" for $response_label.}); $response_value = [ [ $response_value => $selected ] ]; } @@ -99,7 +101,12 @@ sub extend_response { $self->{responses}{$response_label} = $response_value; return $response_value; } else { - $self->internal_debug_message("PGresponsegroup::extend_response: response label |$response_label| not defined"); + if (defined $response_label) { + $self->warning_message( + qq{PGresponsegroup::extend_response error: Response label "$response_label" not defined.}); + } else { + $self->warning_message('PGresponsegroup::extend_response error: Response label not provided.'); + } return; } } diff --git a/macros/PG.pl b/macros/PG.pl index aea6e6a3ba..d363e39487 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -111,14 +111,13 @@ =head2 DOCUMENT sub DOCUMENT { # get environment - $rh_envir = \%envir; #KLUDGE FIXME - # warn "rh_envir is ",ref($rh_envir); - $PG = new PGcore( - $rh_envir, # can add key/value options to modify + $rh_envir = \%envir; #KLUDGE FIXME + + $PG = new PGcore( + $rh_envir, # can add key/value options to modify ); - $PG->clear_internal_debug_messages; - # initialize main:: variables + # initialize main:: variables $ANSWER_PREFIX = $PG->{ANSWER_PREFIX}; $QUIZ_PREFIX = $PG->{QUIZ_PREFIX}; $showPartialCorrectAnswers = $PG->{flags}->{showPartialCorrectAnswers}; @@ -620,7 +619,7 @@ sub NEW_ANS_ARRAY_NAME_EXTENSION { } my $ans_label = $PG->new_ans_name(); my $element_ans_label = $PG->new_array_element_label($ans_label, $row_num, $col_num, vec_num => $vecnum); - my $response = new PGresponsegroup($ans_label, $element_ans_label, undef); + my $response = PGresponsegroup->new($ans_label, $element_ans_label, undef); $PG->extend_ans_group($ans_label, $response); return $element_ans_label; } @@ -632,7 +631,7 @@ sub CLEAR_RESPONSES { if (ref($responsegroup)) { $responsegroup->clear; } else { - $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = new PGresponsegroup($label); + $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = PGresponsegroup->new($ans_label); } } return; From 56451084e55334fa449a22b3ba7be441ffb83a53 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 25 Mar 2026 17:21:19 -0500 Subject: [PATCH 111/111] Fix an npm dependency security vulnerability. Yes, this is the same one the dependabot pointed out for the main branch. --- htdocs/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 2ffe0a6ea6..b47c393e54 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -1184,9 +1184,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "optional": true, @@ -2737,9 +2737,9 @@ "dev": true }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "optional": true },