<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://sprnjt.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://sprnjt.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-09-05T09:34:04+00:00</updated><id>https://sprnjt.github.io/feed.xml</id><title type="html">blank</title><subtitle>Applied Scientist 1 at Glance, working on LLMs, computer vision, and generative AI. </subtitle><entry><title type="html">Repurposing Text-to-Image Models as Image Editing Models</title><link href="https://sprnjt.github.io/blog/2026/repurposing-text-to-image-models-as-image-editing-models/" rel="alternate" type="text/html" title="Repurposing Text-to-Image Models as Image Editing Models"/><published>2026-08-04T16:45:14+00:00</published><updated>2026-08-04T16:45:14+00:00</updated><id>https://sprnjt.github.io/blog/2026/repurposing-text-to-image-models-as-image-editing-models</id><content type="html" xml:base="https://sprnjt.github.io/blog/2026/repurposing-text-to-image-models-as-image-editing-models/"><![CDATA[<p><strong><em>This work was carried out by </em></strong><a href="https://www.linkedin.com/in/suparnojit/"><strong><em>Suparnojit Sarkar</em></strong></a><strong><em>, during his Machine Learning Engineer Internship at Glance under the mentorship of </em></strong><a href="https://www.linkedin.com/in/vajra-ganeshkumar/"><strong><em>Vajra Ganesh</em></strong></a><strong><em>.</em></strong></p> <h3>Introduction</h3> <p>Text-to-image diffusion models have revolutionized image generation. Given a prompt such as <em>“a person wearing a red jacket standing on a beach,”</em> they can synthesize photorealistic images from pure noise.</p> <p>Image editing, however, poses a different challenge. Rather than generating a new image, the goal is to modify only the requested attribute while preserving everything else. For example, changing a red jacket to a black shirt should leave the person’s identity, pose, lighting, composition, and background untouched.</p> <p>Conventional text-to-image models are not designed for this task. Since they are trained to generate images from scratch, every inference starts from random noise and follows a denoising trajectory toward an image matching the prompt. They have no inherent mechanism to preserve the contents of an existing image, so naïvely prompting them with an editing instruction often produces an entirely new image instead of a faithful edit.</p> <blockquote>Can we transform a text-to-image model into an image editor without retraining it?</blockquote> <p>In this article, we answer the above question by showing how a pre-trained text-to-image model can be repurposed as an image editor entirely at inference time. Rather than fine-tuning the model or introducing additional trainable components, we leverage inference-time techniques — FlowEdit and FlowAlign — to guide the latent generation trajectory. This enables instruction-guided image editing while preserving the source image, all without modifying the underlying Z-Image model.</p> <p>To understand how this works, we’ll first look at how Z-Image generates images and why direct editing approaches often fail. We’ll then see how FlowEdit and FlowAlign overcome these limitations, enabling high-fidelity, instruction-guided image editing without any additional training.</p> <h3>Understanding Z-Image</h3> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GwPWvDEuQebpvIMa7KItyQ.png"/><figcaption>Z-Image Architecture. Image taken from [3]</figcaption></figure> <h4>How Z-Image Generates Images</h4> <p>At a high level, <strong>Z-Image</strong> consists of three components:</p> <ul><li><strong>Qwen3–4B</strong>, which converts the text prompt into contextual language embeddings.</li><li><strong>Flux VAE</strong>, which defines the latent space for image generation and decodes the final latent into an RGB image.</li><li><strong>S3-DiT (Scalable Single-Stream Diffusion Transformer)</strong>, which progressively transforms a noisy latent into one representing the target image.</li></ul> <h4>From Text to Image</h4> <p>Given a prompt such as <em>“A person wearing a red jacket standing on a beach,”</em> Qwen3–4B first produces contextual text embeddings that condition the image generation process.</p> <p>Unlike autoregressive language models, Z-Image generates directly in the Flux VAE latent space. Since there is no input image during inference, generation begins from a randomly sampled Gaussian noise latent. This latent, together with the text embeddings, is then fed into S3-DiT.</p> <h4>Flow Matching</h4> <p>Z-Image is trained using <strong>Flow Matching</strong>, where the model predicts a <strong>velocity</strong> — the direction in which the current latent should move — rather than the noise itself. Starting from random noise, S3-DiT repeatedly predicts these velocities, and a flow solver integrates them over multiple timesteps, gradually transforming the latent into one that matches the text prompt.</p> <p>The final latent is then decoded by the Flux VAE decoder to produce the output image.</p> <h3>Our First Attempts at Image Editing</h3> <p>Having understood how Z-Image generates images, the next challenge was adapting it for image editing.</p> <p>A natural first idea was to provide the model with the source image alongside the editing instruction — the image would specify what to preserve, while the text would specify what to change. Before modifying the generation process itself, we explored several conditioning strategies.</p> <h4>Attempt 1: Conditioning on the Reference Image</h4> <p>We first encoded the source image using <strong>SigLIP 2 </strong>[4], producing visual tokens that were concatenated with the text embeddings from Qwen3–4B before being passed to S3-DiT. Classifier-free guidance (CFG) was then used to encourage the requested edit.</p> <p>[ Edit Instruction | Reference Image Features ]</p> <p>While the model utilized both inputs, the results were inconsistent. Beyond the requested edit, it often altered the subject’s identity, pose, composition, or background. The core issue is that conditioning informs the model <em>what</em> information is available, but not <em>what must remain unchanged</em>. As a result, the image features and editing instruction compete during generation, and CFG can amplify unintended changes.</p> <h4>Attempt 2: Converting the Image into Text</h4> <p>Next, we described the source image using <strong>Qwen3-VL </strong>[5] and conditioned Z-Image on both the generated caption and the editing instruction.</p> <p>[ Edit Instruction | Source Image Caption ]</p> <p>Although this unified both inputs into a single modality, captions are inherently lossy. They capture high-level semantics but omit fine-grained details such as facial identity, pose, spatial layout, lighting, and texture. Consequently, the generated images preserved the overall scene but failed to faithfully reproduce the source image.</p> <h4>Attempt 3: Image-Guided Generation</h4> <p>Finally, we removed the editing instruction entirely and conditioned the model only on the <strong>SigLIP 2</strong> image features, while still starting generation from random noise.</p> <p>The model produced plausible variations of the reference image, preserving its overall subject and style, but the pose, viewpoint, and composition frequently changed. It could generate similar images, but not controlled edits.</p> <h3>The Common Failure</h3> <p>Despite their differences, all three approaches relied on the same assumption: <strong><em>better conditioning should lead to better editing</em></strong>.</p> <p>In practice, the bottleneck was not the conditioning but the <strong>generation trajectory</strong>. Once generation begins from random noise, nothing constrains the latent to remain close to the source image. Conditioning influences <em>what</em> the model should generate, but not <em>how</em> the latent evolves over time.</p> <p>This shifts the problem from improving the input to controlling the generation process itself.</p> <blockquote>Can we directly modify the generation trajectory instead?</blockquote> <p>That question leads us to <strong>FlowEdit </strong>[1].</p> <h3>FlowEdit: Editing the Trajectory Instead of the Conditioning</h3> <p>Our earlier attempts all shared the same limitation: they improved the <strong>conditioning</strong>, but left the <strong>generation process</strong> unchanged. Although the model had access to the source image through image features or captions, the latent trajectory could still drift away once denoising began.</p> <p>FlowEdit takes a different approach. Instead of providing more information to the model, it directly modifies <strong>how the latent evolves</strong> during generation.</p> <h4>Thinking in Terms of Velocities</h4> <p>Recall that Z-Image is trained using <strong>Flow Matching</strong>, where the model predicts a <strong>velocity</strong> at every timestep — the direction in which the current latent should move.</p> <p>Consider two prompts describing the same scene:</p> <ul><li><strong>Source:</strong> <em>“A person wearing a black shirt standing on a beach.”</em></li><li><strong>Target:</strong> <em>“A person wearing a red jacket standing on a beach.”</em></li></ul> <p>At the same latent point, we query the model twice: once with the source prompt and once with the target prompt. The difference between the two predicted velocities represents the <strong>semantic edit direction</strong> — the change required to transform the source concept into the target one.</p> <pre>v_src = model(z_t, source prompt)<br />v_tgt = model(z_t, target prompt)</pre> <h4>Steering the Trajectory</h4> <p>The updated velocity is computed as:</p> <pre>v_edit = v_src + α · (v_tgt - v_src)</pre> <p>Here, <strong>α</strong> controls the edit strength.</p> <p>Small values of α keep the trajectory close to the source image, while larger values produce stronger edits. In our example, the source velocity preserves the person’s identity, pose, lighting, and background, while the velocity difference gradually replaces the black shirt with a red jacket.</p> <p>Instead of regenerating the image, FlowEdit performs a sequence of small semantic updates throughout the denoising process.</p> <h4>Why This Works Better</h4> <p>FlowEdit shifts the focus from <strong>conditioning</strong> to <strong>trajectory control</strong>. Rather than relying on the model to infer what should be preserved, it explicitly edits the predicted velocity at every flow step, making the generation process itself editable.</p> <h3>Results:</h3> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PRxiiQViH1sS3iJoxiok6A.png"/><figcaption>From left to right: the input image, the source and target instruction, and the image edited with FlowEdit.</figcaption></figure> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XvplMVDkMlHNy2W7zymdKw.png"/><figcaption>From left to right: the input image, the source and target instruction, and the image edited with FlowEdit.</figcaption></figure> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wDNiNQkHxsJLsIsgqFKc0w.png"/><figcaption>From left to right: the input image, the source and target instruction, and the image edited with FlowEdit.</figcaption></figure> <h4>But One Problem Remains</h4> <p>Although FlowEdit greatly improves controllability, it does not fully prevent trajectory drift. Strong editing prompts can gradually pull the latent away from the source image, causing unintended changes such as altered facial identity, pose, or background.</p> <p>In other words, FlowEdit provides the <strong>direction</strong> of the edit, but not an explicit mechanism to keep the edited trajectory close to the original one.</p> <p>This motivates <strong>FlowAlign </strong>[2], which continuously anchors the edited trajectory to the source trajectory throughout generation.</p> <h3>FlowAlign<strong><em>: Anchoring the Trajectory to the Source Image</em></strong></h3> <p><strong>FlowAlign</strong> addresses the drawbacks of FlowEdit by continuously anchoring the edited trajectory to the source image throughout generation.</p> <h4>Constructing a Reference Trajectory</h4> <p>Recall that during editing we already have access to the source image. Unlike text-to-image generation, where inference begins from random noise, image editing begins with a known image that can be encoded into the same latent space used by Z-Image.</p> <p>Let z_src be the clean source latent, x_t the current edited latent, σ the current noise level, and ε fresh noise.</p> <pre>q_t = (1 - σ) · z_src + σ · ε</pre> <p>q_t represents the noisy source latent at the current timestep.</p> <p>The latent <strong>q_t</strong> can be thought of as the <strong>reference point</strong> for the source image at the current timestep. Instead of comparing the edited latent to the fully clean source image, FlowAlign compares two latents that exist at the same noise level, making the comparison meaningful throughout the denoising process.</p> <h4>Aligning the Edited Trajectory</h4> <p>The edited latent, denoted by <strong>x_t</strong>, may already have drifted away from the source trajectory due to the semantic edit introduced by FlowEdit.</p> <p>Before computing velocity updates, FlowAlign first aligns the edited latent with the source trajectory as follows:</p> <pre>p_t = x_t + q_t - z_src</pre> <p>This simple transformation ensures that both trajectories are represented at a comparable point in the diffusion process before the model predicts their respective velocities.</p> <h4>Predicting Source and Target Velocities</h4> <p>FlowAlign then queries the transformer using both the source and target prompts.</p> <p>Using the aligned latent <strong>p_t</strong>, the model predicts</p> <ul><li>a velocity conditioned on the <strong>target prompt</strong>, and</li><li>a velocity conditioned on the <strong>source prompt</strong>.</li></ul> <p>Classifier-Free Guidance (CFG) combines these predictions to produce the final editing velocity:</p> <pre>v_pc = transformer(p_t, target prompt)<br />v_pn = transformer(p_t, source prompt)<br />v_p  = v_pn + cfg_scale · (v_pc - v_pn)<br />v_q  = transformer(q_t, source prompt)</pre> <p>At the same time, the transformer predicts another velocity <strong>v_q</strong> using the noisy source latent <strong>q_t </strong>and the original source prompt.</p> <p>Intuitively,</p> <ul><li><strong>v_p</strong> describes where the <strong>edited image wants to go</strong>, while</li><li><strong>v_q</strong> describes how the <strong>original image would naturally evolve</strong>.</li></ul> <h4>Preventing Trajectory Drift</h4> <p>The final update contains two separate components.</p> <p>The first is the familiar <strong>FlowEdit</strong> update, which moves the latent toward satisfying the editing instruction.</p> <p>The second is an <strong>alignment term</strong>, which pulls the edited trajectory back toward the source trajectory whenever the two begin to diverge.</p> <p>The complete update is:</p> <pre><br />x_t_new = x_t<br />        + (σ_next - σ) · (v_p - v_q)<br />        + λ · (q_t - σ · v_q - p_t + σ · v_p)</pre> <p>The parameter λ controls the strength of this alignment.</p> <ul><li>A <strong>small </strong>λ allows the trajectory to move more freely, producing stronger edits but increasing the risk of structural changes.</li><li>A <strong>large </strong>λ keeps the edited trajectory tightly anchored to the source image, preserving identity and composition but making edits more conservative.</li></ul> <p>This creates a natural trade-off between <strong>edit strength</strong> and <strong>content preservation</strong>, allowing the user to adjust the behavior depending on the task.</p> <h3>Results:</h3> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qm94bOCnJHjQMk5X-2buiA.png"/><figcaption>From left to right: the input image, the editing instruction, and the image edited with FlowAlign.</figcaption></figure> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zWmBu9mYTAw4Qn5nQQRBFQ.png"/><figcaption>From left to right: the input image, the editing instruction, and the image edited with FlowAlign.</figcaption></figure> <h3>Building Z-Image Edit</h3> <p>By combining FlowEdit and FlowAlign, Z-Image can be repurposed into a training-free image editor entirely at inference time. FlowEdit introduces the desired semantic change, while FlowAlign preserves the source image by anchoring the latent trajectory. The complete pipeline consists of four steps.</p> <h4>Step 1: Encode the Source Image</h4> <p>The source image is encoded by the <strong>Flux VAE</strong> into a latent representation, providing the starting point for editing.</p> <h4>Step 2: Define the Edit</h4> <p>The model receives two prompts: a <strong>source prompt</strong> describing the original image and a <strong>target prompt</strong> describing the desired edit.</p> <p><strong>Source:</strong> <em>“A person wearing a black shirt standing on a beach.”</em></p> <p><strong>Target:</strong> <em>“A person wearing a red jacket standing on a beach.”</em></p> <p>Ideally, the prompts differ only in the attribute to be modified.</p> <h4>Step 3: Guided Flow Updates</h4> <p>At each flow timestep, Z-Image Edit:</p> <ol><li>Reconstructs the noisy source latent for the current timestep.</li><li>Predicts source and target velocities using S3-DiT.</li><li>Applies <strong>FlowEdit</strong> to compute the semantic edit direction.</li><li>Applies <strong>FlowAlign</strong> to keep the trajectory aligned with the source image.</li></ol> <p>The latent is therefore updated toward the target concept while remaining faithful to the original scene.</p> <h4>Step 4: Decode the Result</h4> <p>After the final flow step, the edited latent is decoded by the <strong>Flux VAE</strong> to produce the final image. Since all modifications occur during inference, no additional training or fine-tuning is required.</p> <h4>Controlling the Edit</h4> <p>Z-Image Edit exposes two key controls:</p> <ul><li><strong>Edit guidance</strong>, which determines how strongly the model follows the target instruction.</li><li><strong>Alignment strength (λ)</strong>, which controls how closely the edited trajectory stays aligned with the source image.</li></ul> <p>Together, these parameters balance <strong>edit strength</strong> and <strong>content preservation</strong>. Stronger alignment is generally preferred for localized edits (e.g., changing clothing color), while larger semantic changes may require stronger edit guidance.</p> <h3>Limitations</h3> <p>Although <strong>FlowEdit</strong> and <strong>FlowAlign</strong> enable training-free image editing with pre-trained Z-Image models, they have important limitations. FlowEdit can suffer from structural drift, identity loss, and poor source preservation, particularly for large semantic edits. FlowAlign mitigates these issues by anchoring the editing trajectory to the source image, but it requires multiple flow evaluations and careful hyperparameter tuning, increasing computational cost and reducing robustness across diverse editing tasks.</p> <p>These challenges motivate a future dedicated image editing model trained from scratch on editing pairs, which could achieve higher edit fidelity, stronger instruction following, and better identity preservation than inference-time approaches.”</p> <h3>Conclusion</h3> <p>Transforming a text-to-image model into an image editor requires more than conditioning on a reference image. As we saw, conditioning alone cannot prevent the generation trajectory from drifting away from the source image.</p> <p><strong>Z-Image Edit</strong> addresses this by controlling the generation process itself. <strong>FlowEdit</strong> steers the latent toward the desired edit, while <strong>FlowAlign</strong> keeps the trajectory anchored to the source image, enabling high-quality, instruction-guided editing entirely at inference time — without retraining the underlying model.</p> <p>The key insight is that effective image editing comes not from changing the model itself, but from controlling its latent generation trajectory.</p> <h3>References</h3> <p>[1] V. Kulikov, M. Kleiner, I. Huberman-Spiegelglas, and T. Michaeli, <em>FlowEdit: Inversion-Free Text-Based Editing Using Pre-Trained Flow Models</em>, Proc. IEEE/CVF International Conference on Computer Vision (ICCV), 2025, pp. 19721–19730.</p> <p>[2] J. Kim, Y. Hong, J. Park, and J. C. Ye, <em>FlowAlign: Trajectory-Regularized, Inversion-Free Flow-based Image Editing</em>, International Conference on Learning Representations (ICLR), 2026.</p> <p>[3] Z-Image Team, H. Cai, S. Cao, R. Du, P. Gao, S. Hoi, <em>et al</em>., <em>Z-Image: An Efficient Image Generation Foundation Model with Single-Stream Diffusion Transformer</em>, arXiv:2511.22699, 2025.</p> <p>[4] Tschannen, M., Gritsenko, A., Wang, X., Naeem, M. F., Alabdulmohsin, I., Parthasarathy, N., … &amp; Zhai, X. (2025). Siglip 2: Multilingual vision-language encoders with improved semantic understanding, localization, and dense features. <em>arXiv preprint arXiv:2502.14786</em>.</p> <p>[5] Bai, S., Cai, Y., Chen, R., Chen, K., Chen, X., Cheng, Z., … &amp; Zhu, K. (2025). Qwen3-vl technical report. <em>arXiv preprint arXiv:2511.21631</em>.</p> <p><img src="https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=5b54a6b2de00" width="1" height="1" alt=""/>&lt;hr&gt;&lt;p&gt;<a href="https://engg.glance.com/repurposing-text-to-image-models-as-image-editing-models-5b54a6b2de00">Repurposing Text-to-Image Models as Image Editing Models</a> was originally published in <a href="https://engg.glance.com">Glance</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.&lt;/p&gt;</p>]]></content><author><name></name></author><category term="data-science"/><category term="diffusion-models"/><category term="image-editing"/><category term="image-generation"/><category term="machine-learning"/><category term="medium"/></entry><entry><title type="html">a post with plotly.js</title><link href="https://sprnjt.github.io/blog/2025/plotly/" rel="alternate" type="text/html" title="a post with plotly.js"/><published>2025-03-26T14:24:00+00:00</published><updated>2025-03-26T14:24:00+00:00</updated><id>https://sprnjt.github.io/blog/2025/plotly</id><content type="html" xml:base="https://sprnjt.github.io/blog/2025/plotly/"><![CDATA[<p>This is an example post with some <a href="https://plotly.com/javascript/">plotly</a> code.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">plotly
</span><span class="sb">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "type": "scatter"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [16, 5, 11, 9],
      "type": "scatter"
    }
  ]
}</span>
<span class="p">```</span>
</code></pre></div></div> <p>Which generates:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "type": "scatter"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [16, 5, 11, 9],
      "type": "scatter"
    }
  ]
}
</code></pre> <p>Also another example chart.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">plotly
</span><span class="sb">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "mode": "markers"
    },
    {
      "x": [2, 3, 4, 5],
      "y": [16, 5, 11, 9],
      "mode": "lines"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [12, 9, 15, 12],
      "mode": "lines+markers"
    }
  ],
  "layout": {
    "title": {
      "text": "Line and Scatter Plot"
    }
  }
}</span>
<span class="p">```</span>
</code></pre></div></div> <p>This is how it looks like:</p> <pre><code class="language-plotly">{
  "data": [
    {
      "x": [1, 2, 3, 4],
      "y": [10, 15, 13, 17],
      "mode": "markers"
    },
    {
      "x": [2, 3, 4, 5],
      "y": [16, 5, 11, 9],
      "mode": "lines"
    },
    {
      "x": [1, 2, 3, 4],
      "y": [12, 9, 15, 12],
      "mode": "lines+markers"
    }
  ],
  "layout": {
    "title": {
      "text": "Line and Scatter Plot"
    }
  }
}
</code></pre>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="charts"/><summary type="html"><![CDATA[this is what included plotly.js code could look like]]></summary></entry><entry><title type="html">a post with image galleries</title><link href="https://sprnjt.github.io/blog/2024/photo-gallery/" rel="alternate" type="text/html" title="a post with image galleries"/><published>2024-12-04T01:59:00+00:00</published><updated>2024-12-04T01:59:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/photo-gallery</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/photo-gallery/"><![CDATA[<p>The images in this post are all zoomable, arranged into different mini-galleries using different libraries.</p> <h2 id="lightbox2">Lightbox2</h2> <p><a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/></a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/></a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg" data-lightbox="roadtrip"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/></a></p> <hr/> <h2 id="photoswipe"><a href="https://photoswipe.com/">PhotoSwipe</a></h2> <div class="pswp-gallery pswp-gallery--single-column" id="gallery--getting-started"> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg" data-pswp-width="1669" data-pswp-height="2500" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg" alt=""/> </a> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/7/img-2500.jpg" data-pswp-width="1875" data-pswp-height="2500" data-cropped="true" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/7/img-200.jpg" alt=""/> </a> <a href="https://unsplash.com" data-pswp-src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg" data-pswp-width="2500" data-pswp-height="1666" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg" alt=""/> </a> <div> <a href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-2500.jpg" data-pswp-width="2500" data-pswp-height="1667" target="_blank"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-200.jpg" alt=""/> </a> </div> </div> <hr/> <h2 id="spotlight-js"><a href="https://nextapps-de.github.io/spotlight/">Spotlight JS</a></h2> <div class="spotlight-group"> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/> </a> </div> <div class="spotlight-group"> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/4/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/4/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/5/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/5/img-200.jpg"/> </a> <a class="spotlight" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-2500.jpg"> <img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/6/img-200.jpg"/> </a> </div> <hr/> <h2 id="venobox"><a href="https://veno.es/venobox/">Venobox</a></h2> <p><a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-200.jpg"/></a> <a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg"/></a> <a class="venobox" data-gall="myGallery" href="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg"><img src="https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg"/></a></p>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="images"/><summary type="html"><![CDATA[this is what included image galleries could look like]]></summary></entry><entry><title type="html">a post with tabs</title><link href="https://sprnjt.github.io/blog/2024/tabs/" rel="alternate" type="text/html" title="a post with tabs"/><published>2024-05-01T00:32:13+00:00</published><updated>2024-05-01T00:32:13+00:00</updated><id>https://sprnjt.github.io/blog/2024/tabs</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/tabs/"><![CDATA[<p>This is how a post with <a href="https://github.com/Ovski4/jekyll-tabs">tabs</a> looks like. Note that the tabs could be used for different purposes, not only for code.</p> <h2 id="first-tabs">First tabs</h2> <p>To add tabs, use the following syntax:</p> <div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{%</span><span class="w"> </span><span class="nt">tabs</span><span class="w"> </span><span class="nv">group-name</span><span class="w"> </span><span class="cp">%}</span>

