Skip to content

Commit 12e11b0

Browse files
committed
Adding extra functionality for svg (rotate, color, ... )
1 parent be5d3f3 commit 12e11b0

File tree

5 files changed

+289
-32
lines changed

5 files changed

+289
-32
lines changed

web_iconify_proxy/README.rst

+28-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,34 @@ Usage
4242
=====
4343

4444
This module works in conjunction with web_iconify. Once installed, icons
45-
will be served through the proxy and cached locally. No specific usage
46-
instructions are required.
45+
will be served through the proxy and cached locally.
46+
47+
SVG Icon Parameters
48+
-------------------
49+
50+
You can customize SVG icons by adding query parameters to the URL. The
51+
format is:
52+
53+
``/web_iconify_proxy/<prefix>/<icon>.svg?param1=value1&param2=value2...``
54+
55+
Available parameters:
56+
57+
- **color**: Icon color (e.g., ``color=red``, ``color=%23ff0000``).
58+
- **width**: Icon width (e.g., ``width=50``, ``width=50px``).
59+
- **height**: Icon height (e.g., ``height=50``, ``height=50px``). If
60+
only one dimension is specified, the other will be calculated
61+
automatically to maintain aspect ratio.
62+
- **flip**: Flip the icon. Possible values: ``horizontal``,
63+
``vertical``, or both (e.g., ``flip=horizontal``, ``flip=vertical``,
64+
``flip=horizontal,vertical``).
65+
- **rotate**: Rotate the icon by 90, 180, or 270 degrees (e.g.,
66+
``rotate=90``, ``rotate=180``).
67+
- **box**: Set to ``true`` to add an empty rectangle to the SVG that
68+
matches the viewBox (e.g., ``box=true``).
69+
70+
Example:
71+
72+
``/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal``
4773

4874
Changelog
4975
=========

web_iconify_proxy/controllers/main.py

+64-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ast
12
import base64
23
import datetime
34
import logging
@@ -15,7 +16,13 @@ class IconifyProxyController(http.Controller):
1516
"""Controller for proxying Iconify requests."""
1617

1718
def _fetch_iconify_data(
18-
self, upstream_url, content_type, prefix, icons=None, icon=None
19+
self,
20+
upstream_url,
21+
content_type,
22+
prefix,
23+
icons=None,
24+
icon=None,
25+
normalized_params_string="",
1926
):
2027
"""Fetches data from the Iconify API or the local cache.
2128
@@ -25,36 +32,42 @@ def _fetch_iconify_data(
2532
prefix (str): The icon prefix.
2633
icons (str, optional): Comma-separated list of icons (for CSS and JSON).
2734
icon (str, optional): The icon name (for SVG).
35+
normalized_params_string (str, optional): Normalized parameters string.
2836
2937
Returns:
3038
Response: The HTTP response.
3139
"""
3240

41+
# Validate prefix
42+
prefix = prefix.lower()
43+
3344
# Validate prefix
3445
if not re.match(r"^[a-z0-9-]+$", prefix):
3546
raise request.not_found()
3647

3748
# Validate icon (if provided)
38-
if icon and not re.match(r"^[a-z0-9:-]+$", icon):
39-
raise request.not_found()
49+
if icon:
50+
icon = icon.lower()
51+
if not re.match(r"^[a-z0-9:-]+$", icon):
52+
raise request.not_found()
4053

4154
# Validate icons (if provided)
4255
if icons:
43-
icon_list = icons.split(",")
56+
icon_list = [i.lower() for i in icons.split(",")]
4457
for single_icon in icon_list:
4558
if not re.match(r"^[a-z0-9:-]+$", single_icon):
4659
raise request.not_found()
4760
icons = ",".join(icon_list) # Reconstruct to prevent injection
4861

4962
Attachment = request.env["ir.attachment"].sudo()
5063
if content_type == "image/svg+xml":
51-
name = f"{prefix}-{icon}"
64+
name = f"{prefix}-{icon}-{normalized_params_string.lower()}"
5265
res_model = "iconify.svg"
5366
elif content_type == "text/css":
54-
name = f"{prefix}-{icons}"
67+
name = f"{prefix}-{icons}-{normalized_params_string.lower()}"
5568
res_model = "iconify.css"
5669
elif content_type == "application/json":
57-
name = f"{prefix}-{icons}"
70+
name = f"{prefix}-{icons}-{normalized_params_string.lower()}"
5871
res_model = "iconify.json"
5972
else:
6073
raise request.not_found()
@@ -98,6 +111,36 @@ def _fetch_iconify_data(
98111
]
99112
return request.make_response(data, headers)
100113

