diff --git a/MANIFEST b/MANIFEST index da76379dc4..b7f89f599c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -987,6 +987,9 @@ t/complex/aastex631_deluxetable.xml t/complex/aliceblog.pdf t/complex/aliceblog.tex t/complex/aliceblog.xml +t/complex/candidates.pdf +t/complex/candidates.tex +t/complex/candidates.xml t/complex/cleveref_minimal.pdf t/complex/cleveref_minimal.tex t/complex/cleveref_minimal.xml @@ -1016,6 +1019,8 @@ t/complex/si.pdf t/complex/si.tex t/complex/si.xml t/complex/sunset.jpg +t/complex/sunset,encoded.gif +t/complex/sunset,encoded.jpg t/complex/tcilatex_minimal.pdf t/complex/tcilatex_minimal.tex t/complex/tcilatex_minimal.xml diff --git a/lib/LaTeXML/Common/Model/DTD.pm b/lib/LaTeXML/Common/Model/DTD.pm index 5a80e32b60..bf05d1d45d 100644 --- a/lib/LaTeXML/Common/Model/DTD.pm +++ b/lib/LaTeXML/Common/Model/DTD.pm @@ -16,7 +16,6 @@ use LaTeXML::Util::Pathname; use LaTeXML::Global; use LaTeXML::Common::Error; use LaTeXML::Common::XML; -use URI::file; #********************************************************************** # NOTE: Arglist is DTD specific. @@ -118,7 +117,7 @@ sub readDTD { paths => $STATE->lookupValue('SEARCHPATHS'), installation_subdir => 'resources/DTD'); if ($dtdfile) { - $dtd = XML::LibXML::Dtd->new($$self{public_id}, URI::file->new($dtdfile)); + $dtd = XML::LibXML::Dtd->new($$self{public_id}, pathname_to_file_url($dtdfile)); $how = " from $dtdfile "; Error('misdefined', $$self{system_id}, undef, "Parsing of DTD \"$$self{public_id}\" \"$$self{system_id}\" failed") diff --git a/lib/LaTeXML/Core.pm b/lib/LaTeXML/Core.pm index 160c5d67f0..ff53418c33 100644 --- a/lib/LaTeXML/Core.pm +++ b/lib/LaTeXML/Core.pm @@ -263,7 +263,7 @@ sub convertDocument { my @dedup = (); while (my $check = shift(@copy)) { unshift(@dedup, $check) if !(grep { $_ eq $check } @dedup); } - $document->insertPI('latexml', searchpaths => join(',', @dedup)); } } + $document->insertPI('latexml', searchpaths => pathname_to_urls(@dedup)); } } foreach my $preload_by_reference (@{ $$self{preload} }) { my $preload = $preload_by_reference; # copy preload value, as we want to preserve the hash as-is, for (potential) future daemon calls next if $preload =~ /\.pool$/; diff --git a/lib/LaTeXML/Engine/TeX_FileIO.pool.ltxml b/lib/LaTeXML/Engine/TeX_FileIO.pool.ltxml index 3c0cca1ae9..28b81bb08f 100644 --- a/lib/LaTeXML/Engine/TeX_FileIO.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_FileIO.pool.ltxml @@ -243,7 +243,7 @@ DefConstructor('\lx@special@graphics OptionalKeyVals:SpecialPS Semiverbatim', my $bottom = -$voffset; $options .= "," if $options; $options .= "trim=$left $bottom 0 0,clip=true"; } } - (options => $options, path => $path, candidates => join(',', @candidates)); }, + (options => $options, path => pathname_to_url($path), candidates => pathname_to_urls(@candidates)); }, mode => 'restricted_horizontal'); # Since these ultimately generate external resources, it can be useful to have a handle on them. Tag('ltx:graphics', afterOpen => sub { GenerateID(@_, 'g'); }); diff --git a/lib/LaTeXML/Package/epsf.sty.ltxml b/lib/LaTeXML/Package/epsf.sty.ltxml index dc931a43b5..d596c4e285 100644 --- a/lib/LaTeXML/Package/epsf.sty.ltxml +++ b/lib/LaTeXML/Package/epsf.sty.ltxml @@ -57,8 +57,8 @@ DefConstructor('\epsfbox[] Semiverbatim', $options .= ($options ? ',' : '') . 'width=' . ToString($w); } if ($h->valueOf > 0) { $options .= ($options ? ',' : '') . 'height=' . ToString($h); } - (graphic => $file, - candidates => join(',', @candidates), + (graphic => pathname_to_url($file), + candidates => pathname_to_urls(@candidates), options => $options); }); Let('\epsfgetlitbb', '\epsfbox'); diff --git a/lib/LaTeXML/Package/epsfig.sty.ltxml b/lib/LaTeXML/Package/epsfig.sty.ltxml index 7fd9b5e4df..0035b33e9e 100644 --- a/lib/LaTeXML/Package/epsfig.sty.ltxml +++ b/lib/LaTeXML/Package/epsfig.sty.ltxml @@ -47,9 +47,9 @@ DefConstructor('\psfig RequiredKeyVals:epsGin', if (my $base = LookupValue('SOURCEDIRECTORY')) { @candidates = map { pathname_relative($_, $base) } @candidates; } my $options = ToString($kv); - (graphic => $file, - candidates => join(',', @candidates), - options => $options); }); + (graphic => pathname_to_url($file), + candidates => pathname_to_urls(@candidates), + options => $options); }); Let('\epsfig', '\psfig'); diff --git a/lib/LaTeXML/Package/graphics.sty.ltxml b/lib/LaTeXML/Package/graphics.sty.ltxml index f3878e1ea1..ce330c5309 100644 --- a/lib/LaTeXML/Package/graphics.sty.ltxml +++ b/lib/LaTeXML/Package/graphics.sty.ltxml @@ -248,7 +248,7 @@ DefConstructor('\reflectbox {}', DefConstructor('\graphicspath DirectoryList', sub { my ($document, $paths, %props) = @_; foreach my $path (@{ $props{paths} }) { - $document->insertPI('latexml', graphicspath => $path); } }, + $document->insertPI('latexml', graphicspath => pathname_to_url($path)); } }, properties => sub { my ($stomach, $paths) = @_; my @paths = (); @@ -297,8 +297,8 @@ DefConstructor('\@includegraphics OptionalMatch:* [][] Semiverbatim', my ($stomach, $starred, $op1, $op2, $graphic) = @_; my $options = graphicS_options($starred, $op1, $op2); my ($path, @candidates) = image_candidates(ToString($graphic)); - (graphic => $path, - candidates => join(',', @candidates), + (graphic => pathname_to_url($path), + candidates => pathname_to_urls(@candidates), options => $options); }, alias => '\includegraphics'); diff --git a/lib/LaTeXML/Package/graphicx.sty.ltxml b/lib/LaTeXML/Package/graphicx.sty.ltxml index ce17fe8923..0dc6559107 100644 --- a/lib/LaTeXML/Package/graphicx.sty.ltxml +++ b/lib/LaTeXML/Package/graphicx.sty.ltxml @@ -65,8 +65,8 @@ DefConstructor('\@includegraphicx OptionalMatch:* OptionalKeyVals:Gin Semiverbat my $options = graphicX_options($starred, $kv); my ($path, @candidates) = image_candidates(ToString($graphic)); my $alt = $kv && $kv->getValue('alt'); - (path => $path, - candidates => join(',', @candidates), + (path => pathname_to_url($path), + candidates => pathname_to_urls(@candidates), (defined $alt ? (alt => $alt) : ()), options => $options); }, mode => 'restricted_horizontal', enterHorizontal => 1); diff --git a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml index 6d44b4072e..13727a1da6 100644 --- a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml +++ b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml @@ -617,8 +617,8 @@ DefConstructor('\lxSVG@includegraphics{}{} Semiverbatim', paths => LookupValue('GRAPHICSPATHS')); if (my $base = LookupValue('SOURCEDIRECTORY')) { @candidates = map { pathname_relative($_, $base) } @candidates; } - (graphic => $graphic, - candidates => join(',', @candidates), + (graphic => pathname_to_url($graphic), + candidates => pathname_to_urls(@candidates), options => $options); }, alias => '\includegraphics'); diff --git a/lib/LaTeXML/Package/psfrag.sty.ltxml b/lib/LaTeXML/Package/psfrag.sty.ltxml index f09028ac85..6dcde84dec 100644 --- a/lib/LaTeXML/Package/psfrag.sty.ltxml +++ b/lib/LaTeXML/Package/psfrag.sty.ltxml @@ -87,8 +87,8 @@ DefConstructor('\lx@psfrag@includegraphics OptionalMatch:* [][] Semiverbatim', my $options = graphicS_options($starred, $op1, $op2); my ($path, @candidates) = image_candidates(ToString($graphic)); my $ifpicture = psfrag_requirepicture(@candidates); - (graphic => $path, - candidates => join(',', @candidates), + (path => pathname_to_url($path), + candidates => pathname_to_urls(@candidates), options => $options, pic => $ifpicture); }, afterDigest => sub { @@ -110,8 +110,8 @@ DefConstructor('\lx@psfrag@includegraphicx OptionalMatch:* OptionalKeyVals:Gin S my $options = graphicX_options($starred, $kv); my ($path, @candidates) = image_candidates(ToString($graphic)); my $ifpicture = psfrag_requirepicture(@candidates); - (path => $path, - candidates => join(',', @candidates), + (path => pathname_to_url($path), + candidates => pathname_to_urls(@candidates), options => $options, pic => $ifpicture); }, afterDigest => sub { @@ -133,8 +133,8 @@ DefConstructor('\lx@psfrag@epsfbox[] Semiverbatim', my $options = ($clip ? ($bb ? "viewport=$bb, clip" : "clip") : ''); my ($path, @candidates) = image_candidates(ToString($graphic)); my $ifpicture = psfrag_requirepicture(@candidates); - (graphic => $path, - candidates => join(',', @candidates), + (graphic => pathname_to_url($path), + candidates => pathname_to_urls(@candidates), options => $options, pic => $ifpicture); }, afterDigest => sub { diff --git a/lib/LaTeXML/Post.pm b/lib/LaTeXML/Post.pm index 8859e5d726..e90007f5bc 100644 --- a/lib/LaTeXML/Post.pm +++ b/lib/LaTeXML/Post.pm @@ -664,7 +664,6 @@ use DB_File; use Unicode::Normalize; use LaTeXML::Post; # to import error handling... use LaTeXML::Common::Error; -use URI::file; use base qw(LaTeXML::Common::Object); our $NSURI = "http://dlmf.nist.gov/LaTeXML"; our $XPATH = LaTeXML::Common::XML::XPath->new(ltx => $NSURI); @@ -821,7 +820,7 @@ sub setDocument_internal { @paths = @{ $$self{searchpaths} } if $$self{searchpaths}; foreach my $pi (@{ $$self{processingInstructions} }) { if ($pi =~ /^\s*searchpaths\s*=\s*([\"\'])(.*?)\1\s*$/) { - push(@paths, split(',', $2)); } } + push(@paths, pathname_from_urls($2)); } } ### No, this ultimately can be the xml source, which may be the destination; ### adding this gets the wrong graphics (already processed!) ### push(@paths, pathname_absolute($$self{sourceDirectory})) if $$self{sourceDirectory}; @@ -857,7 +856,8 @@ sub setDocument_internal { else { Fatal('unexpected', $root, undef, "Dont know how to use '$root' as document element"); } # set URI to destination - $$self{document}->setURI(URI::file->new($self->getDestination)); + my $url = pathname_to_file_url($self->getDestination); + $$self{document}->setURI($url) if defined $url; return $self; } our @MonthNames = (qw( January February March April May June diff --git a/lib/LaTeXML/Post/CrossRef.pm b/lib/LaTeXML/Post/CrossRef.pm index 72cbc3a0ce..291c3d2134 100644 --- a/lib/LaTeXML/Post/CrossRef.pm +++ b/lib/LaTeXML/Post/CrossRef.pm @@ -650,8 +650,9 @@ sub generateURL { if ($location = $object->getValue('location')) { my $doclocation = $doc->siteRelativeDestination; my $pathdir = pathname_directory($doclocation); - my $url = pathname_relative(($location =~ m|^/| ? $location : '/' . $location), + my $relpath = pathname_relative(($location =~ m|^/| ? $location : '/' . $location), ($pathdir =~ m|^/| ? $pathdir : '/' . $pathdir)); + my $url = pathname_to_url($relpath); my $extension = $$self{extension} || 'xml'; my $urlstyle = $$self{urlstyle} || 'file'; if ($urlstyle eq 'server') { @@ -667,7 +668,7 @@ sub generateURL { $url .= '#' . $fragid; } elsif ($location eq $doclocation) { $url = ''; } - return pathname_to_url($url); } + return $url; } else { $self->note_missing('warn', 'File location for ID', $id); } } else { diff --git a/lib/LaTeXML/Post/Graphics.pm b/lib/LaTeXML/Post/Graphics.pm index ff9aa82d0e..3f1424eff7 100644 --- a/lib/LaTeXML/Post/Graphics.pm +++ b/lib/LaTeXML/Post/Graphics.pm @@ -53,7 +53,7 @@ sub new { $$self{trivial_scaling} = $options{trivial_scaling} || 1; $$self{graphics_types} = $options{graphics_types} || [qw(svg png gif jpg jpeg - eps ps postscript ai pdf)]; + eps ps postscript ai pdf)]; $$self{type_properties} = $options{type_properties} || { ai => { destination_type => 'png', @@ -123,7 +123,7 @@ sub findGraphicsPaths { my @paths = (); foreach my $pi ($doc->findnodes('.//processing-instruction("latexml")')) { if ($pi->textContent =~ /^\s*graphicspath\s*=\s*([\"\'])(.*?)\1\s*$/) { - push(@paths, $2); } } + push(@paths, pathname_from_url($2)); } } return @paths; } sub getGraphicsSourceTypes { @@ -133,9 +133,9 @@ sub getGraphicsSourceTypes { # Return the pathname to an appropriate image. sub findGraphicFile { my ($self, $doc, $node) = @_; - if (my $source = $node->getAttribute('graphic')) { + if (my $source = pathname_from_url($node->getAttribute('graphic'))) { # if we already have a usable candidate, save ourselves the work - my @paths = grep { -e $_ } split(',', $node->getAttribute('candidates') || ''); + my @paths = grep { -e $_ } pathname_from_urls($node->getAttribute('candidates') || ''); if (!scalar(@paths)) { # Find all acceptable image files, in order of search paths my ($dir, $name, $reqtype) = pathname_split($source); diff --git a/lib/LaTeXML/Post/Manifest/Epub.pm b/lib/LaTeXML/Post/Manifest/Epub.pm index 42f7d3245a..3ab876a778 100644 --- a/lib/LaTeXML/Post/Manifest/Epub.pm +++ b/lib/LaTeXML/Post/Manifest/Epub.pm @@ -13,7 +13,7 @@ package LaTeXML::Post::Manifest::Epub; use strict; use warnings; use File::Find qw(find); -use URI::file; +use LaTeXML::Util::Pathname; our $uuid_tiny_installed; @@ -27,7 +27,7 @@ BEGIN { use base qw(LaTeXML::Post::Manifest); use LaTeXML::Util::Pathname; use File::Spec::Functions qw(catdir); -use POSIX qw(strftime); +use POSIX qw(strftime); use LaTeXML::Post; # for error handling! our $container_content = <<'EOL'; @@ -171,7 +171,7 @@ sub process { # Add to manifest my $manifest = $$self{opf_manifest}; my $item = $manifest->addNewChild(undef, 'item'); - my $item_url = URI::file->new($relative_destination); + my $item_url = pathname_to_url($relative_destination); my $item_id = url_id($item_url); $item->setAttribute('id', $item_id); $item->setAttribute('href', $item_url); @@ -215,7 +215,7 @@ sub finalize { $file_type = 'application/octet-stream'; } my $file_item = $manifest->addNewChild(undef, 'item'); - my $file_url = URI::file->new($file); + my $file_url = pathname_to_url($file); $file_item->setAttribute('id', url_id($file_url)); $file_item->setAttribute('href', $file_url); $file_item->setAttribute('media-type', $file_type); } diff --git a/lib/LaTeXML/Util/Image.pm b/lib/LaTeXML/Util/Image.pm index 4e77c40348..b110866842 100644 --- a/lib/LaTeXML/Util/Image.pm +++ b/lib/LaTeXML/Util/Image.pm @@ -252,7 +252,7 @@ sub image_graphicx_sizer { my ($whatsit) = @_; if (my $candidates = $whatsit->getProperty('candidates')) { my $dpi = ($STATE && $STATE->lookupValue('DPI')) || $DPI; - foreach my $source (split(/,/, $candidates)) { + foreach my $source (pathname_from_urls($candidates)) { if (!pathname_is_absolute($source)) { if (my $base = $STATE->lookupValue('SOURCEDIRECTORY')) { $source = pathname_concat($base, $source); } } diff --git a/lib/LaTeXML/Util/Pathname.pm b/lib/LaTeXML/Util/Pathname.pm index 4d857546e7..9664b7e2b2 100644 --- a/lib/LaTeXML/Util/Pathname.pm +++ b/lib/LaTeXML/Util/Pathname.pm @@ -31,6 +31,7 @@ use warnings; use File::Spec; use File::Copy; use File::Which; +use URI::file; use Cwd; use base qw(Exporter); our @EXPORT = qw( &pathname_find &pathname_findall &pathname_kpsewhich @@ -39,6 +40,7 @@ our @EXPORT = qw( &pathname_find &pathname_findall &pathname_kpsewhich &pathname_timestamp &pathname_concat &pathname_relative &pathname_absolute &pathname_to_url + &pathname_to_urls &pathname_from_url &pathname_from_urls &pathname_to_file_url &pathname_is_absolute &pathname_is_contained &pathname_is_url &pathname_is_literaldata &pathname_is_raw @@ -212,13 +214,34 @@ sub pathname_absolute { ? File::Spec->rel2abs($pathname, ($base ? pathname_canonical($base) : pathname_cwd())) : $pathname); } +# conversions to/from file URLs + +# remove file:// part for backwards compatibility sub pathname_to_url { - my ($pathname) = @_; - return unless defined $pathname; - my $relative_pathname = pathname_relative($pathname); - if ($SEP ne '/') { - $relative_pathname = join('/', split(/\Q$SEP\E/, $relative_pathname)); } - return $relative_pathname; } + return unless defined $_[0]; + my $url = URI::file->new(pathname_canonical($_[0]))->path; + $url =~ s/,/%2C/g; + return $url; } + +sub pathname_to_urls { + return join(',', map { pathname_to_url($_) } @_); } + +sub pathname_from_url { + return unless defined $_[0]; + return URI->new($_[0], 'file')->file; } + +sub pathname_from_urls { + return unless defined $_[0]; + my @urls = split(/,/, $_[0]); + my @nonempty = grep { $_ } @urls; + return map { pathname_from_url($_) } @nonempty; } + +# complete file:// URL +sub pathname_to_file_url { + return unless defined $_[0]; + my $url = URI::file->new(pathname_canonical($_[0]))->as_string; + $url =~ s/,/%2C/g; + return $url; } #====================================================================== # Actual file system operations. @@ -377,6 +400,7 @@ sub candidate_pathnames { opendir(my $dir_handle, $dir) or next; my @dir_files = readdir($dir_handle); closedir($dir_handle); + my @dir_paths; for my $local_file (@dir_files) { for my $regex_pair (@regexes) { my ($i_regex, $regex) = @$regex_pair; @@ -386,7 +410,8 @@ sub candidate_pathnames { if ($local_file =~ m/$regex/) { # if we are only interested in the first match, return it: return ($full_file) if $options{findfirst}; - push(@paths, $full_file); } } } } } + push(@dir_paths, $full_file); } } } } + push(@paths, sort @dir_paths); } # Fallback: if no strict matches were found, return any existing case-insensitive matches # Defer the -f check until we are sure we need it, to keep the usual cases fast. return @paths ? @paths : @nocase_paths; } @@ -553,10 +578,26 @@ Returns the absolute pathname resulting from interpretting C<$path> relative to the directory C<$base>. If C<$path> is already absolute, it is returned unchanged. -=item C<< $relative_url = pathname_to_url($path); >> +=item C<< $url = pathname_to_file_url($path); >> + +Creates a file URL for a given pathname. + +=item C<< $url = pathname_to_url($path); >> + +Creates a file URL for a given pathname without the file:// scheme. + +=item C<< $url = pathname_to_urls(@paths); >> + +Create a comma separated list of file URLs for a given list of pathnames. + +=item C<< $path = pathname_from_url($url); >> + +Create a pathname, with local OS path separators, from a file URL. + +=item C<< @paths = pathname_from_urls($urls); >> -Creates a local, relative URL for a given pathname, -also ensuring proper path separators on non-Unix systems. +Create a list of pathnames, with local OS path separator, from a +comma separated list of file URLs. =back diff --git a/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rnc b/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rnc index a401830ba0..79361165da 100644 --- a/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rnc +++ b/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rnc @@ -82,12 +82,12 @@ graphics_attributes = ## the path to the graphics file. This is the (often minimally specified) path ## to a graphics file omitting the type extension. Once resolved to a specific ## image file, the \attr{imagesrc} (from Imageable.attributes) is used. - attribute graphic { text }?, + attribute graphic { xsd:anyURI }?, ## a comma separated list of candidate graphics files that could be used to ## for \attr{graphic}. A post-processor or application may choose from these, ## or may make its own selection or synthesis to implement the graphic for a given target. - attribute candidates { text }?, + attribute candidates { xsd:anyURI, (",", xsd:anyURI)* }?, ## an encoding of the scaling and positioning options ## to be used in processing the graphic. diff --git a/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rng b/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rng index c303912de9..66915e9572 100644 --- a/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rng +++ b/lib/LaTeXML/resources/RelaxNG/LaTeXML-misc.rng @@ -12,7 +12,7 @@ | http://dlmf.nist.gov/LaTeXML/ (o o) | \=========================================================ooo==U==ooo=/ --> - + Miscellaneous (Misc) elements are (typically) visible elements which don't have clear inline or block character; @@ -103,6 +103,7 @@ typesets its contents as a block. the path to the graphics file. This is the (often minimally specified) path to a graphics file omitting the type extension. Once resolved to a specific image file, the \attr{imagesrc} (from Imageable.attributes) is used. + @@ -110,6 +111,13 @@ image file, the \attr{imagesrc} (from Imageable.attributes) is used.a comma separated list of candidate graphics files that could be used to for \attr{graphic}. A post-processor or application may choose from these, or may make its own selection or synthesis to implement the graphic for a given target. + + + + , + + + diff --git a/t/complex/candidates.pdf b/t/complex/candidates.pdf new file mode 100644 index 0000000000..0690e1ce45 Binary files /dev/null and b/t/complex/candidates.pdf differ diff --git a/t/complex/candidates.tex b/t/complex/candidates.tex new file mode 100644 index 0000000000..ef1f0bd7e7 --- /dev/null +++ b/t/complex/candidates.tex @@ -0,0 +1,8 @@ +\documentclass{article} +\usepackage{graphicx} +\title{Multiple candidate test} +\begin{document} + +\includegraphics{sunset,encoded} + +\end{document} diff --git a/t/complex/candidates.xml b/t/complex/candidates.xml new file mode 100644 index 0000000000..130ca4e37f --- /dev/null +++ b/t/complex/candidates.xml @@ -0,0 +1,12 @@ + + + + + + + + Multiple candidate test + + + + diff --git a/t/complex/sunset,encoded.gif b/t/complex/sunset,encoded.gif new file mode 100644 index 0000000000..fbac39b6c0 Binary files /dev/null and b/t/complex/sunset,encoded.gif differ diff --git a/t/complex/sunset,encoded.jpg b/t/complex/sunset,encoded.jpg new file mode 100644 index 0000000000..31197918cf Binary files /dev/null and b/t/complex/sunset,encoded.jpg differ