diff --git a/pd/doc/5.reference/declare-help.pd b/pd/doc/5.reference/declare-help.pd
index 046719bc022aaf71fbf4b7d3f2ca1880d9797be6..798c72b81b51aa47fa99181b5dfb239b5480835f 100644
--- a/pd/doc/5.reference/declare-help.pd
+++ b/pd/doc/5.reference/declare-help.pd
@@ -1,6 +1,10 @@
 #N canvas 426 36 555 619 10;
 #X declare;
-#X declare;
+#X declare -path mylibs;
+#X declare -lib mylib;
+#X declare -stdpath mylibs;
+#X declare -stdlib mylib;
+#X declare -zoom 1;
 #X obj 0 595 cnv 15 552 21 empty \$0-pddp.cnv.footer empty 20 12 0
 14 -228856 -66577 0;
 #X obj 0 0 cnv 15 552 40 empty \$0-pddp.cnv.header declare 3 12 0 18
@@ -53,7 +57,6 @@ calling patch as well as the abstraction.);
 #X text 98 561 BUG: The name "-stdpath" is confusing \, as it has a
 quite different effect from "-stdpath" on the pd command line.;
 #X text 11 23 set environment for loading patch;
-#X text 139 69 NEEDS AN EXAMPLE;
 #X text 165 185 - the [declare] object adds one or more directories
 to the search path \, and/or pre-loads one or more libraries ("extensions")
 to Pd in preparation for opening the patch from a file. Usage is "declare
@@ -66,3 +69,33 @@ you can get it using "declare -stdpath extra/zexy".;
 #X text 80 185 n) symbol atom;
 #X obj 4 597 pddp/pddplink all_about_help_patches.pd -text Usage Guide
 ;
+#X obj 40 52 declare -path mylibs;
+#X obj 240 52 declare -lib mylib;
+#X obj 40 76 declare -stdpath mylibs;
+#X obj 240 76 declare -stdlib mylib;
+#N canvas 480 189 445 363 Patch_local 0;
+#X obj 1 1 cnv 15 437 20 empty \$0-pddp.cnv.subheading empty 3 12 0
+14 -204280 -1 0;
+#X text 7 1 [declare] Patch-local options;
+#X obj 10 40 declare -zoom 1;
+#X text 133 40 turns on load/restore of zoom levels in a patch;
+#X text 10 120 * [declare -zoom 0/1] turns save/restore of zoom levels
+on or off. This overrides the corresponding global option in the GUI
+preferences. Please note that this does *not* actually set the zoom
+level itself \, it merely enables or disables storing and retrieving
+that setting in a patch., f 69;
+#X text 10 70 [declare] also offers the following special (and experimental)
+patch-local option which overrides the corresponding global flag in
+the current patch (including all its one-off subpatches):, f 69;
+#X text 10 200 CAVEAT: This option is only available in Purr Data \,
+it is NOT supported by vanilla Pd (which has no way of configuring
+the zoom behavior)., f 69;
+#X text 10 250 The -zoom option is intended mainly for classroom use
+\, e.g. \, when projecting patches on a whiteboard. It ensures that
+the patch is displayed at the preset zoom level by default \, even
+if the global zoom save/load option is disabled in the preferences.
+If you abuse this option to enforce unusually high or low zoom levels
+\, you take away precious zoom levels from the user. So please use
+this option with due skill \, care \, and diligence \, or better don't
+use it at all., f 69;
+#X restore 221 597 pd Patch_local;
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index abd50f2bcc36ab516656aa2d9a096607759ac4d3..15bbca59a945d02fd6c78803c762e34910d35ba9 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -412,7 +412,8 @@ t_symbol *canvas_field_templatesym; /* for "canvas" data type */
 t_word *canvas_field_vec;           /* for "canvas" data type */
 t_gpointer *canvas_field_gp;        /* parent for "canvas" data type */
 
