Hallo Rudi,
hier meine Vorschläge.
1. in Zeile 2029 ist by x= ... ein "'" zuviel. Statt
SVG_pO sprintf("<rect x='%d'' y='%d' width='%d' height='%d' $attr/>",muss es heißen
SVG_pO sprintf("<rect x='%d' y='%d' width='%d' height='%d' $attr/>",2.Die beim Zeichnen von Symbolen verwendete sprintf-Funktion wirft eine Warnungsmeldung
Zitat2026.09.21 11:45:09 1: PERL WARNING: Invalid conversion in sprintf: "%"" at /opt/fhem/FHEM/98_SVG.pm line 2061.
(bzw. mit anderen Zeilennummern, wenn das Symbol kein Diamond ist) aus, wenn in einem Label des Plots ein "%"-Zeichen auftaucht, und dieses Label dann in der Legende verwendet wird. Die Darstellung ist zwar korrekt, aber die Warnungsmeldung müsste nicht sein. Dazu muss bei den Symbolen (außer circle) das $attr aus dem ersten Argument sprintf herausgenommen werden und durch ein "%s" ersetzt werden,
$attr wird dann als weiterer Parameter übergeben. Also ab Zeile 2007
if ($lType =~ /^points(:([a-z]+))?(:([\d.]+))?/) {
my $pShape = $2 ? $2 : "di";
my $pSize = $4;
if(!$pSize) { # if not defined, use the line-width as size
$attributes =~ s/stroke-width:(\d+)/stroke-width:1/;
$pSize = $1 && $1 >= 2 ? $1 : 3;
}
foreach my $i (0..int(@{$dxp})-1) {
my ($x1, $y1) = (int($x+$dxp->[$i]),
int($y+$h-($dyp->[$i]-$min)*$hmul));
next if($x1 == $lx && $y1 == $ly);
$lx = $x1; $ly = $y1;
my $attr = "$attributes $lStyle".($isFill ? "" : ' fill="none"');
if ($pShape eq "circle") {
SVG_pO "<circle cx='$x1' cy='$y1' r='$pSize' $attr/>";
} elsif ($pShape eq "square") {
my $d = $pSize * 2;
SVG_pO sprintf(
"<rect x='%d' y='%d' width='%d' height='%d' %s/>",
$x1-$pSize, $y1-$pSize, $d, $d, $attr
);
} elsif ($pShape eq "triangleup") {
SVG_pO sprintf(
"<polygon points='%d,%d %d,%d %d,%d' %s/>",
$x1, $y1-$pSize,
$x1-$pSize, $y1+$pSize,
$x1+$pSize, $y1+$pSize,
$attr
);
} elsif ($pShape eq "triangledown") {
SVG_pO sprintf(
"<polygon points='%d,%d %d,%d %d,%d' %s/>",
$x1-$pSize, $y1-$pSize,
$x1+$pSize, $y1-$pSize,
$x1, $y1+$pSize,
$attr
);
} elsif ($pShape eq "plus") {
SVG_pO sprintf(
"<line x1='%d' y1='%d' x2='%d' y2='%d' %s/>",
$x1-$pSize, $y1, $x1+$pSize, $y1, $attr
);
SVG_pO sprintf(
"<line x1='%d' y1='%d' x2='%d' y2='%d' %s/>",
$x1, $y1-$pSize, $x1, $y1+$pSize, $attr
);
} elsif ($pShape eq "cross") {
SVG_pO sprintf(
"<line x1='%d' y1='%d' x2='%d' y2='%d' %s/>",
$x1-$pSize, $y1-$pSize,
$x1+$pSize, $y1+$pSize,
$attr
);
SVG_pO sprintf(
"<line x1='%d' y1='%d' x2='%d' y2='%d' %s/>",
$x1+$pSize, $y1-$pSize,
$x1-$pSize, $y1+$pSize,
$attr
);
} elsif ($pShape eq "minus") {
SVG_pO sprintf(
"<line x1='%d' y1='%d' x2='%d' y2='%d' %s/>",
$x1-$pSize, $y1, $x1+$pSize, $y1, $attr
);
} else { # diamond
SVG_pO sprintf(
"<polygon points='%d,%d %d,%d %d,%d %d,%d %d,%d' %s/>",
$x1-$pSize, $y1,
$x1, $y1-$pSize,
$x1+$pSize, $y1,
$x1, $y1+$pSize,
$x1-$pSize, $y1,
$attr
);
}
}
}3. Die Funktion plotAsPng macht etwas Schwierigkeiten, die in der libRSVG-library begründet liegen.
a.) Sie kommt mit CSS-Variablen nicht zurecht. Diese sind aber in CSS-Files sehr praktisch, weil man damit in CSS-Dateien den Wartungsaufwand drastisch verringert. Beispielsweise müssen Farben nur einmal angegeben werden und können dann referenziert werden
--svg-l0: red;
--svg-l1: green;
--svg-l2: blue;
.SVGplot.l0 { stroke: var(--svg-l0); }
.SVGplot.l1 { stroke: var(--svg-l1); }
.SVGplot.l2 { stroke: var(--svg-l2); }
b.) Sie ignoriert die Festlegung von Schriftfamilie und Schriftgröße.
c.) Die erzeugten PNG-Dateien sind nicht skalierbar, sondern haben die durch das FHEM-Attribut plotsize festgelegte Größe.
Zur Behebung dieser drei Schwächen muss man zunächst die erzeugte SVG-Datei bearbeiten, dafür gibt es drei neue Helperfunktionen
##################
# Helper function to replace CSS Vars
sub SVG_resolveCssVars($) {
my ($svg) = @_;
my %vars;
# CSS Custom Properties aus :root lesen
if($svg =~ /:root\s*\{(.*?)\}/s) {
my $root = $1;
while($root =~ /--([\w-]+)\s*:\s*([^;]+)\s*;/g) {
$vars{$1} = $2;
}
}
# var(--name) durch den gefundenen Wert ersetzen
$svg =~ s{
var\(--([\w-]+)\)
}{
exists($vars{$1}) ? $vars{$1} : "var(--$1)"
}gex;
return $svg;
}
##################
# Helper function to fix SVG fonts
sub SVG_fixLibRSVGFonts($) {
my ($svg) = @_;
my $fontFamily;
if($svg =~ /(?:^|\})\s*text\s*\{([^}]*)\}/s) {
my $css = $1;
if($css =~ /font-family\s*:\s*([^;!]+)(?:\s*!important)?\s*;/i) {
$fontFamily = $1;
$fontFamily =~ s/^\s+|\s+$//g;
}
}
return $svg if(!defined($fontFamily) || $fontFamily eq "");
# XML-Attribut sicher quoten
$fontFamily =~ s/&/&/g;
$fontFamily =~ s/"/"/g;
$svg =~ s{
<text\b
}{
qq{<text font-family="$fontFamily"}
}gex;
return $svg;
}
##################
# Helper function to scale SVG
sub SVG_scaleForLibRSVG($$$) {
my ($svg, $newWidth, $newHeight) = @_;
return $svg
if($svg !~ /<svg\b[^>]*\bwidth=['"]([\d.]+)(?:px)?['"][^>]*\bheight=['"]([\d.]+)(?:px)?['"]/s);
my ($oldWidth, $oldHeight) = ($1, $2);
$svg =~ s{<svg\b([^>]*)>}{
my $attr = $1;
$attr =~ s/\bwidth=['"][^'"]+['"]/width="${newWidth}px"/;
$attr =~ s/\bheight=['"][^'"]+['"]/height="${newHeight}px"/;
$attr =~ s/\s+viewBox=['"][^'"]*['"]//;
$attr =~ s/width\s*:\s*[\d.]+px/width:${newWidth}px/;
$attr =~ s/height\s*:\s*[\d.]+px/height:${newHeight}px/;
qq{<svg$attr viewBox="0 0 $oldWidth $oldHeight">};
}ex;
return $svg;
}
In der Funktion plotAsPng werden diese drei Helperfunktionen gerufen, bevor die SVG-Datei an die libRSVG übergeben wird. Nach "($mimetype, $svgdata) = SVG_showLog("unused");" wird also der Code geändert zu
my ($w, $h) = split(",", AttrVal($svgName, "plotsize", "800,160"));
$svgdata =~ s/<\/svg>/<polyline opacity="0" points="0,0 $w,$h"\/><\/svg>/;
# Forum #32791,#116138: some lib versions cannot parse complex CSS selectors
$svgdata =~ s/\.SVGplot\./\./g if(AttrVal($svgName, "plotAsPngFix", 0));
$svgdata = Encode::decode("UTF-8", $svgdata) if(!$unicodeEncoding); #129693
#-- CSS Custom Properties fuer LibRSVG aufloesen
$svgdata = SVG_resolveCssVars($svgdata);
#-- Fix fonts
$svgdata = SVG_fixLibRSVGFonts($svgdata);
#-- target width as fourth parameter for output as png
my $targetWidth = $plotName[3];
if($targetWidth) {
my $targetHeight = int($h * $targetWidth / $w + 0.5);
$svgdata = SVG_scaleForLibRSVG(
$svgdata,
$targetWidth,
$targetHeight
);
}Die gewünschte Zielbreite der PNG-Datei wird einfach als 4. Parameter an plotAsPng übergeben.
Und schließlich habe ich noch eine weitere Helperfunktion, die eigentlich das Ziel der ganzen Sache war. Dieser gebe ich als ersten Parameter den Namen eines FHEM SVG-Device, als zweiten Parameter den Namen der PNG-Datei, und als dritten Parameter die horizontale Auflösung in Pixeln.
###############################################################################
#
# plot2png
#
###############################################################################
sub plot2png($$$) {
my ($plot, $file, $width) = @_;
my $png = plotAsPng($plot, undef, undef, $width);
return "plotAsPng failed" if(!defined($png));
open(my $fh, ">", $file)
or return "Cannot write $file: $!";
binmode($fh);
print $fh $png;
close($fh);
return $file;
}Ob man das auch als Ausgabeoption in 98_SVG.pm einbaut, lasse ich offen.
Die komplett geänderte 98_SVG.pm hänge ich zum Testen an. Achtung: Diese Datei enthält in plotAsPng noch einen überflüssigen Block, der mir die komplett erzeugte SVG nach /tmp geschrieben hat.
LG
pah
P.S.: Bevor jemand fragt, warum ich das nicht als Patchdatei liefere: Code-Review...