.. Copyright (c) 2026 Tobias Erbsland - Erbsland DEV. https://erbsland.dev SPDX-License-Identifier: Apache-2.0 .. index:: single: Text Rendering; Loaders single: Layouts; File System Loader single: Layouts; Resource Loader single: Layouts; Custom Loader ********************** Loading Render Layouts ********************** A render call names a layout, but it does not need to know where that layout is stored. A :cpp:class:`Loader ` connects the logical name to source text. You can keep layouts in an editable directory, compile them into the application, or supply them from another source that your application owns. This page shows each approach and explains how several loaders can work together. Logical Layout Names ==================== The environment passes a validated logical name such as ``lessons/tempo.txt`` to its loaders. Names use lowercase ASCII letters, digits, ``-``, ``_``, ``.``, and ``/``. They cannot start or end with a slash, contain empty path components, or exceed 200 code points. The name is the same whether the source lives on disk or in a compiled resource set. Includes and inherited layouts use these names too, so a layout tree can move between storage choices without editing its references. A loader returns a :cpp:class:`LayoutSource ` when it finds the name, or ``std::nullopt`` when it does not. The environment tries the next loader after a miss. An actual loading error is reported as :cpp:class:`RenderError ` during rendering; it is not treated as a miss. For a custom loader, prefer ``RenderError`` with category ``Load`` and set the requested name on its ``RenderErrorContext``. Existing specific runtime exceptions such as ``PathError`` may also propagate; the environment wraps them as loading errors and preserves the cause. Loading Layouts with FileSystemLoader ===================================== :cpp:class:`FileSystemLoader ` looks below one or more existing, absolute directories. It maps ``lessons/tempo.txt`` to the corresponding relative file below each directory. When given several directories, it searches them in the order supplied. The following demo uses two directories with the same layout name; the first one wins. .. erbsland-demo:: :source: text/RenderLayouts/LoaderExamples.cpp :show: f:fileLoader :exec: text/render_layouts --demo FileLoader :sha256: 63365fdaa3109de13a16de1d0a237c3641419d76603f6f937e26219a22482e26 .. code-block:: cpp void fileLoader() { const auto first = el::Path::systemTempDirectoryOrThrow().operations().createTempDirectoryOrThrow(); const auto second = el::Path::systemTempDirectoryOrThrow().operations().createTempDirectoryOrThrow(); (first->path() / "lesson.txt"_el).content().writeTextOrThrow("İlk alıştırma: {{ pattern }}\n"_el); (second->path() / "lesson.txt"_el).content().writeTextOrThrow("İkinci alıştırma: {{ pattern }}\n"_el); const auto environment = el::render::Environment::create(); environment->addLayoutLoader( el::render::FileSystemLoader::create(el::util::List{first->path(), second->path()})); el::io::print(environment->render("lesson.txt"_el, el::render::Context{}.set("pattern"_el, "yavaş-hızlı"_el))); } .. erbsland-demo-run:: .. erbsland-ansi:: :escape-char: ␛ ␛[0mİlk alıştırma: yavaş-hızlı␛[0m .. erbsland-demo-end:: Use an absolute directory that your application knows, rather than depending on its current working directory. The loader only accepts regular files below its roots, does not traverse symbolic links below a root, and reads valid UTF-8 layout text. :cpp:class:`FileSystemLoaderOptions ` is accepted by the factory; it currently has no configurable settings. Loading Compiled Layouts with ResourceLoader ============================================ Compiled layouts are useful when the application ships a stable set of defaults. :cpp:class:`ResourceLoader ` resolves a name against an exact resource identifier and an optional path prefix. In this demo, the logical name ``note.txt`` becomes the resource key ``(render-layouts, tempo/note.txt)``. The resource is added to the demo target at build time: .. code-block:: cmake erbsland_core_add_resources( TARGET render_layouts DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/data" IDENTIFIER "render-layouts" RECURSIVE SUFFIXES ".txt" ) The runtime code still asks the environment for the logical name: .. erbsland-demo:: :source: text/RenderLayouts/LoaderExamples.cpp :show: f:resourceLoader :exec: text/render_layouts --demo ResourceLoader :sha256: 7b14d9ee90060d3346770a84d82860f3ce309d441124f1926ab8dc7b6aad3270 .. code-block:: cpp void resourceLoader() { const auto environment = el::render::Environment::create(); environment->addLayoutLoader(el::render::ResourceLoader::create("render-layouts"_el, "tempo"_el)); el::io::print(environment->render( "note.txt"_el, el::render::Context{}.set("pattern"_el, "yavaş-hızlı"_el).set("bpm"_el, 84))); } .. erbsland-demo-run:: .. erbsland-ansi:: :escape-char: ␛ ␛[0mÇalışma: yavaş-hızlı (84 BPM)␛[0m .. erbsland-demo-end:: The one-argument factory uses the application's resource manager. An overload accepts an explicit ``ResourcesConstPtr`` when resources come from a provider you supply; the loader retains that provider. Create an application-backed loader after the application is initialized. The optional prefix is a normalized relative path with ``/`` separators; it does not change the name passed to ``render()``. For the build-time resource workflow, see :doc:`/topics/resource/compiled_resources`. Loader Priorities and Local Overrides ===================================== Register more than one loader when a user-editable layout should take precedence over a compiled default. ``addLayoutLoader(loader, priority)`` searches larger priorities first; equal priorities keep registration order. For example, register a resource loader at the default priority, then a file-system loader with priority ``10``. If the file exists, it is used; otherwise, the resource loader may provide the layout. .. erbsland-demo:: :source: text/RenderLayouts/LoaderExamples.cpp :show: f:layeredLoaders :exec: text/render_layouts --demo LayeredLoaders :sha256: 4f1a2096cd2a225c4356851b9105787261730f92e4109c2270e0a9c6f6c236ff .. code-block:: cpp void layeredLoaders() { const auto directory = el::Path::systemTempDirectoryOrThrow().operations().createTempDirectoryOrThrow(); (directory->path() / "note.txt"_el).content().writeTextOrThrow("Yerel: {{ pattern }}\n"_el); const auto environment = el::render::Environment::create(); environment->addLayoutLoader(el::render::ResourceLoader::create("render-layouts"_el, "tempo"_el)); environment->addLayoutLoader(el::render::FileSystemLoader::create(directory->path()), 10); const auto context = el::render::Context{}.set("pattern"_el, "yavaş-hızlı"_el); el::io::print(environment->render("note.txt"_el, context)); el::io::print(environment->render("default.txt"_el, context)); } .. erbsland-demo-run:: .. erbsland-ansi:: :escape-char: ␛ ␛[0mYerel: yavaş-hızlı Varsayılan: yavaş-hızlı␛[0m .. erbsland-demo-end:: This choice is made for every named layout, including :doc:`included layouts ` and :doc:`inherited parents `. One local partial can therefore override its compiled counterpart without copying the entire layout tree. When automatic reload is enabled, the environment checks sources again at render time and picks up changed revisions. Implementing a Custom Layout Loader =================================== You can implement :cpp:class:`Loader ` for layouts held by an application service or generated from another store. The example below keeps one small layout in its loader. The class appears with the demo because it is the part you would adapt for your own source. .. erbsland-demo:: :source: text/RenderLayouts/CustomLoader.cpp :exec: text/render_layouts --demo CustomLoader :sha256: 9855f84e5b9b75007580dd87ce167508597cc8101c7df647fdd4b3507d178412 .. code-block:: cpp /// Supply a layout source from application-owned storage. /// /// A loader gets a validated logical name and returns a source only when it owns that name. This example /// has one stable layout; a service-backed loader would look it up and derive a revision from its content. /// @notest{Demo-only loader verified by the documentation executable.} class LessonLoader final : public el::render::Loader { public: [[nodiscard]] auto load(const el::String &layout) -> std::optional override { if (layout != "lesson.txt"_el) { return std::nullopt; } return el::render::LayoutSource{"Alıştırma: {{ pattern }}\n"_el, "lesson:built-in"_el, "1"_el}; } }; /// Supply layouts from a source owned by the application. /// /// A custom Loader returns a LayoutSource for a known name and std::nullopt for other names. Its origin is /// used in diagnostics, while its revision identifies changes when automatic reload is enabled. /// @notest{Demo function verified by the documentation executable.} void customLoader() { const auto environment = el::render::Environment::create(); environment->addLayoutLoader(std::make_shared()); el::io::print(environment->render("lesson.txt"_el, el::render::Context{}.set("pattern"_el, "yavaş-hızlı"_el))); } .. erbsland-demo-run:: .. erbsland-ansi:: :escape-char: ␛ ␛[0mAlıştırma: yavaş-hızlı␛[0m .. erbsland-demo-end:: ``load()`` receives a name that the environment has already validated. Return ``std::nullopt`` only when your source has no layout by that name. A present source needs its exact UTF-8 text, a stable origin for diagnostics, and a revision token that changes when its content changes. The loader may be called from concurrent renders, so its ``load()`` implementation must be thread-safe. For the environment settings that accompany these loaders, continue with :doc:`configuring_the_render_environment`. The :doc:`rendering reference ` describes the values supplied to a loaded layout.