diff --git a/WolframLanguageForJupyter/Resources/EvaluationUtilities.wl b/WolframLanguageForJupyter/Resources/EvaluationUtilities.wl index 3782571..62917c9 100644 --- a/WolframLanguageForJupyter/Resources/EvaluationUtilities.wl +++ b/WolframLanguageForJupyter/Resources/EvaluationUtilities.wl @@ -72,7 +72,7 @@ If[ (* redirect Print calls into a message to Jupyter, in order to print in the Jupyter notebook *) (* TODO: review other methods: through EvaluationData or WSTP so we don't redefine Print *) Unprotect[Print]; - Print[args___, opts:OptionsPattern[]] := + Print[ourArgs___, opts:OptionsPattern[]] := Block[ { $inPrint=True, @@ -80,7 +80,7 @@ If[ }, If[ !FailureQ[First[$Output]], - Print[args, opts]; + Print[ourArgs, opts]; loopState["printFunction"][ Import[$Output[[1,1]], "String"] ]; diff --git a/WolframLanguageForJupyter/Resources/Initialization.wl b/WolframLanguageForJupyter/Resources/Initialization.wl index 93a7bf2..e328c01 100644 --- a/WolframLanguageForJupyter/Resources/Initialization.wl +++ b/WolframLanguageForJupyter/Resources/Initialization.wl @@ -202,7 +202,34 @@ If[ applyHook[hook_, value_] /; Length[OwnValues[hook]] != 0 := hook[value]; applyHook[hook_, value_] := value; Attributes[applyHook] := HoldAll; + + (* the DPI used by browsers according to the CSS standard: + https://www.w3.org/TR/css3-values/#absolute-lengths *) + cssResolutionDPI = 96; + (* a top-level symbol for controlling the resolution of the output *) + Global`$JupyterResolutionDPI = + (* do not allow a value less than cssResolutionDPI as a default value for this *) + Max[ + cssResolutionDPI, + (* look in SystemInformation for relevant resolutions *) + FirstCase[ + Quiet[UsingFrontEnd[SystemInformation["Devices", "ScreenInformation"]]], + Verbatim[Rule]["Resolution", dpi_?IntegerQ] -> dpi, + 72, + Infinity + ] + ]; + + (* a safe form of Global`$JupyterResolutionDPI *) + safeJupyterResolutionDPI := + First[ + Replace[ + {Global`$JupyterResolutionDPI}, + Except[{value_ /; (IntegerQ[value] && value > 0)}] -> {cssResolutionDPI} + ] + ]; + (* can we use the Front End? *) $canUseFrontEnd := (UsingFrontEnd[$FrontEnd] =!= Null); diff --git a/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl b/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl index c08ac57..26081f6 100644 --- a/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl +++ b/WolframLanguageForJupyter/Resources/OutputHandlingUtilities.wl @@ -31,7 +31,10 @@ If[ Get[FileNameJoin[{DirectoryName[$InputFileName], "Initialization.wl"}]]; (* $canUseFrontEnd, $outputSetToTeXForm, $outputSetToTraditionalForm, - $trueFormatType, failedInBase64 *) + $trueFormatType, + safeJupyterResolutionDPI, + cssResolutionDPI, + failedInBase64 *) (************************************ private symbols @@ -234,28 +237,88 @@ If[ toImageData[result_] := Module[ { + (* whether the result has been "pre-rasterized" *) + preRasterized, (* the preprocessed form of a result *) - preprocessedForm + preprocessedForm, + (* the result of this operation *) + byteArrayResult, + (* the dimensions of the image to use *) + imageDimensions }, + (* preprocess the result *) + preRasterized = (Head[result] =!= Manipulate); If[ - Head[result] === Manipulate, - preprocessedForm = result; + preRasterized, + (* rasterize the result *) + preprocessedForm = + Rasterize[ + result, + ImageResolution -> safeJupyterResolutionDPI + ]; + (* if the preprocessing failed, return $Failed *) + If[ + FailureQ[preprocessedForm], + Return[{$Failed, $Failed}]; + ]; + (* the "natural" image dimensions of preprocessedForm do not appear to be easily predictable, + so just use the dimensions of preprocessedForm when rasterized (again) at 72 DPI *) + imageDimensions = + Rasterize[ + result, + "BoundingBox", + ImageResolution -> 72 + ]; + If[ + (FailureQ[imageDimensions]) || + (!ListQ[imageDimensions]) || + (Length[imageDimensions] < 2), + Return[{$Failed, $Failed}]; + ]; + imageDimensions = + ToString /@ + IntegerPart[imageDimensions[[1;;2]]/72 * cssResolutionDPI * (* also, reduce the size of the image somewhat *) 4/5]; , - preprocessedForm = Rasterize[result]; + (* do not preprocess, and do not set imageDimensions *) + preprocessedForm = result; ]; - (* if the preprocessing failed, return $Failed *) - If[ - FailureQ[preprocessedForm], - Return[$Failed]; - ]; - (* now return preprocessedForm as a byte array corresponding to the PNG format *) - Return[ + + (* save preprocessedForm as a byte array corresponding to the PNG format *) + byteArrayResult = ExportByteArray[ preprocessedForm, - "PNG" - ] + "PNG", + ImageResolution -> safeJupyterResolutionDPI + ]; + If[ + FailureQ[byteArrayResult], + Return[{$Failed, $Failed}]; ]; + + (* for Manipulate results, where the image dimensions are not yet determined, read in the metadata of byteArrayResult *) + If[ + !preRasterized, + imageDimensions = + FirstCase[ + Quiet[ImportByteArray[byteArrayResult, {"PNG", "Options"}]], + Verbatim[Rule]["ImageSize", {width_?IntegerQ, height_?IntegerQ}] :> + (ToString /@ + IntegerPart[ + {width, height}/72 * + cssResolutionDPI * + (* also, reduce the size of the image somewhat *) 4/5 + ]), + $Failed + ]; + If[ + FailureQ[imageDimensions], + Return[{$Failed, $Failed}]; + ]; + ]; + + (* return byteArrayResult and imageDimensions *) + Return[{byteArrayResult, imageDimensions}]; ]; (* generate HTML for the rasterized form of a result *) @@ -265,11 +328,13 @@ If[ (* the rasterization of result *) imageData, (* the rasterization of result in base 64 *) - imageDataInBase64 + imageDataInBase64, + (* the dimensions of the image *) + imageDimensions }, (* rasterize the result *) - imageData = + {imageData, imageDimensions} = toImageData[ $trueFormatType[result] ]; @@ -279,7 +344,7 @@ If[ imageInBase64 = BaseEncode[imageData]; , (* if the rasterization did fail, try to rasterize result with Shallow *) - imageData = + {imageData, imageDimensions} = toImageData[ $trueFormatType[Shallow[result]] ]; @@ -289,7 +354,7 @@ If[ imageInBase64 = BaseEncode[imageData]; , (* if the rasterization did fail, try to rasterize $Failed *) - imageData = + {imageData, imageDimensions} = toImageData[ $trueFormatType[$Failed] ]; @@ -308,8 +373,15 @@ If[ Return[ StringJoin[ (* display a inlined PNG image encoded in base64 *) - "\"Output\"" diff --git a/WolframLanguageForJupyter/Resources/RequestHandlers.wl b/WolframLanguageForJupyter/Resources/RequestHandlers.wl index 8bdd7e7..e42f6e8 100644 --- a/WolframLanguageForJupyter/Resources/RequestHandlers.wl +++ b/WolframLanguageForJupyter/Resources/RequestHandlers.wl @@ -187,7 +187,7 @@ If[ (* otherwise, use a function that converts the output to an image *) If[AllTrue[totalResult["EvaluationResult"], textQ], toOut = toOutText, - toOut = toOutImage + toOut = UsingFrontEnd @* toOutImage ]; (* prepare the content for a reply message frame to be sent on the IO Publish socket *) ioPubReplyContent = ExportString[