<span class="cp">{%</span><span class="w"> </span><span class="nt">tab</span><span class="w"> </span><span class="nv">group-name</span><span class="w"> </span><span class="nv">tab-name-1</span><span class="w"> </span><span class="cp">%}</span>

Content 1

<span class="cp">{%</span><span class="w"> </span><span class="nt">endtab</span><span class="w"> </span><span class="cp">%}</span>

<span class="cp">{%</span><span class="w"> </span><span class="nt">tab</span><span class="w"> </span><span class="nv">group-name</span><span class="w"> </span><span class="nv">tab-name-2</span><span class="w"> </span><span class="cp">%}</span>

Content 2

<span class="cp">{%</span><span class="w"> </span><span class="nt">endtab</span><span class="w"> </span><span class="cp">%}</span>

<span class="cp">{%</span><span class="w"> </span><span class="nt">endtabs</span><span class="w"> </span><span class="cp">%}</span>
</code></pre></div></div> <p>With this you can generate visualizations like:</p> <ul id="log" class="tab" data-tab="ac495dd1-64d2-4450-8faf-bfbb16dfc72a" data-name="log"> <li class="active" id="log-php"> <a href="#">php </a> </li> <li id="log-js"> <a href="#">js </a> </li> <li id="log-ruby"> <a href="#">ruby </a> </li> </ul> <ul class="tab-content" id="ac495dd1-64d2-4450-8faf-bfbb16dfc72a" data-name="log"> <li class="active"> <div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">var_dump</span><span class="p">(</span><span class="s1">'hello'</span><span class="p">);</span>
</code></pre></div></div> </li> <li> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div> </li> <li> <div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">pputs</span> <span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span>
</code></pre></div></div> </li> </ul> <h2 id="another-example">Another example</h2> <ul id="data-struct" class="tab" data-tab="828ed801-8b72-484b-8f66-cbcc712e1f14" data-name="data-struct"> <li class="active" id="data-struct-yaml"> <a href="#">yaml </a> </li> <li id="data-struct-json"> <a href="#">json </a> </li> </ul> <ul class="tab-content" id="828ed801-8b72-484b-8f66-cbcc712e1f14" data-name="data-struct"> <li class="active"> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">hello</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">whatsup"</span>
  <span class="pi">-</span> <span class="s2">"</span><span class="s">hi"</span>
