Initial Commit
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
function varargout = print (varargin)
|
||||
%PRINT Transparent shim: render PNGs through gnuplot's cairo terminal.
|
||||
%
|
||||
% The model writes ordinary MATLAB - print(f,'plot.png','-dpng') - and must
|
||||
% keep writing ordinary MATLAB, because whatever it writes is what the user
|
||||
% runs. But gnuplot's plain `png` terminal does not anti-alias, and draws
|
||||
% every line as a 1-pixel stroke regardless of LineWidth. On a steep curve
|
||||
% that needs several vertical pixels per column it leaves gaps, so a 4 Hz
|
||||
% sine came out as a field of disconnected dashes rather than a wave.
|
||||
%
|
||||
% `pngcairo` anti-aliases and renders the same LineWidth as a continuous
|
||||
% stroke. Swapping it in here means the picture is right without the code
|
||||
% being wrong: -dpngcairo is Octave-only and would fail in MATLAB, so it
|
||||
% must never appear in anything the model writes.
|
||||
%
|
||||
% The layout goes the same way. Canvas size, type size and where the key
|
||||
% sits are properties of this renderer, not of the analysis, so they are
|
||||
% set here rather than in lines the model would have to remember to write.
|
||||
% Three fixes, each explained at its helper below:
|
||||
%
|
||||
% __owuitools_canvas__ a big canvas WITHOUT print's font scaling
|
||||
% __owuitools_type__ label type the size gnuplot reserves for
|
||||
% __owuitools_legends_below__ the key under the axes, not over the data
|
||||
%
|
||||
% Every one of them is undone before this function returns: the figure is
|
||||
% handed back exactly as the script left it, because the script may draw
|
||||
% into it again and print it a second time.
|
||||
%
|
||||
% This file lives outside the working directory and is put on the path
|
||||
% ahead of Octave's own print, so the user's files are untouched.
|
||||
|
||||
args = varargin;
|
||||
has_device = false;
|
||||
has_size = false;
|
||||
has_resolution = false;
|
||||
wants_png = false;
|
||||
% The layout work below is about pixels, so it covers every picture the
|
||||
% tool can hand back - .png, .jpg, .gif - while the cairo swap is about
|
||||
% one gnuplot terminal and stays with PNG alone.
|
||||
raster_ext = {'.png', '.jpg', '.jpeg', '.gif'};
|
||||
raster_dev = {'-dpng', '-djpg', '-djpeg', '-dgif'};
|
||||
wants_raster = false;
|
||||
|
||||
for k = 1:numel (args)
|
||||
if (ischar (args{k}))
|
||||
a = args{k};
|
||||
if (strncmpi (a, '-S', 2))
|
||||
has_size = true;
|
||||
elseif (strncmpi (a, '-r', 2) && numel (a) > 2)
|
||||
has_resolution = true;
|
||||
elseif (strncmpi (a, '-d', 2) && ! strncmpi (a, '-debug', 6))
|
||||
has_device = true;
|
||||
if (any (strcmpi (a, raster_dev)))
|
||||
wants_raster = true;
|
||||
end
|
||||
if (strcmpi (a, '-dpng'))
|
||||
args{k} = '-dpngcairo';
|
||||
wants_png = true;
|
||||
end
|
||||
elseif (a(1) != '-')
|
||||
[~, ~, ext] = fileparts (a);
|
||||
if (any (strcmpi (ext, raster_ext)))
|
||||
wants_raster = true;
|
||||
wants_png = wants_png || strcmpi (ext, '.png');
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% print() also infers the device from the file extension, so a bare
|
||||
% print(f,'plot.png') has to be caught too.
|
||||
if (wants_png && ! has_device)
|
||||
args{end+1} = '-dpngcairo';
|
||||
end
|
||||
|
||||
% Paper inches become pixels at the print resolution, which defaults to
|
||||
% 150 dpi: the JPEG terminal took the 1100 pt canvas set below and made a
|
||||
% 2292 px image of it, with 10 pt type marooned in the middle. pngcairo
|
||||
% ignores the setting, so pinning it to 72 costs nothing there and makes
|
||||
% one inch mean one hundred points wherever it is read.
|
||||
if (wants_raster && ! has_size && ! has_resolution)
|
||||
args{end+1} = '-r72';
|
||||
end
|
||||
|
||||
shim_dir = fileparts (mfilename ('fullpath'));
|
||||
rmpath (shim_dir);
|
||||
unwind_protect
|
||||
undo = {};
|
||||
if (wants_raster)
|
||||
fig = __owuitools_figure__ (args);
|
||||
undo = [__owuitools_canvas__(fig, has_size), ...
|
||||
__owuitools_type__(fig), ...
|
||||
__owuitools_legends_below__(fig)];
|
||||
end
|
||||
unwind_protect
|
||||
if (nargout > 0)
|
||||
[varargout{1:nargout}] = print (args{:});
|
||||
else
|
||||
print (args{:});
|
||||
end
|
||||
unwind_protect_cleanup
|
||||
for k = numel (undo):-1:1
|
||||
try
|
||||
set (undo{k}{1}, undo{k}{2}, undo{k}{3});
|
||||
catch
|
||||
end
|
||||
end
|
||||
end_unwind_protect
|
||||
unwind_protect_cleanup
|
||||
addpath (shim_dir, '-begin');
|
||||
end_unwind_protect
|
||||
end
|
||||
|
||||
function fig = __owuitools_figure__ (args)
|
||||
% print(f,...) names its figure; print('plot.png',...) means the current one.
|
||||
fig = [];
|
||||
try
|
||||
for k = 1:numel (args)
|
||||
if (! ischar (args{k}) && isscalar (args{k}) && ishghandle (args{k}))
|
||||
fig = ancestor (args{k}, 'figure');
|
||||
return;
|
||||
end
|
||||
end
|
||||
if (! isempty (get (0, 'currentfigure')))
|
||||
fig = gcf ();
|
||||
end
|
||||
catch
|
||||
fig = [];
|
||||
end
|
||||
end
|
||||
|
||||
function undo = __owuitools_canvas__ (fig, has_size)
|
||||
%__OWUITOOLS_CANVAS__ A bigger picture without doubling the type with it.
|
||||
%
|
||||
% -S is the obvious way to ask for a 1100 px canvas and the wrong one.
|
||||
% print.m reads it as "a six-inch figure blown up to 1100 px" and scales
|
||||
% every font in the figure by
|
||||
%
|
||||
% width / 6 / screenpixelsperinch (72 here, so width/432)
|
||||
%
|
||||
% which at the old -S900,675 was 2.08x: the 10 pt axes font was drawn at
|
||||
% 16.7 pt and the 11 pt title at 18.3 pt on a canvas only 900 px wide.
|
||||
% That is what pushed titles off the right edge, sat axis labels on top
|
||||
% of tick labels, and ran bar-chart category names into each other. A
|
||||
% bigger -S never helped, because the type grew with the canvas.
|
||||
%
|
||||
% Sizing by paper instead takes the other branch of print.m, where the
|
||||
% canvas is paperposition x 72 and the scale factor stays 1 - so the type
|
||||
% keeps the size Octave gave it and a bigger canvas buys real room.
|
||||
%
|
||||
% The shape follows the figure: a script that made a wide figure gets a
|
||||
% wide PNG. Only the width is fixed, and only when the caller did not ask
|
||||
% for a size itself.
|
||||
undo = {};
|
||||
if (has_size || isempty (fig))
|
||||
return;
|
||||
end
|
||||
try
|
||||
width = 1100;
|
||||
pos = get (fig, 'position');
|
||||
aspect = 0.75;
|
||||
if (numel (pos) == 4 && pos(3) > 0 && pos(4) > 0)
|
||||
aspect = min (1.5, max (0.4, pos(4) / pos(3)));
|
||||
end
|
||||
undo = {{fig, 'paperunits', get(fig, 'paperunits')}, ...
|
||||
{fig, 'paperposition', get(fig, 'paperposition')}, ...
|
||||
{fig, 'paperpositionmode', get(fig, 'paperpositionmode')}};
|
||||
set (fig, 'paperunits', 'inches');
|
||||
set (fig, 'paperposition', [0, 0, width/72, round(width*aspect)/72]);
|
||||
set (fig, 'paperpositionmode', 'manual');
|
||||
catch
|
||||
end
|
||||
end
|
||||
|
||||
function undo = __owuitools_type__ (fig)
|
||||
%__OWUITOOLS_TYPE__ Axis labels the size gnuplot reserved room for.
|
||||
%
|
||||
% gnuplot lays a plot out in character units of the TERMINAL font, and
|
||||
% Octave sets that terminal font from the axes font size - 10 pt - while
|
||||
% drawing axis labels at 11, because labelfontsizemultiplier is 1.1. The
|
||||
% gap is small and the result is not: the space reserved below the tick
|
||||
% labels is a 10 pt line, the xlabel drawn into it is an 11 pt line, and
|
||||
% it lands on the tick row. Same story on the left for the ylabel.
|
||||
%
|
||||
% Dropping the label multiplier to 1 makes the two agree exactly, which
|
||||
% is the whole fix - verified against gnuplot directly: same plot, same
|
||||
% key, xlabel at 11 pt overlaps the ticks and at 10 pt clears them.
|
||||
%
|
||||
% The title keeps its 1.1 and its bold weight. It sits in the top margin
|
||||
% with nothing under it to collide with, and it is the one piece of text
|
||||
% a reader should find first.
|
||||
%
|
||||
% With that tie in place the size can be chosen on its merits, and 10 pt
|
||||
% - Octave's default, for a 560 px figure on a screen - is too small on a
|
||||
% 1100 px canvas that a chat window then shows at 700. 12 pt is about
|
||||
% where it reads at that width and still leaves six category names room
|
||||
% side by side. A script that set a font size itself keeps it: that is
|
||||
% what fontsizemode records.
|
||||
undo = {};
|
||||
if (isempty (fig))
|
||||
return;
|
||||
end
|
||||
try
|
||||
ax = findall (fig, 'type', 'axes');
|
||||
for k = 1:numel (ax)
|
||||
if (strcmp (get (ax(k), 'tag'), 'legend'))
|
||||
continue;
|
||||
end
|
||||
undo{end+1} = {ax(k), 'labelfontsizemultiplier', ...
|
||||
get(ax(k), 'labelfontsizemultiplier')};
|
||||
set (ax(k), 'labelfontsizemultiplier', 1);
|
||||
if (strcmp (get (ax(k), 'fontsizemode'), 'auto'))
|
||||
undo{end+1} = {ax(k), 'fontsize', get(ax(k), 'fontsize')};
|
||||
set (ax(k), 'fontsize', 12);
|
||||
end
|
||||
end
|
||||
catch
|
||||
end
|
||||
end
|
||||
|
||||
function undo = __owuitools_legends_below__ (fig)
|
||||
%__OWUITOOLS_LEGENDS_BELOW__ The key under the axes, not over the data.
|
||||
%
|
||||
% A legend inside the axes sits on top of what it is labelling: the key
|
||||
% is opaque here (see patch-legend-opaque.sh, and transparent is worse -
|
||||
% the lines then run through the text), so it hides the curves under it.
|
||||
% 'best' does not help either; this backend does not implement it and
|
||||
% warns, and every legend lands top-right regardless of what was asked.
|
||||
%
|
||||
% Below the axes nothing can collide with the data, because gnuplot
|
||||
% reserves the space for an outside key and shrinks the plot to fit it.
|
||||
%
|
||||
% The frame comes off with the move. gnuplot packs an outside key flush
|
||||
% against the x-axis label - a one-pixel gap, whatever the type size or
|
||||
% the label's own offset - so the box's top edge is drawn through the
|
||||
% descenders of that label and reads as an underline under it. Out on
|
||||
% the white margin, with no plot lines behind the text, the frame was
|
||||
% only decoration; without it the key is a caption under the plot.
|
||||
% (The fill is handled one layer down: patch-legend-opaque.sh keeps an
|
||||
% outside key transparent, because an opaque one erased those same
|
||||
% descenders outright - "the trigger edge" printed as "the triaaer edae".)
|
||||
%
|
||||
% Two legends are left where they are. One a script deliberately put
|
||||
% outside already - 'eastoutside' - because that is a layout decision
|
||||
% rather than the default falling where it falls. And every legend in a
|
||||
% figure that has more than one plot in it: a subplot grid has no margin
|
||||
% under each plot to give away, only the gap before the next one's title,
|
||||
% and a key dropped into that gap lands on both. Inside is where they fit
|
||||
% there, and inside is where the opaque fill still applies.
|
||||
undo = {};
|
||||
if (isempty (fig))
|
||||
return;
|
||||
end
|
||||
try
|
||||
h = findall (fig, 'type', 'axes');
|
||||
tags = get (h, 'tag');
|
||||
if (! iscell (tags))
|
||||
tags = {tags};
|
||||
end
|
||||
if (sum (! ismember (tags, {'legend', 'colorbar'})) != 1)
|
||||
return;
|
||||
end
|
||||
for k = 1:numel (h)
|
||||
if (! strcmp (get (h(k), 'tag'), 'legend'))
|
||||
continue;
|
||||
end
|
||||
loc = get (h(k), 'location');
|
||||
if (! ischar (loc) || ! isempty (strfind (lower (loc), 'outside')))
|
||||
continue;
|
||||
end
|
||||
undo{end+1} = {h(k), 'location', loc};
|
||||
set (h(k), 'location', 'southoutside');
|
||||
undo{end+1} = {h(k), 'box', get(h(k), 'box')};
|
||||
set (h(k), 'box', 'off');
|
||||
end
|
||||
catch
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user