2

I use the following answer in order to scale my tikzpictures.

I'd now like to turn to the issue of externalizing figures. The problem is that the autoscale autoid option sometimes need 2 compilations for the figure to be the right size, so the pdf image generated by the tikzexternal library is often the wrong size.

But now that the figures are externalized (and a pdf is generated for each of them), I don't think it's necessary to use autoscale autoid any more.

Is it possible to create a tikz environment that allows you to manage the size (0.5\columnwidth for example) of the imported pdf (corresponding to the tikz figure) ?

MWE

\documentclass[twocolumn,landscape]{article}
\usepackage{xparse}
\usepackage{tikz,tkz-fct}
\usetikzlibrary{calc}
\usetikzlibrary{external}
%\tikzexternalize

\makeatletter \ExplSyntaxOn \msg_new:nnn { nilcouv } { duplicate-figure-id } { duplicate~figure~identifier:~'#1'. }

% Sequence recording all figure identifiers (for the 'scale to max size' TikZ % style) found so far \seq_new:N \g__nilcouv_scale_to_max_style_figure_ids_seq % Counter used when generating automatic figure identifiers for 'autoscale' \int_new:N \g_nilcouv_last_autogenerated_figure_nb_int

\cs_new_protected:Npn __nilcouv_check_unique_id:n #1 { \seq_if_in:NnTF \g__nilcouv_scale_to_max_style_figure_ids_seq {#1} { \msg_error:nnn { nilcouv } { duplicate-figure-id } {#1} } { \seq_gput_right:Nn \g__nilcouv_scale_to_max_style_figure_ids_seq {#1} } }

% Automatic generation of figure ids (the pattern is defined here) \cs_new:Npn __nilcouv_autogenerated_id:n #1 { nilcouv~autogenerated~id~#1 }

\cs_generate_variant:Nn __nilcouv_autogenerated_id:n { V }

\cs_new_protected:Npn __nilcouv_autoscale:nnn #1#2#3 { \tikzset { scale~to~max~size={#1}{#2}{#3} } }

\cs_generate_variant:Nn __nilcouv_autoscale:nnn { x }

\cs_new_protected:Npn __nilcouv_autoscale_autoid:nn #1#2 { % Increment the counter \int_gincr:N \g_nilcouv_last_autogenerated_figure_nb_int % Call the 'autoscale' style with the new id __nilcouv_autoscale:xnn { __nilcouv_autogenerated_id:V \g_nilcouv_last_autogenerated_figure_nb_int } {#1} {#2} }

% Set up aliases using LaTeX2e naming style \cs_new_eq:NN \nilcouv@check@unique@id __nilcouv_check_unique_id:n \cs_new_eq:NN \nilcouv@autoscale@autoid __nilcouv_autoscale_autoid:nn \ExplSyntaxOff

% Autoscaling technique that doesn't affect font sizes in TikZ pictures. % (based on code from marmot: <https://tex.stackexchange.com/a/497749/73317>) % % #1: unique per-picture id allowing several pictures to use this mechanism % in a given document (it should contain no control sequence token nor % active character) % #2: target width % #3: target height \newcommand*{\nilcouv@ExportBB}[3]{% \path let \p1=($(current bounding box.north east)-(current bounding box.south west)$), \n1={#2/\x1},\n2={#3/\y1} in \pgfextra{\pgfmathsetmacro{\nilcouv@figscale}{min(\n1,\n2)}% \expandafter\xdef\csname nilcouv@auto@figscale@#1\endcsname{% \nilcouv@figscale}}; \immediate\write@mainaux{% \string\expandafter \gdef\string\csname\space nilcouv@auto@figscale@#1\string\endcsname{% \csname nilcouv@auto@figscale@#1\endcsname}}% }