</code></pre></div></div> </li> <li> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"hello"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"whatsup"</span><span class="p">,</span><span class="w"> </span><span class="s2">"hi"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div> </li> </ul> <h2 id="tabs-for-something-else">Tabs for something else</h2> <ul id="something-else" class="tab" data-tab="c1f76f15-2bff-4681-bf45-98247324210e" data-name="something-else"> <li class="active" id="something-else-text"> <a href="#">text </a> </li> <li id="something-else-quote"> <a href="#">quote </a> </li> <li id="something-else-list"> <a href="#">list </a> </li> </ul> <ul class="tab-content" id="c1f76f15-2bff-4681-bf45-98247324210e" data-name="something-else"> <li class="active"> <p>Regular text</p> </li> <li> <blockquote> <p>A quote</p> </blockquote> </li> <li> <p>Hipster list</p> <ul> <li>brunch</li> <li>fixie</li> <li>raybans</li> <li>messenger bag</li> </ul> </li> </ul>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="code"/><summary type="html"><![CDATA[this is what included tabs in a post could look like]]></summary></entry><entry><title type="html">a post with typograms</title><link href="https://sprnjt.github.io/blog/2024/typograms/" rel="alternate" type="text/html" title="a post with typograms"/><published>2024-04-29T23:36:10+00:00</published><updated>2024-04-29T23:36:10+00:00</updated><id>https://sprnjt.github.io/blog/2024/typograms</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/typograms/"><![CDATA[<p>This is an example post with some <a href="https://github.com/google/typograms/">typograms</a> code.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">typograms
</span><span class="sb">+----+
|    |---&gt; My first diagram!
+----+</span>
<span class="p">```</span>
</code></pre></div></div> <p>Which generates:</p> <pre><code class="language-typograms">+----+
|    |---&gt; My first diagram!
+----+
</code></pre> <p>Another example:</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">typograms
</span><span class="sb">.------------------------.
|.----------------------.|
||"https://example.com" ||
|'----------------------'|
| ______________________ |
||                      ||
||   Welcome!           ||
||                      ||
||                      ||
||  .----------------.  ||
||  | username       |  ||
||  '----------------'  ||
||  .----------------.  ||
||  |"*******"       |  ||
||  '----------------'  ||
||                      ||
||  .----------------.  ||
||  |   "Sign-up"    |  ||
||  '----------------'  ||
||                      ||
|+----------------------+|
.------------------------.</span>
<span class="p">```</span>
</code></pre></div></div> <p>which generates:</p> <pre><code class="language-typograms">.------------------------.
|.----------------------.|
||"https://example.com" ||
|'----------------------'|
| ______________________ |
||                      ||
||   Welcome!           ||
||                      ||
||                      ||
||  .----------------.  ||
||  | username       |  ||
||  '----------------'  ||
||  .----------------.  ||
||  |"*******"       |  ||
||  '----------------'  ||
||                      ||
||  .----------------.  ||
||  |   "Sign-up"    |  ||
||  '----------------'  ||
||                      ||
|+----------------------+|
.------------------------.
</code></pre> <p>For more examples, check out the <a href="https://google.github.io/typograms/#examples">typograms documentation</a>.</p>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="diagrams"/><summary type="html"><![CDATA[this is what included typograms code could look like]]></summary></entry><entry><title type="html">a post that can be cited</title><link href="https://sprnjt.github.io/blog/2024/post-citation/" rel="alternate" type="text/html" title="a post that can be cited"/><published>2024-04-28T15:06:00+00:00</published><updated>2024-04-28T15:06:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/post-citation</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/post-citation/"><![CDATA[<p>This is an example post that can be cited. The content of the post ends here, while the citation information is automatically provided below. The only thing needed is for you to set the <code class="language-plaintext highlighter-rouge">citation</code> key in the front matter to <code class="language-plaintext highlighter-rouge">true</code>.</p>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="citation"/><summary type="html"><![CDATA[this is what a post that can be cited looks like]]></summary></entry><entry><title type="html">a post with pseudo code</title><link href="https://sprnjt.github.io/blog/2024/pseudocode/" rel="alternate" type="text/html" title="a post with pseudo code"/><published>2024-04-15T00:01:00+00:00</published><updated>2024-04-15T00:01:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/pseudocode</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/pseudocode/"><![CDATA[<p>This is an example post with some pseudo code rendered by <a href="https://github.com/SaswatPadhi/pseudocode.js">pseudocode</a>. The example presented here is the same as the one in the <a href="https://saswat.padhi.me/pseudocode.js/">pseudocode.js</a> documentation, with only one simple but important change: everytime you would use <code class="language-plaintext highlighter-rouge">$</code>, you should use <code class="language-plaintext highlighter-rouge">$$</code> instead. Also, note that the <code class="language-plaintext highlighter-rouge">pseudocode</code> key in the front matter is set to <code class="language-plaintext highlighter-rouge">true</code> to enable the rendering of pseudo code. As an example, using this code:</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">pseudocode
</span><span class="sb">% This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
\begin{algorithm}
\caption{Quicksort}
\begin{algorithmic}
\PROCEDURE{Quicksort}{$$A, p, r$$}
    \IF{$$p &lt; r$$}
        \STATE $$q = $$ \CALL{Partition}{$$A, p, r$$}
        \STATE \CALL{Quicksort}{$$A, p, q - 1$$}
        \STATE \CALL{Quicksort}{$$A, q + 1, r$$}
    \ENDIF