114+
def _normalize_params_common(self, params):
115+
"""Normalizes common parameters for Iconify requests."""
116+
normalized = []
117+
for key in sorted(params.keys()): # Sort keys alphabetically
118+
normalized.append(f"{key.lower()}={params[key]}")
119+
return ";".join(normalized)
120+
121+
def _normalize_params_svg(self, params):
122+
"""Normalizes parameters specifically for SVG requests."""
123+
allowed_params = ["color", "width", "height", "flip", "rotate", "box"]
124+
normalized = []
125+
for key in sorted(params.keys()):
126+
if key in allowed_params:
127+
value = params[key]
128+
key = key.lower()
129+
# Basic type validation
130+
if key in ("width", "height", "rotate") and not (
131+
isinstance(value, str) or isinstance(value, int)
132+
):
133+
continue # Skip invalid values
134+
if key == "box":
135+
try:
136+
value = ast.literal_eval(str(value).lower())
137+
if not isinstance(value, bool):
138+
continue
139+
except (ValueError, SyntaxError):
140+
continue
141+
normalized.append(f"{key}={value}")
142+
return ";".join(normalized)
143+
101144
@http.route(
102145
"/web_iconify_proxy/<string:prefix>/<string:icon>.svg",
103146
type="http",
@@ -115,9 +158,21 @@ def get_svg(self, prefix, icon, **params):
115158
Returns:
116159
Response: The HTTP response containing the SVG data.
117160
"""
118-
upstream_url = f"https://api.iconify.design/{prefix}/{icon}.svg"
161+
normalized_params = self._normalize_params_svg(params)
162+
if normalized_params:
163+
query_string = normalized_params.replace(";", "&")
164+
upstream_url = (
165+
f"https://api.iconify.design/{prefix}/{icon}.svg?{query_string}"
166+
)
167+
else:
168+
upstream_url = f"https://api.iconify.design/{prefix}/{icon}.svg"
169+
119170
return self._fetch_iconify_data(
120-
upstream_url, "image/svg+xml", prefix, icon=icon
171+
upstream_url,
172+
"image/svg+xml",
173+
prefix,
174+
icon=icon,
175+
normalized_params_string=normalized_params,
121176
)
122177

123178
@http.route(

web_iconify_proxy/readme/USAGE.md

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
11
This module works in conjunction with web_iconify. Once installed, icons
2-
will be served through the proxy and cached locally. No specific usage
3-
instructions are required.
2+
will be served through the proxy and cached locally.
3+
4+
## SVG Icon Parameters
5+
6+
You can customize SVG icons by adding query parameters to the URL. The format is:
7+
8+
`/web_iconify_proxy/<prefix>/<icon>.svg?param1=value1&param2=value2...`
9+
10+
Available parameters:
11+
12+
* **color**: Icon color (e.g., `color=red`, `color=%23ff0000`).
13+
* **width**: Icon width (e.g., `width=50`, `width=50px`).
14+
* **height**: Icon height (e.g., `height=50`, `height=50px`). If only one dimension is specified, the other will be calculated automatically to maintain aspect ratio.
15+
* **flip**: Flip the icon. Possible values: `horizontal`, `vertical`, or both (e.g., `flip=horizontal`, `flip=vertical`, `flip=horizontal,vertical`).
16+
* **rotate**: Rotate the icon by 90, 180, or 270 degrees (e.g., `rotate=90`, `rotate=180`).
17+
* **box**: Set to `true` to add an empty rectangle to the SVG that matches the viewBox (e.g., `box=true`).
18+
19+
Example:
20+
21+
`/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal`

web_iconify_proxy/static/description/index.html

+42-17
Original file line numberDiff line numberDiff line change
@@ -377,59 +377,84 @@ <h1 class="title">Web Iconify Proxy</h1>
377377
<p><strong>Table of contents</strong></p>
378378
<div class="contents local topic" id="contents">
379379
<ul class="simple">
380-
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
381-
<li><a class="reference internal" href="#changelog" id="toc-entry-2">Changelog</a><ul>
382-
<li><a class="reference internal" href="#section-1" id="toc-entry-3">18.0.1.0.0 (2025-02-23)</a></li>
380+
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a><ul>
381+
<li><a class="reference internal" href="#svg-icon-parameters" id="toc-entry-2">SVG Icon Parameters</a></li>
383382
</ul>
384383
</li>
385-
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
386-
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
387-
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
388-
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
389-
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
384+
<li><a class="reference internal" href="#changelog" id="toc-entry-3">Changelog</a><ul>
385+
<li><a class="reference internal" href="#section-1" id="toc-entry-4">18.0.1.0.0 (2025-02-23)</a></li>
386+
</ul>
387+
</li>
388+
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-5">Bug Tracker</a></li>
389+
<li><a class="reference internal" href="#credits" id="toc-entry-6">Credits</a><ul>
390+
<li><a class="reference internal" href="#authors" id="toc-entry-7">Authors</a></li>
391+
<li><a class="reference internal" href="#contributors" id="toc-entry-8">Contributors</a></li>
392+
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
390393
</ul>
391394
</li>
392395
</ul>
393396
</div>
394397
<div class="section" id="usage">
395398
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
396399
<p>This module works in conjunction with web_iconify. Once installed, icons
397-
will be served through the proxy and cached locally. No specific usage
398-
instructions are required.</p>
400+
will be served through the proxy and cached locally.</p>
401+
<div class="section" id="svg-icon-parameters">
402+
<h2><a class="toc-backref" href="#toc-entry-2">SVG Icon Parameters</a></h2>
403+
<p>You can customize SVG icons by adding query parameters to the URL. The
404+
format is:</p>
405+
<p><tt class="docutils literal"><span class="pre">/web_iconify_proxy/&lt;prefix&gt;/&lt;icon&gt;.svg?param1=value1&amp;param2=value2...</span></tt></p>
406+
<p>Available parameters:</p>
407+
<ul class="simple">
408+
<li><strong>color</strong>: Icon color (e.g., <tt class="docutils literal">color=red</tt>, <tt class="docutils literal"><span class="pre">color=%23ff0000</span></tt>).</li>
409+
<li><strong>width</strong>: Icon width (e.g., <tt class="docutils literal">width=50</tt>, <tt class="docutils literal">width=50px</tt>).</li>
410+
<li><strong>height</strong>: Icon height (e.g., <tt class="docutils literal">height=50</tt>, <tt class="docutils literal">height=50px</tt>). If
411+
only one dimension is specified, the other will be calculated
412+
automatically to maintain aspect ratio.</li>
413+
<li><strong>flip</strong>: Flip the icon. Possible values: <tt class="docutils literal">horizontal</tt>,
414+
<tt class="docutils literal">vertical</tt>, or both (e.g., <tt class="docutils literal">flip=horizontal</tt>, <tt class="docutils literal">flip=vertical</tt>,
415+
<tt class="docutils literal">flip=horizontal,vertical</tt>).</li>
416+
<li><strong>rotate</strong>: Rotate the icon by 90, 180, or 270 degrees (e.g.,
417+
<tt class="docutils literal">rotate=90</tt>, <tt class="docutils literal">rotate=180</tt>).</li>
418+
<li><strong>box</strong>: Set to <tt class="docutils literal">true</tt> to add an empty rectangle to the SVG that
419+
matches the viewBox (e.g., <tt class="docutils literal">box=true</tt>).</li>
420+
</ul>
421+
<p>Example:</p>
422+
<p><tt class="docutils literal"><span class="pre">/web_iconify_proxy/mdi/home.svg?color=blue&amp;width=64&amp;flip=horizontal</span></tt></p>
423+
</div>
399424
</div>
400425
<div class="section" id="changelog">
401-
<h1><a class="toc-backref" href="#toc-entry-2">Changelog</a></h1>
426+
<h1><a class="toc-backref" href="#toc-entry-3">Changelog</a></h1>
402427
<div class="section" id="section-1">
403-
<h2><a class="toc-backref" href="#toc-entry-3">18.0.1.0.0 (2025-02-23)</a></h2>
428+
<h2><a class="toc-backref" href="#toc-entry-4">18.0.1.0.0 (2025-02-23)</a></h2>
404429
<ul class="simple">
405430
<li>Initial version.</li>
406431
</ul>
407432
</div>
408433
</div>
409434
<div class="section" id="bug-tracker">
410-
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
435+
<h1><a class="toc-backref" href="#toc-entry-5">Bug Tracker</a></h1>
411436
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
412437
In case of trouble, please check there if your issue has already been reported.
413438
If you spotted it first, help us to smash it by providing a detailed and welcomed
414439
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_iconify_proxy%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
415440
<p>Do not contact contributors directly about support or help with technical issues.</p>
416441
</div>
417442
<div class="section" id="credits">
418-
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
443+
<h1><a class="toc-backref" href="#toc-entry-6">Credits</a></h1>
419444
<div class="section" id="authors">
420-
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
445+
<h2><a class="toc-backref" href="#toc-entry-7">Authors</a></h2>
421446
<ul class="simple">
422447
<li>jaco.tech</li>
423448
</ul>
424449
</div>
425450
<div class="section" id="contributors">
426-
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
451+
<h2><a class="toc-backref" href="#toc-entry-8">Contributors</a></h2>
427452
<ul class="simple">
428453
<li>Jaco Waes &lt;<a class="reference external" href="mailto:jaco&#64;jaco.tech">jaco&#64;jaco.tech</a>&gt;</li>
429454
</ul>
430455
</div>
431456
<div class="section" id="maintainers">
432-
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
457+
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
433458
<p>This module is maintained by the OCA.</p>
434459
<a class="reference external image-reference" href="https://odoo-community.org">
435460
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />

0 commit comments

Comments
 (0)