\tikzset{ % Arguments: figure identifier, target width, target height scale to max size/.style n args={3}{ execute at end picture={\nilcouv@ExportBB{#1}{#2}{#3}}, /utils/exec={\nilcouv@check@unique@id{#1}% \ifcsname nilcouv@auto@figscale@#1\endcsname \wlog{Found autoscale value for picture '#1'}% \else \typeout{Automatically-scaled pictures: please recompile for picture '#1'.}% \expandafter\gdef \csname nilcouv@auto@figscale@#1\endcsname{1}% \fi}, scale=\csname nilcouv@auto@figscale@#1\endcsname, }, % Same style except the id is automatically generated using a counter autoscale autoid/.style 2 args={% /utils/exec={\nilcouv@autoscale@autoid{#1}{#2}}}, } % End of the code based on <https://tex.stackexchange.com/a/497749/73317> \makeatother

\begin{document}

\begin{tikzpicture}[autoscale autoid={0.3\columnwidth}{\maxdimen}] \tkzInit[xmin=-1,xmax=3,ymin=-1,ymax=1.2] \tkzDrawX \tkzDrawY % Fonction \tkzClip \tkzFct[thick,domain=0.55:5]{(\x\x+\x-1)/(\x*3)} \tkzDefPointByFct(1.39)\tkzGetPoint{A}\tkzDrawPointsize=3 \draw[dotted,thick] (1.39,0) -- (1.39, 0.86) -- (0,0.86) node[left] {( f(x) )}; \draw[thick] (1.39,-0.1) node[below] {( x )} -- (1.39,0.1); %Annotations \draw[<->,ultra thick] (1,-0.35) -- (2,-0.35); \node at (1.5,-0.45){(A)}; \draw[dashed] (1, 0) -- (1, 1) -- (0, 1); \draw[dashed] (2, 0) -- (2, 0.625) -- (0, 0.625); \draw[<->,ultra thick] (-0.6,0.625) -- (-0.6,1); \end{tikzpicture}

\end{document}

jowe_19
  • 939
  • Have you seen the [tag:tikzscale] package? There are a lot of ressources combining some form of scaling and externalizing the image on this site already. Writing to the aux file and externalizing will be hard to make compatible. The externalization process has to check that value again the previous one as well. – Qrrbrbirlbel Oct 23 '23 at 14:30
  • Not sure if memoize could do this, but tikzscale is generally preferable for scaling. – cfr Oct 23 '23 at 16:23
  • @cfr Yeah, (untransformed) nodes and the line width (that doesn't scale) contributing to the bounding box make this an asymptotic process. One will need a stop condition (say size difference less than 0.05pt) for this to finalize rather sooner than later. – Qrrbrbirlbel Oct 23 '23 at 23:23
  • Wondering about just having memoize do the scaling ...? – cfr Oct 25 '23 at 18:57

3 Answers3

4

The idea in cfr's answer is absolutely sound: If/when a command does not want to be memoized, it should issue \mmzAbort. This is precisely how Memoize itself deals with \refs: it aborts memoization until the reference is defined.

I'm posting a new answer merely to iron out a couple of details, some related to Memoization, some not.

  1. Enumerating the autoscaled pictures has the downside of recompiling them when a picture is inserted or removed.

  2. cfr's mechanism for detecting that the scaling factor had stabilized is an absolute necessity for memoization — otherwise, it's impossible to tell when the picture is ready for this. However, there is a problem introduced by her modification: the picture won't resize when the target width/height is changed.

  3. In a similar fashion, once memoized, the picture won't ever get recompiled. The problem arises when \columnwidth changes. (This is not a problem when the code of the picture changes, e.g. when 0.3\columnwidth is changed to 0.5\columnwidth.)

  4. The autoscale code should work both in the presence and absence of Memoize.

My code below addresses all these issues. I have furthermore taken the liberty of separating it into a package (autoscale.sty) and the document, and fashioning the UI into my liking ;-)

Issue (1) also concerns TikZ external library, and Memoize solves it by referring to md5sums of the picture source code. The problem here is of course to grab the source code, but the mechanism used by Memoize can be easily used by other packages, too, as it is shipped as a separate package, Advice. Below, Advice is configured so that whenever a tikzpicture environment is invoked, \storemdfivesum is executed instead. This macro gets the entire environment as the argument, and can easily compute its md5sum — and then invoke the original tikzpicture.

I address issue (2) by storing (in .aux) the scaling factor into control sequence \autoscale@factor@<md5sum>@<target dimension>. Whenever the target width or height changes, the factor from .aux simply does not apply, and resizing starts from scratch.

For (3), the argument to autoscale width/height is appended to Memoize's context — whenever the value (i.e. expansion) of the context changes, the extern is recompiled. While something like \mmzset/\mmznext{context={columnwidth=\the\columnwidth}} in the document itself would work, the autoscaling macro below can do this as well, simply sticking whatever argument to autoscale width/height into the context (and illustrating that the context may be appended to while the picture is being memoized).

(4) Memoize has a built-in solution for a package wishing to support it: a package stub memoizable, which defines all Memoize commands as dummies.

%%%%%%%%% autoscale.sty %%%%%%%%%

% cwestiwn: https://tex.stackexchange.com/questions/699289/scaling-externalized-tikz-pictures

% When a package uses Memoize commands, one should say % \RequirePackage{memoizable} to freely use \mmzset etc. even when Memoize is % not loaded. % % I found two bugs in Memoize v1.0.0, however --- I forgot to load pgfkeys and % define \mmzAbort in packages memoizable/nomemoize --- thus the workaround % below (until v1.0.1).

\RequirePackage{pgfkeys} \ifdefined\mmzAbort\else\let\mmzAbort\relax\fi \RequirePackage{memoizable}

\RequirePackage{tikz} \usetikzlibrary{calc}

% Automatically compute the md5sum of every tikzpicture environment; the md5sum % is available, within and after the picture, in \currentmdfivesum. \RequirePackage{advice} % actually unnecessary when Memoize is loaded \pgfqkeys{/md5sum}{ .install advice, advice={tikzpicture}{inner handler=\storemdfivesum}, } \RequirePackage{pdftexcmds} \ifdefined\luatexversion \long\def\pdf@mdfivesum#1{% \directlua{% oberdiek.pdftexcmds.mdfivesum("\luaescapestring{#1}", "byte")% }% }% \fi \def\storemdfivesum#1{% \xdef\currentmdfivesum{\pdf@mdfivesum{\unexpanded{#1}}}% \AdviceOriginal#1% }

% The definition of autoscale begins here.

\tikzset{ % #1 = target width/height autoscale width/.code={\autoscale{#1}{width}{\x1}}, autoscale height/.code={\autoscale{#1}{height}{\y1}}, }

% #1 = target width/height, #2 = "width"/"height", #3 = "\x1"/"\y1" \def\autoscale#1#2#3{% \ifcsdef{autoscale@factor@\currentmdfivesum @\the\dimexpr#1\relax}{% \letcs\autoscale@factor{autoscale@factor@\currentmdfivesum @\the\dimexpr#1\relax}% }{% \def\autoscale@factor{1}% }% \pgfkeysalso{% execute at end picture=\autoscale@size@to@aux{#1}{#2}{#3}, scale=\autoscale@factor, }% }

% #1 = target width/height, #2 = "width"/"height", #3 = "\x1"/"\y1" \def\autoscale@size@to@aux#1#2#3{% \path let \p1=($(current bounding box.north east)-(current bounding box.south west)$), \n1={\autoscale@factor*\x1}, \n2={#1/#3} in \pgfextra{% \autoscale@if@roughly@equal{0.1pt}{#1}{\n1}{% \immediate\write@mainaux{% \csgdef{autoscale@factor@\currentmdfivesum @\the\dimexpr#1\relax}{\autoscale@factor}% }% \mmzset{context={target #2=\the\dimexpr#1\relax}}% }{% \immediate\write@mainaux{% \csgdef{autoscale@factor@\currentmdfivesum @\the\dimexpr#1\relax}{\n2}% }% \mmzAbort }% }; }

% Such a macro is actually already defined by Memoize, but as we want autoscale % to work without Memoize as well, I copied the definition here. This macro % checks whether the given dimensions (|#2| and |#3|) are equal within the % tolerance given by |#1|. (We don't use |\ifpdfabsdim|, because it is % unavailable in \hologo{XeTeX}.) \def\autoscale@if@roughly@equal#1#2#3{% \dimen0=\dimexpr#2-#3\relax \ifdim\dimen0<0pt \dimen0=-\dimen0\relax \fi \ifdim\dimen0>#1\relax \expandafter@secondoftwo \else \expandafter@firstoftwo \fi }%

And the document:

\documentclass[twocolumn,landscape]{article}

% Memoize must be loaded before autoscale, because autoscale supports % Memoize. And it must be loaded before tkz-fct, because the latter loads TikZ % library "fadings". \usepackage{memoize} \usepackage{autoscale} % loads TikZ as well \usepackage{tkz-fct}

\usepackage{lipsum} \begin{document}

\lipsum[1]

\noindent \begin{tikzpicture}[autoscale width=\columnwidth] \tkzInit[xmin=-1,xmax=3,ymin=-1,ymax=1.2] \tkzDrawX \tkzDrawY % Fonction \tkzClip \tkzFct[thick,domain=0.55:5]{(\x\x+\x-1)/(\x*3)} \tkzDefPointByFct(1.39)\tkzGetPoint{A}\tkzDrawPointsize=3 \draw[dotted,thick] (1.39,0) -- (1.39, 0.86) -- (0,0.86) node[left] {( f(x) )}; \draw[thick] (1.39,-0.1) node[below] {( x )} -- (1.39,0.1); % Annotations \draw[<->,ultra thick] (1,-0.35) -- (2,-0.35); \node at (1.5,-0.45){(A)}; \draw[dashed] (1, 0) -- (1, 1) -- (0, 1); \draw[dashed] (2, 0) -- (2, 0.625) -- (0, 0.625); \draw[<->,ultra thick] (-0.6,0.625) -- (-0.6,1); \end{tikzpicture}%

\lipsum[2]

\end{document}

cfr
  • 198,882
  • Thanks a lot for this answer, the code doesn't seem to work with lualatex. I get the error ! Undefined control sequence. \storemdfivesum ...urrentmdfivesum {\pdfmdfivesum {\unexpanded {#1}}}\Advice...l.31 \end{tikzpicture} – jowe_19 Oct 26 '23 at 18:14
  • It's not that I haven't read the whole manual … but putting it all together as intended is another challenge. 1. Are we doing the md5sum thing in autoscale in case memoize is not loaded but we still want the ID of a diagram to be its MD5? Otherwise, why not just use the same hash that MD5 uses? (Is there a way to access it with memoize active?) (In my answer, I'm simply using the pgfid which, yes, changes when a user is moving around pictures but I don't worry about that, really.) – Qrrbrbirlbel Oct 26 '23 at 18:15
  • On that idea, is there a way to retrigger compilation of the TikZ picture when we detect it's not yet scaled correctly? Why not just typeset the whole TikZpicture until it is stable without going through the compilations and the aux file? Is this something Memoize can do? It already grabs the whole environment … or rather Advice is? It's already doing the same by forwarding it to the MD5 process? Couldn't we just forward the TikZpicture to or own \recompileUntilFinalzedAndThenMemoizeIt. Wouldn't that be possibly faster? – Qrrbrbirlbel Oct 26 '23 at 18:18
  • @jowe_19 I thought I'd not complicate matters with LuaTeX, but I see I was wrong ;-) Edited. (LuaTeX removed the \pdfmdfivesum primitive because it is easily implementable in Lua.) – Sašo Živanović Oct 26 '23 at 18:21
  • 1
    @Qrrbrbirlbel At the moment, the md5sum computed by Memoize is not available in a public macro, but I could easily change that. One other reason for not using Memoize's md5sum is that I wanted autoscale to work both with and without Memoize. And I also wanted to show off Advice :D – Sašo Živanović Oct 26 '23 at 18:25
  • I'm trying to understand your suggestion, but I think that what you're suggesting is actually what's already going on, anyway (and was going on even in the previous incarnations of autoscale). Essentially what is happening is that the picture is compiled and if the resulting dimension is close enough to the target, the scaling factor is "frozen". Similarly for the Memoization part: once the scaling factor is established, Memoization is not aborted anymore. – Sašo Živanović Oct 26 '23 at 18:29
  • Perhaps it is worth emphasizing that memoization overhead is almost negligible, especially the initial part. Memoize simply tries to load a file or two and if it fails, then compilation of the picture starts. That compilation takes most of the time anyway, and the fact that the result is in the end "caught" in a box counts for almost nothing. Then, I know \mmzAbort sounds scary but all it does is set a conditional which prevents output of memos and externs after compiling the picture. In short: running memoization and then aborting it is perfectly fine, also time-wise. – Sašo Živanović Oct 26 '23 at 18:34
  • I was thinking of just re-typesetting the TikZ picture in one TeX compilation run which would make the pictures stable after one compilation (which takes much longer then, of course). That way, the whole other document wouldn't need to be typeset again and again … but maybe it's too much headache on top of everything else we could cache with Memoize (whole chapters and whatnot). – Qrrbrbirlbel Oct 26 '23 at 18:36
  • @SašoŽivanović : thanks, but I think that your posted answer has a layout problem now :) – jowe_19 Oct 26 '23 at 18:39
  • @jowe_19 Can you explain what the problem is? – Sašo Živanović Oct 26 '23 at 18:56
  • 1
    @SašoŽivanović I think that Cfr has fixed it. – jowe_19 Oct 26 '23 at 19:04
  • @SašoŽivanović Curiously, when I compile in my editor (Texifier, in Lualatex with shell-escape too), the pdfs of the externalized images do not disappear, whereas this is the case in the terminal. – jowe_19 Oct 26 '23 at 19:08
  • I'm not familiar with Texifier. Is it perhaps MikTeX based? There seem to be some initial hurdles there ... https://github.com/sasozivanovic/memoize/issues/4 – Sašo Živanović Oct 26 '23 at 19:15
  • Unfortunately, on some figures, I get the following error : `Runaway argument? { \node at (0,0) {{\large {(\mcP _{n_0})}}}; \node at (3,0) {{\large \ETC. ! Paragraph ended before \storemdfivesum was complete. \par` – jowe_19 Nov 03 '23 at 15:08
3

I think this version works. It takes 3 compilations for the output to stabilise. And (finally!), the output then (hopefully) stays stable.

This uses memoize rather than the external library. If you use the default perl extraction method or the python version, you do not need full shell-escape.

The basic idea is prevent the continuous recompilation required by the original autoscale code by not using the newly calculated scaling factor unless it differs by more than a little bit, because otherwise the factor changes on every compilation. (This has nothing to do with externalisation. It's just a function of the original code.)

We then compare the resulting scaling factor with the one which resulted on the previous compilation (if any). If the factors differ, we abort memoization. If the factors are the same, we proceed. Whether memoization actually completes then depends on whatever further things memoize looks at. We just don't forcibly stop it.

This is not an ideal solution, by any means, because we are not using a proper memoization mechanism to determine when to compile the external image. But I think it works. That is, I don't yet know all the cases where it fails.

\documentclass[twocolumn,landscape]{article}
% cwestiwn: https://tex.stackexchange.com/questions/699289/scaling-externalized-tikz-pictures
\usepackage[extract=perl]{memoize}
\usepackage{tikz,tkz-fct}
\usetikzlibrary{calc}

\makeatletter \ExplSyntaxOn \msg_new:nnn { nilcouv } { duplicate-figure-id } { duplicate~figure~identifier:~'#1'. }

% Sequence recording all figure identifiers (for the 'scale to max size' TikZ % style) found so far \seq_new:N \g__nilcouv_scale_to_max_style_figure_ids_seq % Counter used when generating automatic figure identifiers for 'autoscale' \int_new:N \g_nilcouv_last_autogenerated_figure_nb_int

\cs_new_protected:Npn __nilcouv_check_unique_id:n #1 { \seq_if_in:NnTF \g__nilcouv_scale_to_max_style_figure_ids_seq {#1} { \msg_error:nnn { nilcouv } { duplicate-figure-id } {#1} } { \seq_gput_right:Nn \g__nilcouv_scale_to_max_style_figure_ids_seq {#1} } }

% Automatic generation of figure ids (the pattern is defined here) \cs_new:Npn __nilcouv_autogenerated_id:n #1 { nilcouv~autogenerated~id~#1 }

\cs_generate_variant:Nn __nilcouv_autogenerated_id:n { V }

\cs_new_protected:Npn __nilcouv_autoscale:nnn #1#2#3 { \tikzset { scale~to~max~size={#1}{#2}{#3} } }

\cs_generate_variant:Nn __nilcouv_autoscale:nnn { x }

\cs_new_protected:Npn __nilcouv_autoscale_autoid:nn #1#2 { % Increment the counter \int_gincr:N \g_nilcouv_last_autogenerated_figure_nb_int % Call the 'autoscale' style with the new id __nilcouv_autoscale:xnn { __nilcouv_autogenerated_id:V \g_nilcouv_last_autogenerated_figure_nb_int } {#1} {#2} }

% Set up aliases using LaTeX2e naming style \cs_new_eq:NN \nilcouv@check@unique@id __nilcouv_check_unique_id:n \cs_new_eq:NN \nilcouv@autoscale@autoid __nilcouv_autoscale_autoid:nn \ExplSyntaxOff

% Autoscaling technique that doesn't affect font sizes in TikZ pictures. % (based on code from marmot: <https://tex.stackexchange.com/a/497749/73317>) % % #1: unique per-picture id allowing several pictures to use this mechanism % in a given document (it should contain no control sequence token nor % active character) % #2: target width % #3: target height \newcommand*{\nilcouv@ExportBB}[3]{% \path let \p1=($(current bounding box.north east)-(current bounding box.south west)$), \n1={#2/\x1},\n2={#3/\y1} in \pgfextra{\pgfmathsetmacro{\nilcouv@figscale}{min(\n1,\n2)}% \ifcsname nilcouv@auto@figscale@#1\endcsname \edef\nilcouv@tmpb{\csname nilcouv@auto@figscale@#1\endcsname}% \pgfmathsetmacro \nilcouv@tmpc{\nilcouv@tmpb - \nilcouv@figscale}% \ifdim \nilcouv@tmpc pt<0.1pt \relax \else \expandafter\xdef\csname nilcouv@auto@figscale@#1\endcsname{% \nilcouv@figscale}% \fi \else \expandafter\xdef\csname nilcouv@auto@figscale@#1\endcsname{% \nilcouv@figscale}% \fi \edef\nilcouv@tmpe{\csname nilcouv@auto@figscale@old@#1\endcsname}% \ifx\nilcouv@tmpe\nilcouv@tmpb \relax \else \mmzAbort \fi }; \immediate\write@mainaux{% \string\expandafter \gdef\string\csname\space nilcouv@auto@figscale@old@#1\string\endcsname{% \csname nilcouv@auto@figscale@#1\endcsname}% \string\expandafter \gdef\string\csname\space nilcouv@auto@figscale@#1\string\endcsname{% \csname nilcouv@auto@figscale@#1\endcsname}}% }

\tikzset{ % Arguments: figure identifier, target width, target height scale to max size/.style n args={3}{% execute at end picture={\nilcouv@ExportBB{#1}{#2}{#3}}, /utils/exec={\nilcouv@check@unique@id{#1}% \ifcsname nilcouv@auto@figscale@#1\endcsname \wlog{Found autoscale value for picture '#1'}% \else \typeout{Automatically-scaled pictures: please recompile for picture '#1'.}% \expandafter\gdef \csname nilcouv@auto@figscale@#1\endcsname{1}% \fi }, scale=\csname nilcouv@auto@figscale@#1\endcsname, }, % Same style except the id is automatically generated using a counter autoscale autoid/.style 2 args={% /utils/exec={% \nilcouv@autoscale@autoid{#1}{#2}% }, }, } % End of the code based on <https://tex.stackexchange.com/a/497749/73317> \makeatother

\begin{document} \begin{tikzpicture}[autoscale autoid={0.3\columnwidth}{\maxdimen}] \tkzInit[xmin=-1,xmax=3,ymin=-1,ymax=1.2] \tkzDrawX \tkzDrawY % Fonction \tkzClip \tkzFct[thick,domain=0.55:5]{(\x\x+\x-1)/(\x*3)} \tkzDefPointByFct(1.39)\tkzGetPoint{A}\tkzDrawPointsize=3 \draw[dotted,thick] (1.39,0) -- (1.39, 0.86) -- (0,0.86) node[left] {( f(x) )}; \draw[thick] (1.39,-0.1) node[below] {( x )} -- (1.39,0.1); %Annotations \draw[<->,ultra thick] (1,-0.35) -- (2,-0.35); \node at (1.5,-0.45){(A)}; \draw[dashed] (1, 0) -- (1, 1) -- (0, 1); \draw[dashed] (2, 0) -- (2, 0.625) -- (0, 0.625); \draw[<->,ultra thick] (-0.6,0.625) -- (-0.6,1); \end{tikzpicture}%

width is 96.05pt and height is 6.83331pt

width is 97.16948pt and height is 6.83331pt

width is 132.29497pt and height is 6.83331pt

abcdefghijklmnopqrstuvwxyz

\end{document}

externalised, autoscaled image with memoize

Compilation record:

\mmzPrefix {.//prawf.}
\mmzUsedCMemo {.//prawf.AB565A279548684A84116CD5604FF9BE.memo}
\mmzUsedExtern {.//prawf.AB565A279548684A84116CD5604FF9BE-E778DCCCB8AAB0BBD3F6CFEEFD2421F8.pdf}
\mmzUsedCCMemo {.//prawf.AB565A279548684A84116CD5604FF9BE-E778DCCCB8AAB0BBD3F6CFEEFD2421F8.memo}
\endinput 

Files:

prawf.AB565A279548684A84116CD5604FF9BE-E778DCCCB8AAB0BBD3F6CFEEFD2421F8.memo  
prawf.AB565A279548684A84116CD5604FF9BE.memo  
prawf.mmz.log
prawf.AB565A279548684A84116CD5604FF9BE-E778DCCCB8AAB0BBD3F6CFEEFD2421F8.pdf   
prawf.mmz
cfr
  • 198,882
  • \mmzAbort is all? I don't think I understand how memoize works internally at all. That said, I'm getting ! I can't find file `.//prawf.AB565A279548684A84116CD5604FF9BE.memo'. on second compilation even though the file (but why the double //?) is there. Probably still something wrong with my setup. – Qrrbrbirlbel Oct 25 '23 at 22:53
  • I don't understand how it reaches \mmzAbort when it just includes the externalized pdf or anything that's done by autoscale autoid. This will not be executed when the PDF is just included. Is it part of the memo files? – Qrrbrbirlbel Oct 25 '23 at 23:32
  • @Qrrbrbirlbel It doesn't read it in that case. It only aborts when it would otherwise memoize the wrong scale. The idea is basically: compile once (write scale to aux; produce picture in document; abort memoization); compile again (write scale to aux; produce picture in document; memoize); compile again (include extern). It doesn't execute any of it in that case, so nothing gets written to the aux. But that's OK. If the picture's code changes, memoize has other ways to detect that. – cfr Oct 25 '23 at 23:51
  • @Qrrbrbirlbel I've no idea why the double //. I don't know why it uses that in my case either. What happens if you set an actual prefix? – cfr Oct 25 '23 at 23:54
  • '\mmzAbort is all?' Humph! Admittedly, I spent most of the hours trying to do something completely different ;). – cfr Oct 26 '23 at 00:03
  • Oh, yeah, I was trying something differently as well (as per Q's comments). I didn't even think to abort memoization if the pic is not ready at all. This is much smarter. Now I've got it that way with my library as well. – Qrrbrbirlbel Oct 26 '23 at 00:40
  • @Qrrbrbirlbel If feels like cheating. – cfr Oct 26 '23 at 00:46
  • The macro's name surely seems a bit forceful but maybe this is exactly what \mmzAbort is for – in contrast to the final \mmzUnmemoizable. I hope @SašoŽivanović gets a chance to weigh in. – Qrrbrbirlbel Oct 26 '23 at 01:24
2

My thought process went to the new memoize package as well, @cfr wrote a nice summary about it. And their comment is basically implemented here as well. Instead of the linked code, I'm using the ext.scalepicture library of my tikz-ext package and added two things:

  1. A stop condition (see /tikz/scale picture diff):

    When the difference of the sizes of the diagram between two runs is smaller than this length (in both directions), the picture is considered final and stable.

    There are two things that would make this process possibly neverending without it – and the inprecise nature of PGFMath doesn't help:

    1. Nodes.
    2. Line widths.

    Neither of these things scale with scale/xscale/yscale unless transform shape (nodes) or transform canvas (line widths) are used. And both we don't want to use usually.

    A picture like

    \tikz
      \draw (0cm, 0cm) rectangle (1cm, 1cm);
    

    doesn't have size 1cm × 1cm but (1cm + 0.4pt) × (1cm + 0.4pt) because half the line width gets added on all four sides. (The default line width is 0.4pt/thin.)

    The first image in the code below takes roughly five compilations because the nodes are bordering the bounding box.

    There are even images that can't be scaled at all, these two have the exact same size (either with the depecrated right of or positioning's right=of) – though we can implement a better node distance key then if we need.

    \tikz[scale=1, nodes=draw]\node (A) {ABC} node[right of=A] {DEF};
    \tikz[scale=2, nodes=draw]\node (A) {ABC} node[right of=A] {DEF};
    
  2. The \csname mmzAbort\endcsname macro is used then (the \csname makes this code compatible to documents without memoize)

    This allows us to not externalize a picture if it's not even ready.

    The externalize library does not provide this, its workflow doesn't even allow the functionality without another level of overhead work.

    If you change the diagram or the /tikz/scale picture diff value, Memoize will notice and will generate a new picture.

Note that the f of your f(x) gets cut off on the left side because this node doesn't scale but the space left of the y axis becomes so small due to scaling that the node is bigger than the \tkzClip.

Code

\documentclass{article}
\usepackage{memoize}
\usepackage{tikz}
\usetikzlibrary{ext.scalepicture}
\tikzset{
  scale picture diff/.initial=+0.05pt,
  scale picture diff/.value to context}
\makeatletter
\renewcommand*\tikzext@scalepicture@savepicturesize{% overwrite
  \pgf@process{\pgfpointdiff{\pgfpointanchor{current bounding box}{south west}}
                            {\pgfpointanchor{current bounding box}{north east}}}%
  \pgf@xa=\pgf@x \pgf@ya=\pgf@y
  \pgfutil@IfUndefined{tikzext@scalepicture@\pgfpictureid}{%
    \tikzext@scalepicture@write
  }{%
    \tikzext@ifapproxequalto{\pgf@x}{\tikzext@scalepicture@width}{}{%
      \tikzext@ifapproxequalto{\pgf@y}{\tikzext@scalepicture@height}{}{%
        \tikzext@scalepicture@write}}}%
  \let\tikzext@scalepicture@savepicturesize\relax}% only once per picture
\newcommand*\tikzext@scalepicture@write{%
  \immediate\write\pgfutil@auxout{\noexpand\expandafter\gdef\noexpand\csname
    tikzext@scalepicture@\pgfpictureid\endcsname{{\the\pgf@xa}{\the\pgf@ya}}}%
  \csname mmzAbort\endcsname}
% pgfmathapproxequalto checks against 0.0001, too precise for this
\newcommand*\tikzext@ifapproxequalto[2]{%
  \pgfmathsetlength\pgfutil@tempdima{\pgfkeysvalueof{/tikz/scale picture diff}}%
  \advance#1by-#2\relax \ifdim#1<0pt #1=-#1\fi
  \ifdim#1<\pgfutil@tempdima\relax \expandafter\pgfutil@firstoftwo
                             \else \expandafter\pgfutil@secondoftwo \fi}
\makeatother

\usepackage{tikz,tkz-fct} \begin{document} \rule{4cm}{4pt}

\begin{tikzpicture}[picture width=4cm, nodes=draw, ultra thick] \node {ABC}; \node at (2,1) {DEF}; \end{tikzpicture}

\rule{.3\columnwidth}{.4pt}

\begin{tikzpicture}[picture width=.3\columnwidth] \tkzInit[xmin=-1,xmax=3,ymin=-1,ymax=1.2] \tkzDrawX\tkzDrawY\tkzClip \tkzFct[thick,domain=0.55:5]{(\x\x+\x-1)/(\x*3)} \tkzDefPointByFct(1.39)\tkzGetPoint{A}\tkzDrawPointsize=3 \draw[dotted,thick] (1.39,0) -- (1.39, 0.86) -- (0,0.86) node[left] {( f(x) )}; \draw[thick] (1.39,-0.1) node[below] {( x )} -- (1.39,0.1); %Annotations \draw[<->] (1,-0.35) -- (2,-0.35); \node at (1.5,-0.45){(A)}; \draw[dashed] (1, 0) -- (1, 1) -- (0, 1); \draw[dashed] (2, 0) -- (2, 0.625) -- (0, 0.625); \draw[<->] (-0.6,0.625) -- (-0.6,1); \end{tikzpicture}

\tikz[scale=1, nodes=draw]\node (A) {ABC} node[right of=A] {DEF}; \tikz[scale=2, nodes=draw]\node (A) {ABC} node[right of=A] {DEF}; \end{document}

Output

enter image description here

Qrrbrbirlbel
  • 119,821