\ENDPROCEDURE
\PROCEDURE{Partition}{$$A, p, r$$}
    \STATE $$x = A[r]$$
    \STATE $$i = p - 1$$
    \FOR{$$j = p$$ \TO $$r - 1$$}
        \IF{$$A[j] &lt; x$$}
            \STATE $$i = i + 1$$
            \STATE exchange
            $$A[i]$$ with $$A[j]$$
        \ENDIF
        \STATE exchange $$A[i]$$ with $$A[r]$$
    \ENDFOR
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}</span>
<span class="p">```</span>
</code></pre></div></div> <p>Generates:</p> <pre><code class="language-pseudocode">% This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
\begin{algorithm}
\caption{Quicksort}
\begin{algorithmic}
\PROCEDURE{Quicksort}{$$A, p, r$$}
    \IF{$$p &lt; r$$}
        \STATE $$q = $$ \CALL{Partition}{$$A, p, r$$}
        \STATE \CALL{Quicksort}{$$A, p, q - 1$$}
        \STATE \CALL{Quicksort}{$$A, q + 1, r$$}
    \ENDIF
\ENDPROCEDURE
\PROCEDURE{Partition}{$$A, p, r$$}
    \STATE $$x = A[r]$$
    \STATE $$i = p - 1$$
    \FOR{$$j = p$$ \TO $$r - 1$$}
        \IF{$$A[j] &lt; x$$}
            \STATE $$i = i + 1$$
            \STATE exchange
            $$A[i]$$ with $$A[j]$$
        \ENDIF
        \STATE exchange $$A[i]$$ with $$A[r]$$
    \ENDFOR
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}
</code></pre>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="code"/><summary type="html"><![CDATA[this is what included pseudo code could look like]]></summary></entry><entry><title type="html">a post with code diff</title><link href="https://sprnjt.github.io/blog/2024/code-diff/" rel="alternate" type="text/html" title="a post with code diff"/><published>2024-01-27T19:22:00+00:00</published><updated>2024-01-27T19:22:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/code-diff</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/code-diff/"><![CDATA[<p>You can display diff code by using the regular markdown syntax:</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">diff
</span><span class="gh">diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
</span><span class="gd">--- a/sample.js
</span><span class="gi">+++ b/sample.js
</span><span class="p">@@ -1 +1 @@</span>
<span class="gd">-console.log("Hello World!")
</span><span class="gi">+console.log("Hello from Diff2Html!")</span>
<span class="p">```</span>
</code></pre></div></div> <p>Which generates:</p> <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
</span><span class="gd">--- a/sample.js
</span><span class="gi">+++ b/sample.js
</span><span class="p">@@ -1 +1 @@</span>
<span class="gd">-console.log("Hello World!")
</span><span class="gi">+console.log("Hello from Diff2Html!")
</span></code></pre></div></div> <p>But this is difficult to read, specially if you have a large diff. You can use <a href="https://diff2html.xyz/">diff2html</a> to display a more readable version of the diff. For this, just use <code class="language-plaintext highlighter-rouge">diff2html</code> instead of <code class="language-plaintext highlighter-rouge">diff</code> for the code block language:</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">diff2html
</span><span class="sb">diff --git a/sample.js b/sample.js
index 0000001..0ddf2ba
--- a/sample.js
+++ b/sample.js
@@ -1 +1 @@
-console.log("Hello World!")
+console.log("Hello from Diff2Html!")</span>
<span class="p">```</span>
</code></pre></div></div> <p>If we use a longer example, for example <a href="https://github.com/rtfpessoa/diff2html/commit/c2c253d3e3f8b8b267f551e659f72b44ca2ac927">this commit from diff2html</a>, it will generate the following output:</p> <pre><code class="language-diff2html">From 2aaae31cc2a37bfff83430c2c914b140bee59b6a Mon Sep 17 00:00:00 2001
From: Rodrigo Fernandes &lt;rtfrodrigo@gmail.com&gt;
Date: Sun, 9 Oct 2016 16:41:54 +0100
Subject: [PATCH 1/2] Initial template override support

---
 scripts/hulk.js                    |  4 ++--
 src/diff2html.js                   |  3 +--
 src/file-list-printer.js           | 11 ++++++++---
 src/hoganjs-utils.js               | 29 +++++++++++++++++------------
 src/html-printer.js                |  6 ++++++
 src/line-by-line-printer.js        |  6 +++++-
 src/side-by-side-printer.js        |  6 +++++-
 test/file-list-printer-tests.js    |  2 +-
 test/hogan-cache-tests.js          | 18 +++++++++++++++---
 test/line-by-line-tests.js         |  3 +--
 test/side-by-side-printer-tests.js |  3 +--
 11 files changed, 62 insertions(+), 29 deletions(-)

diff --git a/scripts/hulk.js b/scripts/hulk.js
index 5a793c18..a4b1a4d5 100755
--- a/scripts/hulk.js
+++ b/scripts/hulk.js
@@ -173,11 +173,11 @@ function namespace(name) {
 // write a template foreach file that matches template extension
 templates = extractFiles(options.argv.remain)
   .map(function(file) {
-    var openedFile = fs.readFileSync(file, 'utf-8');
+    var openedFile = fs.readFileSync(file, 'utf-8').trim();
     var name;
     if (!openedFile) return;
     name = namespace(path.basename(file).replace(/\..*$/, ''));
-    openedFile = removeByteOrderMark(openedFile.trim());
+    openedFile = removeByteOrderMark(openedFile);
     openedFile = wrap(file, name, openedFile);
     if (!options.outputdir) return openedFile;
     fs.writeFileSync(path.join(options.outputdir, name + '.js')
diff --git a/src/diff2html.js b/src/diff2html.js
index 21b0119e..64e138f5 100644
--- a/src/diff2html.js
+++ b/src/diff2html.js
@@ -7,7 +7,6 @@

 (function() {
   var diffParser = require('./diff-parser.js').DiffParser;
-  var fileLister = require('./file-list-printer.js').FileListPrinter;
   var htmlPrinter = require('./html-printer.js').HtmlPrinter;

   function Diff2Html() {
@@ -43,7 +42,7 @@

     var fileList = '';
     if (configOrEmpty.showFiles === true) {
-      fileList = fileLister.generateFileList(diffJson, configOrEmpty);
+      fileList = htmlPrinter.generateFileListSummary(diffJson, configOrEmpty);
     }

     var diffOutput = '';
diff --git a/src/file-list-printer.js b/src/file-list-printer.js
index e408d9b2..1e0a2c61 100644
--- a/src/file-list-printer.js
+++ b/src/file-list-printer.js
@@ -8,11 +8,16 @@
 (function() {
   var printerUtils = require('./printer-utils.js').PrinterUtils;

-  var hoganUtils = require('./hoganjs-utils.js').HoganJsUtils;
+  var hoganUtils;
+
   var baseTemplatesPath = 'file-summary';
   var iconsBaseTemplatesPath = 'icon';

-  function FileListPrinter() {
+  function FileListPrinter(config) {
+    this.config = config;
+
+    var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
+    hoganUtils = new HoganJsUtils(config);
   }

   FileListPrinter.prototype.generateFileList = function(diffFiles) {
@@ -38,5 +43,5 @@
     });
   };

-  module.exports.FileListPrinter = new FileListPrinter();
+  module.exports.FileListPrinter = FileListPrinter;
 })();
diff --git a/src/hoganjs-utils.js b/src/hoganjs-utils.js
index 9949e5fa..0dda08d7 100644
--- a/src/hoganjs-utils.js
+++ b/src/hoganjs-utils.js
@@ -8,18 +8,19 @@
 (function() {
   var fs = require('fs');
   var path = require('path');
-
   var hogan = require('hogan.js');

   var hoganTemplates = require('./templates/diff2html-templates.js');

-  var templatesPath = path.resolve(__dirname, 'templates');
+  var extraTemplates;

-  function HoganJsUtils() {
+  function HoganJsUtils(configuration) {
+    this.config = configuration || {};
+    extraTemplates = this.config.templates || {};
   }

-  HoganJsUtils.prototype.render = function(namespace, view, params, configuration) {
-    var template = this.template(namespace, view, configuration);
+  HoganJsUtils.prototype.render = function(namespace, view, params) {
+    var template = this.template(namespace, view);
     if (template) {
       return template.render(params);
     }
@@ -27,17 +28,16 @@
     return null;
   };

-  HoganJsUtils.prototype.template = function(namespace, view, configuration) {
-    var config = configuration || {};
+  HoganJsUtils.prototype.template = function(namespace, view) {
     var templateKey = this._templateKey(namespace, view);

-    return this._getTemplate(templateKey, config);
+    return this._getTemplate(templateKey);
   };

-  HoganJsUtils.prototype._getTemplate = function(templateKey, config) {
+  HoganJsUtils.prototype._getTemplate = function(templateKey) {
     var template;

-    if (!config.noCache) {
+    if (!this.config.noCache) {
       template = this._readFromCache(templateKey);
     }

@@ -53,6 +53,7 @@

     try {
       if (fs.readFileSync) {
+        var templatesPath = path.resolve(__dirname, 'templates');
         var templatePath = path.join(templatesPath, templateKey);
         var templateContent = fs.readFileSync(templatePath + '.mustache', 'utf8');
         template = hogan.compile(templateContent);
@@ -66,12 +67,16 @@
   };

   HoganJsUtils.prototype._readFromCache = function(templateKey) {
-    return hoganTemplates[templateKey];
+    return extraTemplates[templateKey] || hoganTemplates[templateKey];
   };

   HoganJsUtils.prototype._templateKey = function(namespace, view) {
     return namespace + '-' + view;
   };

-  module.exports.HoganJsUtils = new HoganJsUtils();
+  HoganJsUtils.prototype.compile = function(templateStr) {
+    return hogan.compile(templateStr);
+  };
+
+  module.exports.HoganJsUtils = HoganJsUtils;
 })();
diff --git a/src/html-printer.js b/src/html-printer.js
index 585d5b66..13f83047 100644
--- a/src/html-printer.js
+++ b/src/html-printer.js
@@ -8,6 +8,7 @@
 (function() {
   var LineByLinePrinter = require('./line-by-line-printer.js').LineByLinePrinter;
   var SideBySidePrinter = require('./side-by-side-printer.js').SideBySidePrinter;
+  var FileListPrinter = require('./file-list-printer.js').FileListPrinter;

   function HtmlPrinter() {
   }
@@ -22,5 +23,10 @@
     return sideBySidePrinter.generateSideBySideJsonHtml(diffFiles);
   };

+  HtmlPrinter.prototype.generateFileListSummary = function(diffJson, config) {
+    var fileListPrinter = new FileListPrinter(config);
+    return fileListPrinter.generateFileList(diffJson);
+  };
+
   module.exports.HtmlPrinter = new HtmlPrinter();
 })();
diff --git a/src/line-by-line-printer.js b/src/line-by-line-printer.js
index b07eb53c..d230bedd 100644
--- a/src/line-by-line-printer.js
+++ b/src/line-by-line-printer.js
@@ -11,7 +11,8 @@
   var utils = require('./utils.js').Utils;
   var Rematch = require('./rematch.js').Rematch;

-  var hoganUtils = require('./hoganjs-utils.js').HoganJsUtils;
+  var hoganUtils;
+
   var genericTemplatesPath = 'generic';
   var baseTemplatesPath = 'line-by-line';
   var iconsBaseTemplatesPath = 'icon';
@@ -19,6 +20,9 @@

   function LineByLinePrinter(config) {
     this.config = config;
+
+    var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
+    hoganUtils = new HoganJsUtils(config);
   }

   LineByLinePrinter.prototype.makeFileDiffHtml = function(file, diffs) {
diff --git a/src/side-by-side-printer.js b/src/side-by-side-printer.js
index bbf1dc8d..5e3033b3 100644
--- a/src/side-by-side-printer.js
+++ b/src/side-by-side-printer.js
@@ -11,7 +11,8 @@
   var utils = require('./utils.js').Utils;
   var Rematch = require('./rematch.js').Rematch;

-  var hoganUtils = require('./hoganjs-utils.js').HoganJsUtils;
+  var hoganUtils;
+
   var genericTemplatesPath = 'generic';
   var baseTemplatesPath = 'side-by-side';
   var iconsBaseTemplatesPath = 'icon';
@@ -26,6 +27,9 @@

   function SideBySidePrinter(config) {
     this.config = config;
+
+    var HoganJsUtils = require('./hoganjs-utils.js').HoganJsUtils;
+    hoganUtils = new HoganJsUtils(config);
   }

   SideBySidePrinter.prototype.makeDiffHtml = function(file, diffs) {
diff --git a/test/file-list-printer-tests.js b/test/file-list-printer-tests.js
index a502a46f..60ea3208 100644
--- a/test/file-list-printer-tests.js
+++ b/test/file-list-printer-tests.js
@@ -1,6 +1,6 @@
 var assert = require('assert');

-var fileListPrinter = require('../src/file-list-printer.js').FileListPrinter;
+var fileListPrinter = new (require('../src/file-list-printer.js').FileListPrinter)();

 describe('FileListPrinter', function() {
   describe('generateFileList', function() {
diff --git a/test/hogan-cache-tests.js b/test/hogan-cache-tests.js
index 190bf6f8..3bb754ac 100644
--- a/test/hogan-cache-tests.js
+++ b/test/hogan-cache-tests.js
@@ -1,6 +1,6 @@
 var assert = require('assert');

-var HoganJsUtils = require('../src/hoganjs-utils.js').HoganJsUtils;
+var HoganJsUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)();
 var diffParser = require('../src/diff-parser.js').DiffParser;

 describe('HoganJsUtils', function() {
@@ -21,16 +21,28 @@ describe('HoganJsUtils', function() {
       });
       assert.equal(emptyDiffHtml, result);
     });
+
     it('should render view without cache', function() {
       var result = HoganJsUtils.render('generic', 'empty-diff', {
         contentClass: 'd2h-code-line',
         diffParser: diffParser
       }, {noCache: true});
-      assert.equal(emptyDiffHtml + '\n', result);
+      assert.equal(emptyDiffHtml, result);
     });
+
     it('should return null if template is missing', function() {
-      var result = HoganJsUtils.render('generic', 'missing-template', {}, {noCache: true});
+      var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)({noCache: true});
+      var result = hoganUtils.render('generic', 'missing-template', {});
       assert.equal(null, result);
     });
+
+    it('should allow templates to be overridden', function() {
+      var emptyDiffTemplate = HoganJsUtils.compile('&lt;p&gt;&lt;/p&gt;');
+
+      var config = {templates: {'generic-empty-diff': emptyDiffTemplate}};
+      var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
+      var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
+      assert.equal('&lt;p&gt;Rodrigo Fernandes&lt;/p&gt;', result);
+    });
   });
 });
diff --git a/test/line-by-line-tests.js b/test/line-by-line-tests.js
index 1cd92073..8869b3df 100644
--- a/test/line-by-line-tests.js
+++ b/test/line-by-line-tests.js
@@ -14,7 +14,7 @@ describe('LineByLinePrinter', function() {
         '            File without changes\n' +
         '        &lt;/div&gt;\n' +
         '    &lt;/td&gt;\n' +
-        '&lt;/tr&gt;\n';
+        '&lt;/tr&gt;';

       assert.equal(expected, fileHtml);
     });
@@ -422,7 +422,6 @@ describe('LineByLinePrinter', function() {
         '        &lt;/div&gt;\n' +
         '    &lt;/td&gt;\n' +
         '&lt;/tr&gt;\n' +
-        '\n' +
         '                &lt;/tbody&gt;\n' +
         '            &lt;/table&gt;\n' +
         '        &lt;/div&gt;\n' +
diff --git a/test/side-by-side-printer-tests.js b/test/side-by-side-printer-tests.js
index 76625f8e..771daaa5 100644
--- a/test/side-by-side-printer-tests.js
+++ b/test/side-by-side-printer-tests.js
@@ -14,7 +14,7 @@ describe('SideBySidePrinter', function() {
         '            File without changes\n' +
         '        &lt;/div&gt;\n' +
         '    &lt;/td&gt;\n' +
-        '&lt;/tr&gt;\n';
+        '&lt;/tr&gt;';

       assert.equal(expectedRight, fileHtml.right);
       assert.equal(expectedLeft, fileHtml.left);
@@ -324,7 +324,6 @@ describe('SideBySidePrinter', function() {
         '        &lt;/div&gt;\n' +
         '    &lt;/td&gt;\n' +
         '&lt;/tr&gt;\n' +
-        '\n' +
         '                    &lt;/tbody&gt;\n' +
         '                &lt;/table&gt;\n' +
         '            &lt;/div&gt;\n' +

From f3cadb96677d0eb82fc2752dc3ffbf35ca9b5bdb Mon Sep 17 00:00:00 2001
From: Rodrigo Fernandes &lt;rtfrodrigo@gmail.com&gt;
Date: Sat, 15 Oct 2016 13:21:22 +0100
Subject: [PATCH 2/2] Allow uncompiled templates

---
 README.md                 |  3 +++
 src/hoganjs-utils.js      |  7 +++++++
 test/hogan-cache-tests.js | 24 +++++++++++++++++++++++-
 3 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 132c8a28..46909f25 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,9 @@ The HTML output accepts a Javascript object with configuration. Possible options
   - `synchronisedScroll`: scroll both panes in side-by-side mode: `true` or `false`, default is `false`
   - `matchWordsThreshold`: similarity threshold for word matching, default is 0.25
   - `matchingMaxComparisons`: perform at most this much comparisons for line matching a block of changes, default is `2500`
+  - `templates`: object with previously compiled templates to replace parts of the html
+  - `rawTemplates`: object with raw not compiled templates to replace parts of the html
+  &gt; For more information regarding the possible templates look into [src/templates](https://github.com/rtfpessoa/diff2html/tree/master/src/templates)

 ## Diff2HtmlUI Helper

diff --git a/src/hoganjs-utils.js b/src/hoganjs-utils.js
index 0dda08d7..b2e9c275 100644
--- a/src/hoganjs-utils.js
+++ b/src/hoganjs-utils.js
@@ -17,6 +17,13 @@
   function HoganJsUtils(configuration) {
     this.config = configuration || {};
     extraTemplates = this.config.templates || {};
+
+    var rawTemplates = this.config.rawTemplates || {};
+    for (var templateName in rawTemplates) {
+      if (rawTemplates.hasOwnProperty(templateName)) {
+        if (!extraTemplates[templateName]) extraTemplates[templateName] = this.compile(rawTemplates[templateName]);
+      }
+    }
   }

   HoganJsUtils.prototype.render = function(namespace, view, params) {
diff --git a/test/hogan-cache-tests.js b/test/hogan-cache-tests.js
index 3bb754ac..a34839c0 100644
--- a/test/hogan-cache-tests.js
+++ b/test/hogan-cache-tests.js
@@ -36,7 +36,7 @@ describe('HoganJsUtils', function() {
       assert.equal(null, result);
     });

-    it('should allow templates to be overridden', function() {
+    it('should allow templates to be overridden with compiled templates', function() {
       var emptyDiffTemplate = HoganJsUtils.compile('&lt;p&gt;&lt;/p&gt;');

       var config = {templates: {'generic-empty-diff': emptyDiffTemplate}};
@@ -44,5 +44,27 @@ describe('HoganJsUtils', function() {
       var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
       assert.equal('&lt;p&gt;Rodrigo Fernandes&lt;/p&gt;', result);
     });
+
+    it('should allow templates to be overridden with uncompiled templates', function() {
+      var emptyDiffTemplate = '&lt;p&gt;&lt;/p&gt;';
+
+      var config = {rawTemplates: {'generic-empty-diff': emptyDiffTemplate}};
+      var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
+      var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
+      assert.equal('&lt;p&gt;Rodrigo Fernandes&lt;/p&gt;', result);
+    });
+
+    it('should allow templates to be overridden giving priority to compiled templates', function() {
+      var emptyDiffTemplate = HoganJsUtils.compile('&lt;p&gt;&lt;/p&gt;');
+      var emptyDiffTemplateUncompiled = '&lt;p&gt;Not used!&lt;/p&gt;';
+
+      var config = {
+        templates: {'generic-empty-diff': emptyDiffTemplate},
+        rawTemplates: {'generic-empty-diff': emptyDiffTemplateUncompiled}
+      };
+      var hoganUtils = new (require('../src/hoganjs-utils.js').HoganJsUtils)(config);
+      var result = hoganUtils.render('generic', 'empty-diff', {myName: 'Rodrigo Fernandes'});
+      assert.equal('&lt;p&gt;Rodrigo Fernandes&lt;/p&gt;', result);
+    });
   });
 });
</code></pre>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="code"/><summary type="html"><![CDATA[this is how you can display code diffs]]></summary></entry><entry><title type="html">a post with advanced image components</title><link href="https://sprnjt.github.io/blog/2024/advanced-images/" rel="alternate" type="text/html" title="a post with advanced image components"/><published>2024-01-27T11:46:00+00:00</published><updated>2024-01-27T11:46:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/advanced-images</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/advanced-images/"><![CDATA[<p>This is an example post with advanced image components.</p> <h2 id="image-slider">Image Slider</h2> <p>This is a simple image slider. It uses the <a href="https://swiperjs.com/">Swiper</a> library. Check the <a href="https://swiperjs.com/demos">examples page</a> for more information of what you can achieve with it.</p> <swiper-container keyboard="true" navigation="true" pagination="true" pagination-clickable="true" pagination-dynamic-bullets="true" rewind="true"> <swiper-slide> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/9-480.webp 480w,/assets/img/9-800.webp 800w,/assets/img/9-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/9.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </swiper-slide> <swiper-slide> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/7-480.webp 480w,/assets/img/7-800.webp 800w,/assets/img/7-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/7.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </swiper-slide> <swiper-slide> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/8-480.webp 480w,/assets/img/8-800.webp 800w,/assets/img/8-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/8.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </swiper-slide> <swiper-slide> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/10-480.webp 480w,/assets/img/10-800.webp 800w,/assets/img/10-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/10.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </swiper-slide> <swiper-slide> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/12-480.webp 480w,/assets/img/12-800.webp 800w,/assets/img/12-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/12.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="eager" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </swiper-slide> </swiper-container> <h2 id="image-comparison-slider">Image Comparison Slider</h2> <p>This is a simple image comparison slider. It uses the <a href="https://img-comparison-slider.sneas.io/">img-comparison-slider</a> library. Check the <a href="https://img-comparison-slider.sneas.io/examples.html">examples page</a> for more information of what you can achieve with it.</p> <img-comparison-slider> <figure slot="first"> <picture> <source class="responsive-img-srcset" srcset="/assets/img/prof_pic-480.webp 480w,/assets/img/prof_pic-800.webp 800w,/assets/img/prof_pic-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/prof_pic.jpg" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="lazy" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> <figure slot="second"> <picture> <source class="responsive-img-srcset" srcset="/assets/img/prof_pic_color-480.webp 480w,/assets/img/prof_pic_color-800.webp 800w,/assets/img/prof_pic_color-1400.webp 1400w," type="image/webp" sizes="95vw"/> <img src="/assets/img/prof_pic_color.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" loading="lazy" onerror="this.onerror=null; document.querySelectorAll('.responsive-img-srcset').forEach(function (n) { n.remove(); });"/> </picture> </figure> </img-comparison-slider>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="images"/><summary type="html"><![CDATA[this is what advanced image components could look like]]></summary></entry><entry><title type="html">a post with vega lite</title><link href="https://sprnjt.github.io/blog/2024/vega-lite/" rel="alternate" type="text/html" title="a post with vega lite"/><published>2024-01-27T00:20:00+00:00</published><updated>2024-04-14T04:30:00+00:00</updated><id>https://sprnjt.github.io/blog/2024/vega-lite</id><content type="html" xml:base="https://sprnjt.github.io/blog/2024/vega-lite/"><![CDATA[<p>This is an example post with some <a href="https://vega.github.io/vega-lite/">vega lite</a> code.</p> <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">vega_lite
</span><span class="sb">{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "A dot plot showing each movie in the database, and the difference from the average movie rating. The display is sorted by year to visualize everything in sequential order. The graph is for all Movies before 2019.",
  "data": {
    "url": "https://raw.githubusercontent.com/vega/vega/main/docs/data/movies.json"
  },
  "transform": [
    {"filter": "datum['IMDB Rating'] != null"},
    {"filter": {"timeUnit": "year", "field": "Release Date", "range": [null, 2019]}},
    {
      "joinaggregate": [{
        "op": "mean",
        "field": "IMDB Rating",
        "as": "AverageRating"
      }]
    },
    {
      "calculate": "datum['IMDB Rating'] - datum.AverageRating",
      "as": "RatingDelta"
    }
  ],
  "mark": "point",
  "encoding": {
    "x": {
      "field": "Release Date",
      "type": "temporal"
    },
    "y": {
      "field": "RatingDelta",
      "type": "quantitative",
      "title": "Rating Delta"
    },
    "color": {
      "field": "RatingDelta",
      "type": "quantitative",
      "scale": {"domainMid": 0},
      "title": "Rating Delta"
    }
  }
}</span>
<span class="p">```</span>
</code></pre></div></div> <p>Which generates:</p> <pre><code class="language-vega_lite">{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "description": "A dot plot showing each movie in the database, and the difference from the average movie rating. The display is sorted by year to visualize everything in sequential order. The graph is for all Movies before 2019.",
  "data": {
    "url": "https://raw.githubusercontent.com/vega/vega/main/docs/data/movies.json"
  },
  "transform": [
    {"filter": "datum['IMDB Rating'] != null"},
    {"filter": {"timeUnit": "year", "field": "Release Date", "range": [null, 2019]}},
    {
      "joinaggregate": [{
        "op": "mean",
        "field": "IMDB Rating",
        "as": "AverageRating"
      }]
    },
    {
      "calculate": "datum['IMDB Rating'] - datum.AverageRating",
      "as": "RatingDelta"
    }
  ],
  "mark": "point",
  "encoding": {
    "x": {
      "field": "Release Date",
      "type": "temporal"
    },
    "y": {
      "field": "RatingDelta",
      "type": "quantitative",
      "title": "Rating Delta"
    },
    "color": {
      "field": "RatingDelta",
      "type": "quantitative",
      "scale": {"domainMid": 0},
      "title": "Rating Delta"
    }
  }
}
</code></pre> <p>This plot supports both light and dark themes.</p>]]></content><author><name></name></author><category term="sample-posts"/><category term="formatting"/><category term="charts"/><summary type="html"><![CDATA[this is what included vega lite code could look like]]></summary></entry></feed>