-static int calculate_zoom(t_float zoom_hack)
+// This is also used in g_editor.c, so make sure to keep it non-static.
+int calculate_zoom(t_float zoom_hack)
 {
   // This gives back the zoom level stored in the patch (cf. zoom_hack
   // in g_readwrite.c). Make sure to round this value to handle any rounding
@@ -461,6 +462,25 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
         canvas_addtolist(x);
     /* post("canvas %zx, owner %zx", x, owner); */
 
+    /* This records the zoom level obtained from the patch (cf. zoom_hack in
+       g_readwrite.c, and the calculate_zoom() function above), so that the
+       actual zoom level can be calculated later in a lazy fashion if needed.
+       Initially zero, the actual value is extracted from the canvas arguments
+       below. If loading/saving zoom is enabled globally (sys_zoom != 0) then
+       we can apply the zoom factor right away and we are done (gl_zoom_hack
+       stays zero in that case).
+
+       Otherwise, we record the value so that we can apply the zoom later when
+       a [declare -zoom 1] object in the patch calls for it. (In the case of a
+       one-off subpatch, this may actually happen much later at the time the
+       subpatch window is first mapped; see the comment preceding
+       canvas_calculate_zoom() in g_editor.c for further explanation.)
+
+       Note that this 'declare' option is patch-local and will make sure that
+       the recorded zoom level is applied even if loading/saving zoom is
+       globally disabled (sys_zoom == 0). */
+    x->gl_zoom_hack = 0;
+
     if (argc == 5)  /* toplevel: x, y, w, h, font */
     {
         t_float zoom_hack = atom_getfloatarg(3, argc, argv);
@@ -472,6 +492,9 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
         zoom_hack -= height;
         if (sys_zoom && zoom_hack > 0)
             zoom = calculate_zoom(zoom_hack);
+        else
+            // record the zoom hack for later, see comment above
+            x->gl_zoom_hack = zoom_hack;
     }
     else if (argc == 6)  /* subwindow: x, y, w, h, name, vis */
     {
@@ -485,6 +508,8 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
         zoom_hack -= height;
         if (sys_zoom && zoom_hack > 0)
             zoom = calculate_zoom(zoom_hack);
+        else
+            x->gl_zoom_hack = zoom_hack;
     }
         /* (otherwise assume we're being created from the menu.) */
 
@@ -555,6 +580,7 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     x->gl_edit_save = 0;
     x->gl_font = sys_nearestfontsize(font);
     x->gl_zoom = zoom;
+    x->gl_zoomflag = sys_zoom;
     pd_pushsym(&x->gl_pd);
 
     x->u_queue = canvas_undo_init(x);
@@ -612,6 +638,7 @@ t_glist *glist_addglist(t_glist *g, t_symbol *sym,
     t_float x1, t_float y1, t_float x2, t_float y2,
     t_float px1, t_float py1, t_float px2, t_float py2)
 {
+    extern int sys_zoom;
     static int gcount = 0;
     int zz;
     int menu = 0;
@@ -663,6 +690,8 @@ t_glist *glist_addglist(t_glist *g, t_symbol *sym,
     x->gl_font =  (canvas_getcurrent() ?
         canvas_getcurrent()->gl_font : sys_defaultfont);
     x->gl_zoom = 0;
+    x->gl_zoom_hack = 0;
+    x->gl_zoomflag = sys_zoom;
     x->gl_screenx1 = x->gl_screeny1 = 0;
     x->gl_screenx2 = 450;
     x->gl_screeny2 = 300;
@@ -2907,6 +2936,21 @@ void canvas_declare(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
             canvas_stdlib(e, atom_getsymbolarg(i+1, argc, argv)->s_name);
             i++;
         }
+        else if ((argc > i+1) && !strcmp(flag, "-zoom"))
+        {
+            x->gl_zoomflag = atom_getfloatarg(i+1, argc, argv) != 0;
+            if (x->gl_zoomflag && x->gl_zoom_hack) {
+              x->gl_zoom = calculate_zoom(x->gl_zoom_hack);
+              // we only want to calculate this once, so reset the zoom_hack
+              // value to ensure that we don't do it again
+              x->gl_zoom_hack = 0;
+            } else if (!x->gl_zoomflag) {
+              // reset the zoom level in case we already calculated it
+              // previously
+              x->gl_zoom = 0;
+            }
+            i++;
+        }
         // ag: Handle the case of an unrecognized option argument (presumably
         // a float).
         else if (!*flag) {
diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h
index cde9837014ad4a16ab8b7ca2eb8275062a2b1f7f..80a5840e6d6b7087b88920d79c1bf112815cef90 100644
--- a/pd/src/g_canvas.h
+++ b/pd/src/g_canvas.h
@@ -225,6 +225,8 @@ struct _glist
     t_symbol *gl_name;          /* symbol bound here */
     int gl_font;                /* nominal font size in points, e.g., 10 */
     int gl_zoom;                /* current zoom level (-7..8) */
+    t_float gl_zoom_hack;           /* stored zoom_hack used by declare */
+    int gl_zoomflag;                /* canvas-local zoom flag (0/1) */
     struct _glist *gl_next;         /* link in list of toplevels */
     t_canvasenvironment *gl_env;    /* root canvases and abstractions only */
     unsigned int gl_havewindow:1;   /* true if we own a window */
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index f8db767e81d8a2841cdfc9e7d315e4e99fe40bb6..e744212b79f8cee2b9eea4e8520fee44aaf762d7 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -2828,6 +2828,48 @@ void canvas_init_menu(t_canvas *x)
     gui_vmess("gui_menu_font_set_initial_size", "xi", x, x->gl_font);
 }
 
+/* ag: Calculate the proper zoom factor for a (sub)patch.
+
+   We may need to pick up the slack from 'declare -zoom 1' here, which
+   calculates zoom factors lazily from their "zoom_hack" values stored in the
+   (sub)patch headers. This will only be necessary if we're mapping a one-off
+   subpatch, because 'declare' in a subpatch always gets invoked on the
+   root patch or abstraction containing that subpatch.
+
+   So in this case the zoom factor (or rather its "zoom_hack" value) will get
+   stored at its proper location in the subpatch canvas (which 'declare -zoom'
+   never sees, thus it can't update the zoom level right there), while the
+   zoomflag gets set by 'declare -zoom' in the root/abstraction.
+
+   Catch 22. :) We solve this by looking up the zoomflag in the root, and
+   calculating the proper zoom level lazily in canvas_vis(), when the window
+   first gets mapped and we have both the zoomflag and zoom_hack values
+   readily available. */
+
+static int canvas_calculate_zoom(t_canvas *x)
+{
+  // Check whether the zoom_hack value of canvas x is nonzero. Otherwise the
+  // canvas either has no zoom level, or we already calculated it previously,
+  // and we simply return the recorded value.
+  if (x->gl_zoom_hack) {
+    t_glist *gl = x;
+    // in a subpatch, look up the enclosing root or abstraction
+    while (!gl->gl_env && gl->gl_owner) {
+      gl = gl->gl_owner;
+    }
+    // If the root zoomflag is set, employ calculate_zoom() from g_canvas.c
+    // to calculate the zoom level from the zoom_hack value.
+    if (gl->gl_zoomflag) {
+      extern int calculate_zoom(t_float zoom_hack);
+      x->gl_zoom = calculate_zoom(x->gl_zoom_hack);
+      // Now that we've updated the value, reset the zoom_hack so that we
+      // don't do that calculation again.
+      x->gl_zoom_hack = 0;
+    }
+  }
+  return x->gl_zoom;
+}
+
 extern int sys_snaptogrid; /* whether we are snapping to grid or not */
 extern int sys_gridsize;
 
@@ -2903,7 +2945,7 @@ void canvas_vis(t_canvas *x, t_floatarg f)
                 geobuf,
                 sys_snaptogrid,
                 sys_gridsize,
-                x->gl_zoom,
+                canvas_calculate_zoom(x),
                 x->gl_edit,
                 x->gl_name->s_name,
                 canvas_getdir(x)->s_name,
diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c
index ae4ee02abf9c5ebf17ed69add8a053982bd7d3d8..3f477c9e4fa229c74a4e3c9296fa0431a3f62d90 100644
--- a/pd/src/g_readwrite.c
+++ b/pd/src/g_readwrite.c
@@ -786,7 +786,13 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b)
         patchsym = atom_getsymbolarg(name_index,
             binbuf_getnatom(bz), binbuf_getvec(bz));
         binbuf_free(bz);
-        if (sys_zoom && x->gl_zoom != 0) {
+        // look up the enclosing root or abstraction for the zoomflag value
+        // (this is where 'declare -zoom' stores it)
+        t_glist *gl = x;
+        while (!gl->gl_env && gl->gl_owner) {
+          gl = gl->gl_owner;
+        }
+        if (gl->gl_zoomflag && x->gl_zoom != 0) {
           // This uses the hack described above to store the zoom factor in
           // the fractional part of the windows height parameter. Note that
           // any of the other canvas geometry parameters would do just as
@@ -818,7 +824,7 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b)
     else 
     {
         // See above.
-        if (sys_zoom && x->gl_zoom != 0) {
+        if (x->gl_zoomflag && x->gl_zoom != 0) {
           binbuf_addv(b, "ssiiifi;", gensym("#N"), gensym("canvas"),
                       (int)(x->gl_screenx1),
                       (int)(x->gl_screeny1),