Compare commits

..

15 Commits

Author SHA1 Message Date
shamoon
86f07c80ff Embedded one too 2026-03-02 21:53:29 -08:00
shamoon
13d4b37deb eee 2026-03-02 21:22:16 -08:00
shamoon
346ac5f9e6 cleanup 2026-03-02 14:37:00 -08:00
shamoon
bd8922c2d6 fix leaf 2026-03-02 14:26:22 -08:00
shamoon
9b6ac4bb67 Update favicon.ico 2026-03-02 14:15:24 -08:00
shamoon
f14d36c176 Remove 2026-03-02 13:54:31 -08:00
shamoon
1fceed64eb Leave this pointing to main 2026-03-02 13:52:45 -08:00
shamoon
32b7a107c7 sizing cleanup, yada yada 2026-03-02 13:52:45 -08:00
shamoon
c270e70d63 readme, remove redundant resources 2026-03-02 13:52:44 -08:00
shamoon
898f5ea857 slightly heavier for small sizes 2026-03-02 13:52:33 -08:00
shamoon
5a9129915e In app 2026-03-02 13:52:33 -08:00
shamoon
a1256092d0 Lol we still have a favicon.ico 2026-03-02 13:52:32 -08:00
shamoon
c4267b49c4 in docs 2026-03-02 13:52:31 -08:00
shamoon
409490c897 New logo, cleanup files etc 2026-03-02 13:52:31 -08:00
shamoon
25a8ce7c05 Chore: add existing logo for temporary url resolution 2026-03-02 13:52:30 -08:00
61 changed files with 721 additions and 3226 deletions

View File

@@ -30,7 +30,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs
# Comments:
# - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.10.7-python3.12-trixie-slim AS s6-overlay-base
FROM ghcr.io/astral-sh/uv:0.10.5-python3.12-trixie-slim AS s6-overlay-base
WORKDIR /usr/src/s6

View File

@@ -7,9 +7,9 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/resources/logo/web/png/White%20logo%20-%20no%20background.png" width="50%">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_white.png" width="50%">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_black.png" width="50%">
<img src="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_black.png" width="50%">
</picture>
</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

After

Width:  |  Height:  |  Size: 748 B

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M299,891.7c-4.2-19.8-12.5-59.6-13.6-59.6c-176.7-105.7-155.8-288.7-97.3-393.4
c12.5,131.8,245.8,222.8,109.8,383.9c-1.1,2,6.2,27.2,12.5,50.2c27.2-46,68-101.4,65.8-106.7C208.9,358.2,731.9,326.9,840.6,73.7
c49.1,244.8-25.1,623.5-445.5,719.7c-2,1.1-76.3,131.8-79.5,132.9c0-2-31.4-1.1-27.2-11.5C290.7,908.4,294.8,900.1,299,891.7
L299,891.7z M293.8,793.4c53.3-61.8-9.4-167.4-47.1-201.9C310.5,701.3,306.3,765.1,293.8,793.4L293.8,793.4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -1,68 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2962.2 860.2" style="enable-background:new 0 0 2962.2 860.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;stroke:#000000;stroke-miterlimit:10;}
</style>
<path d="M1055.6,639.7v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3v249.6L1055.6,639.7L1055.6,639.7z M1059.1,514.9c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
c16.8,0,30.3-5.9,40.6-17.7C1054,546.9,1059.1,532.3,1059.1,514.9z"/>
<path d="M1417.8,398.2c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V390.2h85.3v20.6
c18-20,43.1-30.1,75.4-30.1C1379.2,380.7,1399.5,386.6,1417.8,398.2z M1389.5,514.9c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7S1389.5,532.3,1389.5,514.9z"/>
<path d="M1713.6,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2s37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8c3.6,11.4,10.5,20.7,20.9,28.1
c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1695.8,570.1,1704.9,563.7,1713.6,555.3z M1596.9,486.2h92.9
c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11S1599,473.9,1596.9,486.2z"/>
<path d="M1908.8,418.4c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V390.2h85.3V418.4L1908.8,418.4z"/>
<path d="M2113,258.2v381.5h-85.3V258.2H2113z"/>
<path d="M2360.8,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2343.1,570.1,2352.1,563.7,2360.8,555.3z
M2244.1,486.2h92.9c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11C2251.7,464.1,2246.2,473.9,2244.1,486.2z"/>
<path d="M2565.9,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2590.5,448.7,2577.6,446.3,2565.9,446.3z"/>
<path d="M2817.3,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2841.8,448.7,2828.9,446.3,2817.3,446.3z"/>
<g>
<path d="M2508,724h60.2v17.3H2508V724z"/>
<path d="M2629.2,694.4c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6v-52.6
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2620.2,699.4,2624.4,696.4,2629.2,694.4z"/>
<path d="M2790.3,833.2c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2s-6.6-11.9-7.1-19.6h19.6
c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7c-3.6,3.4-8,6.2-13.3,8.2
c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6s2-17.3,6-24.7
s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2803.2,815.9,2798.9,826.4,2790.3,833.2z M2782.2,755.7c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2776.1,764.1,2779.6,760.4,2782.2,755.7z"/>
<path d="M2843.5,788.4h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2843.5,788.4z
"/>
</g>
<path d="M835.8,319.2c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2H647v-135h72.7c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7c11.5-18.9,17.3-39.5,17.3-61.9
C853.1,358.9,847.4,338.1,835.8,319.2z M747,416.6c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C761.1,396.6,756.4,407.7,747,416.6z"/>
<path class="st0" d="M164.7,698.7c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4C82.8,431.4,277,507.1,163.8,641.2
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C89.7,254.7,525,228.6,615.5,17.9c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C157.8,712.6,161.2,705.7,164.7,698.7L164.7,698.7z M160.4,616.9
c44.4-51.4-7.8-139.3-39.2-168C174.3,540.2,170.8,593.3,160.4,616.9L160.4,616.9z"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #eee;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1,69 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2962.2 860.2" style="enable-background:new 0 0 2962.2 860.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
.st1{fill:#17541F;stroke:#000000;stroke-miterlimit:10;}
</style>
<path class="st0" d="M1055.6,639.7v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3v249.6L1055.6,639.7L1055.6,639.7z M1059.1,514.9c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
c16.8,0,30.3-5.9,40.6-17.7C1054,546.9,1059.1,532.3,1059.1,514.9z"/>
<path class="st0" d="M1417.8,398.2c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V390.2h85.3v20.6
c18-20,43.1-30.1,75.4-30.1C1379.2,380.7,1399.5,386.6,1417.8,398.2z M1389.5,514.9c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7S1389.5,532.3,1389.5,514.9z"/>
<path class="st0" d="M1713.6,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2s37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8c3.6,11.4,10.5,20.7,20.9,28.1
c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1695.8,570.1,1704.9,563.7,1713.6,555.3z M1596.9,486.2h92.9
c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11S1599,473.9,1596.9,486.2z"/>
<path class="st0" d="M1908.8,418.4c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7
c-8.4-2.1-15.7-3.1-22-3.1c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V390.2h85.3V418.4L1908.8,418.4z"/>
<path class="st0" d="M2113,258.2v381.5h-85.3V258.2H2113z"/>
<path class="st0" d="M2360.8,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2343.1,570.1,2352.1,563.7,2360.8,555.3z
M2244.1,486.2h92.9c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11C2251.7,464.1,2246.2,473.9,2244.1,486.2z"/>
<path class="st0" d="M2565.9,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2590.5,448.7,2577.6,446.3,2565.9,446.3z"/>
<path class="st0" d="M2817.3,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2841.8,448.7,2828.9,446.3,2817.3,446.3z"/>
<g>
<path class="st0" d="M2508,724h60.2v17.3H2508V724z"/>
<path class="st0" d="M2629.2,694.4c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6v-52.6
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2620.2,699.4,2624.4,696.4,2629.2,694.4z"/>
<path class="st0" d="M2790.3,833.2c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2s-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2803.2,815.9,2798.9,826.4,2790.3,833.2z M2782.2,755.7c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2776.1,764.1,2779.6,760.4,2782.2,755.7z"/>
<path class="st0" d="M2843.5,788.4h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35
L2843.5,788.4z"/>
</g>
<path class="st0" d="M835.8,319.2c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2H647v-135h72.7c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7c11.5-18.9,17.3-39.5,17.3-61.9
C853.1,358.9,847.4,338.1,835.8,319.2z M747,416.6c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C761.1,396.6,756.4,407.7,747,416.6z"/>
<path class="st1" d="M164.7,698.7c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4C82.8,431.4,277,507.1,163.8,641.2
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C89.7,254.7,525,228.6,615.5,17.9c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C157.8,712.6,161.2,705.7,164.7,698.7L164.7,698.7z M160.4,616.9
c44.4-51.4-7.8-139.3-39.2-168C174.3,540.2,170.8,593.3,160.4,616.9L160.4,616.9z"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #fff;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

11
docs/assets/logo_leaf.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
<defs>
<style>
.st0 {
fill: #005616;
}
</style>
</defs>
<path class="st0" d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
<defs>
<style>
.st0 {
fill: #fff;
}
</style>
</defs>
<path class="st0" d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 641 B

View File

@@ -4,7 +4,7 @@ title: Home
<div class="grid-left" markdown>
![image](assets/logo_full_black.svg#only-light){.index-logo}
![image](assets/logo_full_white.svg#only-dark){.index-logo}
![image](assets/logo_full_eee.svg#only-dark){.index-logo}
**Paperless-ngx** is a _community-supported_ open-source document management system that transforms your
physical documents into a searchable online archive so you can keep, well, _less paper_.

View File

@@ -111,7 +111,6 @@ docs = [
testing = [
"daphne",
"factory-boy~=3.3.1",
"faker~=40.5.1",
"imagehash",
"pytest~=9.0.0",
"pytest-cov~=7.0.0",

View File

@@ -1,16 +0,0 @@
9w
{@@N
Q@@@@H
G@@@@@@@\
SilN@@@@@@@
*Q *@@@@@@@@S /= = = = = = = = = = = = = = = = = =\
*@ B@@@@@@@@N || ||
N R$ A@@@@@@@@@@ || PAPERLESS-NGX ||
x@@ $U B@@@@@@@@@R || ||
N@@N^ @ N@@@@@@@@@* \= = = = = = = = = = = = = = = = = =/
|@@@u @ E@@@@@@@@l
Q@@@ \ Px@@@@@@P
1@@S` @@@o'
z$ ;
v
/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,329 +0,0 @@
%PDF-1.6
%âãÏÓ
3 0 obj
<</Metadata 7 0 R/OCProperties<</D<</ON[8 0 R 22 0 R]/Order 23 0 R/RBGroups[]>>/OCGs[8 0 R 22 0 R]>>/Pages 4 0 R/Type/Catalog>>
endobj
7 0 obj
<</Length 8109/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.1-c000 79.a8731b9, 2021/09/09-00:37:38 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/"
xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/">
<xmp:CreateDate>2018-12-29T21:47:38Z</xmp:CreateDate>
<xmp:CreatorTool>Chromium</xmp:CreatorTool>
<xmp:ModifyDate>2022-02-26T20:11:14-08:00</xmp:ModifyDate>
<xmp:MetadataDate>2022-02-26T20:11:14-08:00</xmp:MetadataDate>
<xmp:Thumbnails>
<rdf:Alt>
<rdf:li rdf:parseType="Resource">
<xmpGImg:width>256</xmpGImg:width>
<xmpGImg:height>76</xmpGImg:height>
<xmpGImg:format>JPEG</xmpGImg:format>
<xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgATAEAAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB&#xA;UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE&#xA;1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ&#xA;qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy&#xA;obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp&#xA;0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo&#xA;+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYqlnmXzJ&#xA;pHlvR7jV9VmENpAP9k7n7MaD9pm7DBKQAssZSAFl5x+RvnjUfN2r+br+8+BWmtJLaCtVijZZUVB8&#xA;liFT3O+VYp8RLVhnxEvWsub3Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FWG+f/zU8seTLdlvJfrWqMKwaZCw9U1Gxc7iNfdvoByueQRa55BF8t+evzB8&#xA;w+c9T+t6pLxgjJFpYx1EMKn+Ud2PdjufltmJOZlzcOczI7vVP+cVYSbjzJNX4VSzSnuxmP8Axrl2&#xA;n6t2m6voLMlynYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7&#xA;FVk88FvBJPPIsMESl5ZZCFRVUVLMx2AA74q8A/Mv/nIqV2l0ryYeEYqkusuPiPY/V0Ybf67fQB1z&#xA;GyZugcXJn6B4TcXFxczyXFxK808rF5ZZGLuzHclmNSScxnGU8VfTH/OMOlNB5R1LUXFDfXnBPdII&#xA;wAf+CkYZl4Bs5enGz2TL3IdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVYj5X/MGw13zf5i8vRFRJosiJDv8UqgcJ2/55zfD92QjOyQ1xnZIZdk2x8v/np+a1zr2qTe&#xA;XNJmMeh2MhjuHQ0+tTIaMWI6xow+EdD9rwpiZclmhycPNks0OTyPKGh2Kr4YZZ5khhQySysEjjUV&#xA;ZmY0AA8ScVfbnkXy2nlryjpeiinqWkAE5HQzPV5SPnIxzYQjQp2MI0KT3JMnYq7FXYq7FXYq7FXY&#xA;q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXw9oPm/WdE8zx+Y7OWl+szTSg14SCQ1kj&#xA;cd1etD/XNeJEG3XRkQbfUt9+Ytlqv5U6n5q0ZykqWcw9Ov7y3uuPHi3TdGYH3G/fMwzuNhzDkuNh&#xA;8f5guC7FXYq9q/5x5/LaXUNTTzdqUVNPsWP6NRx/e3A29QA/sxdj/N8jmRhhe7kYMdmy+ksynLdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir4a836BP5f8AM2pa&#xA;NOpVrOd40r+1HWsbivZkIYZr5CjTrpRo0y3ytqd6dCuLK3J9DzFYXmn3cP8ANe6dCLi3kUdjJG6R&#xA;e5qcnE7e9nE7e953lTU7FXrv5W/kPquuzQ6r5kiew0RSHS1eqXFyOwpsY4z3Y7n9nryF+PCTuW/H&#xA;hJ3PJ9NWlpbWltFa2sSwW0CiOGGMBURFFAqgdAMywHMAVcVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVdirsVdirsVdirsVdirsVdirsVdiryr86/yjfzbbprOjKq69aJwaI0UXMQ3Ccugdf2SevQ9&#xA;qU5cfFuObRlxcW45vL/ym0y40i+vNc8xwvZaP5Raa5uopk4yPezxCGKDi1DypuPeleuU4xW56NOI&#xA;VueiJ8n/APOP2q+Z7WLXbq6j0XStQLT2tmqNNOsLMSgoxRQCv2TyO29MMcJO6Y4Cd3snk78mvI3l&#xA;Z0uLa0N7qKUIvrykrqw7otAifNVr75fHEA5EcUYs4yxsdirsVdirsVdirsVdirsVdirsVdirsVdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVeRedfy/wBa8z/mjBbyQyxeUJYra81aQlfSnntP&#xA;URUWhrUpIqUNNqnsMolAmXk0TgTLyeuIiIioihUUBVVRQADYAAZe3t4q7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq//2Q==</xmpGImg:image>
</rdf:li>
</rdf:Alt>
</xmp:Thumbnails>
<pdf:Producer>Skia/PDF m64</pdf:Producer>
<xmpTPg:NPages>1</xmpTPg:NPages>
<xmpTPg:HasVisibleTransparency>False</xmpTPg:HasVisibleTransparency>
<xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
<xmpTPg:MaxPageSize rdf:parseType="Resource">
<stDim:w>2409.000000</stDim:w>
<stDim:h>909.000000</stDim:h>
<stDim:unit>Pixels</stDim:unit>
</xmpTPg:MaxPageSize>
<xmpTPg:PlateNames>
<rdf:Seq>
<rdf:li>Cyan</rdf:li>
<rdf:li>Magenta</rdf:li>
<rdf:li>Yellow</rdf:li>
<rdf:li>Black</rdf:li>
</rdf:Seq>
</xmpTPg:PlateNames>
<xmpTPg:SwatchGroups>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Default Swatch Group</xmpG:groupName>
<xmpG:groupType>0</xmpG:groupType>
</rdf:li>
</rdf:Seq>
</xmpTPg:SwatchGroups>
<xmpMM:InstanceID>uuid:e5f59418-0be8-dd42-a564-bc1f41615750</xmpMM:InstanceID>
<xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
<xmpMM:DocumentID>uuid:c2483dfa-3a53-3149-80a7-6822614a9dee</xmpMM:DocumentID>
<dc:format>application/pdf</dc:format>
<illustrator:CreatorSubTool>Adobe Illustrator</illustrator:CreatorSubTool>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
4 0 obj
<</Count 1/Kids[5 0 R]/Type/Pages>>
endobj
5 0 obj
<</ArtBox[152.941 154.947 2262.04 755.845]/BleedBox[0.0 0.0 2409.0 909.0]/Contents 24 0 R/CropBox[0.0 0.0 2409.0 909.0]/LastModified(D:20220226201114-08'00')/MediaBox[0 0 2409 909]/Parent 4 0 R/PieceInfo<</Illustrator 25 0 R>>/Resources<</ExtGState<</GS0 26 0 R>>/Properties<</MC0 22 0 R>>>>/Thumb 27 0 R/TrimBox[0.0 0.0 2409.0 909.0]/Type/Page>>
endobj
24 0 obj
<</Filter/FlateDecode/Length 3540>>stream
H‰ì—ÍŽ¹„ïýõU"™ü½Z|2 Ã?ÀÀ»{Øõûþ"ɪîI¼;ö° i²ø“ÌŒˆÌüð·<C3B0>Û‡¿~ ÛŸþüq»ý| ÛcK™ÿvýúå_·nÿ¾ÅMÿ~ùñöá/ÿÛ<>ÿ¹ýÌ0ð/n}ԣƴÙÈG¨£l/Ÿnú¢ÿc:j­ÛnGëeKv¤·=Æ#uÛ,<1E>?/·=v¾o9%—mOý¨ŒòÑjÛvf»ÕsÈâŽ1Òõ½¦£Ç¡Í¹¥moáH9?œÝ& ¿{ض÷xX<78>—ak´rýÞÏokç~š½ŽÞïg¯»÷Óòbx€¿olßïÆëm¡ÝWÌ—ï§õË3WLÏíËüpYÜæŸ8«»butŒûtM´ÂÄØ^ñn9d+²®6;†ž3ÚÑ
Æ—vd³k,—q©õûŠÞ<C5A0>Ò5¶£h>†8ßÕË8JäÐqN«G Yf#Ëá5ÏD“søÇÑkòM<»š<š<>¡=xÅru¯X~œGh)k†¹¾Æ<C2BE>ºûsÝÂ%Íß";
þ’Ñ=\ŽXÚ†<C39A>Ùâý-]&t‡¯<>eã±Yn[ÎÀµ·Ë[k¨Mwžß9¾Qw6öÍ`‹ÖªÇßÇ|m-2çÏÆŠÕˆMÁ»‡wNütûáö÷öÅh툱~“<þ<>ìzæ_á„<5A> Ôô<> ,©_ ¬ãÅžh¸ä<C2B8><C3A4>èÊ<19>ü“ê<ÐOÛ.ú­sŸé§ïôvío ¦Ÿ^öH?=üòÌ3ÿ›¯ò/á<Ÿn \Ü’€Ú‚^áL¹i!“P­F£`<60>Â]°?Ç|Θ¯­‰wœv|'˜']^|_ÌtƒïÌm\8îÔÝã<C39D>ù<»‡'mÈD%–ûŠ*q™vßÕGÑ_34aèöëä}Ó­_˜»¨pnülb‰ î.H æiÚÉÄeû<65>©…r¢öÿ]aDò<44>¶Ç ¾¼%XêÚHŒrdE…ÕÁ*g§ÌÏ×[­„hl_<>
`F,<2C>B|Rä.¹\ȶ%Òf†tX<>ÿú,<2C>tÑ-É<>ÕÇ­ <1E>øk'² X;
u<EFBFBD>à]Ê{#ÔpsD™¬ë‰{!“<>Á"Nr
õ
T;ïˆÁt
ekìiÒdq>Ú8¼}Ž_n+‡<>ø²øwPX´†&üà˜0ÖHÖ‰…%îÊäQ TVÝ¢“¾“qûð@^©¸àå4¡%¸¥Ì¡é]Ië=]3~å^a £ÁÝèçgÄJÆB5<42>ÎÖ0;°¯<C2AF>\É‚).<2E>qf;ÆckÈŽŸév5^©ø±s<C2B1>ˆIâàzŽk÷<6B>³ÑpQÇ8DZ%ËŠÁWx[ -6Ljz||š‚=¦ŠG^ÇúØ<C3BA>-J…†,%yÐò´kÌß<C38C><Jüš4™ªÂÃ@ËrÐÖü¿óþŠy`·«ÞžÐXåyðnÜ.OÅîâpå Ž5œX<C593>íš(诈q2ØÀSÚÊ&wÉ¥1{¼ZÂQ㈭ž~ßϘZ”ÛU¢e…_B~1µ4¤ã( ·á`ÐÎýØŽátóÒ“7FÈcªkA Xû3ޝçÈ#«d³Æà3@Ièr}<7D>V
F “Ôa½9xIéÉëØ¡ZŠaGx°¤9”v`R#þýšÀR³Šz„d79LKžùTôt¥+\7†Ò•
™¢ì VApﮡàcÄ/Ma}I*9ÂãÏ=\EH¬¼³Éæ ]¶k'©»/z‡ãCÂÀèòm%
peJ—”Tpž¢ö´<)iùº ¤ý
JjßIIíû)i{£¤í;)©ýO%Ío•´þ_Jê”äØìB*KHão éP]$™É¨Sù<>¨Tˆ)ÔUgRBÜÎ.“I¿_µj¢À—"¿$d'|í>Zò¥Êüœa½÷T1ÝËGpe>]­¬j.Ì“Œf¥¼ãÓüÏ2þkb]QÁ¾[¡[쎥1Èdî^®-…jTá.É Žà⤂}  qV®Ñå<ú¾œÛ9\Ô“©lë¨Ò9dÕ`7Ëׄô¡)) #b5O‰¨B¬i^Ì+šËtW­³ÔЄÜ(E*^5|„Àj¾<6A>å}^ÖâÃS¥í¢Ê¿z³dòQi*«Eõ><>hDƒ/:~Œj.mf>1µN[x»/T_ œéÏðD”]¡ÏþU.Ò'zÕT« <09>ªÓ•ú<E280A2>D,­WtaŸv¶kBš­žo+¥ç5ì$tóôyNdÏÌî—t5¡ë,ìO®DÖ¼óÌ8äæ­LîzÒÌgÖšš²§Áî4xy ìêw¡fÞKé“VÄÙyrsqè}øÆ®/8ªw*^"H[JùY+ÁmÈœc¸g³=[3@TË!#aâÍC[¬B˜½œ÷‰KER™?ý¾F¸<46>:fr<66><63>ÂOÎ*ϼté±ÍÖ¶™·¶
©‡ƒd2sôpƒý«7óÚ(†I÷<49>½Þ8«9¶)) ¦5Ï„8_³ÔBC¢0;Œb,˜"ir}ØfŽ~´IÙˆã7%­ý!i_”´ü,iù­¤åß«¤…º$­M
_#Q“ ~­¨¥w5%ûwÓ´þžšÖÞ_ÓâïUÓâ¯Ô´þŽÖ…SÒ<53>«'I£³Ä e5-I•vOŽß£0ý¦çJA
%/HÞ“J˜NÙ£fÞÝÐáuN^;¢4•«¯ŽÙOY<‡ÞD9~ð dU¸·žüPþ[Xç™ÙUN<55>I¹†IÎŸÏ éü¨m•°o'L"RÏ Ž ·¨Nѧ~N<4E>¡;nÛÃÅÁôWXBÓ¶Æ™÷Ѫvo±‹ÊøÀ’ïÿôú„ÉYŒŸ <08>XÖî#ìÕI²&x™š"EZò>ŸÁ£A˜Y[Þ I59¸$ÕiŸº0uo¸RÖðǾþ(õI:DÐ{Le<4C><dÇõ¼<1F>м!xÝA=Î<16>àDg5.¯I®Hy?×ðA.Ò ¸(fŒ©å³‚ªæÚàrJ£·²dCÅL
L¿²¢†qJgbI<>cWœÝ%d Éd³zªì:ü;ö¶8¶sæÒ¸ÃbS¢—ܰèg‡êÈ?Ý‘],g'&Sò˜©>Î ]Fw°$ݵLßWÊÖK³ûÒgÚ¤ÈX`ÓÁ«ID"ÃEJ'õÓquô[Ê+¶<>PÛIΙäB³’%ìkoýä¢ÉÀê<C380>ã¿8Ø<ch¦Q}ñ
ÍGáARýÀÛ<C380>Á WªEì†ÊT3ˆÓhó”c2uõL_dd÷<64>½ÍqQ‰A,Zð¤©|žìšyõ¸ôu[Ô-ØÕ”ˆ¹ÍñÃRóŽ”ÏÐèíêlcñØ%2ìÊžå…mó±E÷ºÐôV­H3Êžé,g~R­<52>†#¼Z<C2BC>Ù>OîÅ!:;ÈP„n
I æ&øzszV7¡ „»¢3¦”ÈXŽªÅY>F=:ûê˜ecô ¬ÔV^Òilc=Þ$<24>^É$¯Kl•RjþdI€ÇäNÉôòL‰JY˜š¤TYÀÅÝqL<71>U¥Žaæzê¢HŽ$ô­ú(D/TÛ¯± î¿d—=rÛ0F{ŸÂÐ  YgRú*â"irÿ"ï-@HŠ{R$±ûí÷ã„6§<04>©á¯g3[ý­~¢»i<<3C>o¯ÀƒÎfû5Ë2<C38B>66¢M‘³)t+
b÷ó¸ÚbJRT8kÝÊÛÚ®éÝb£Ø¦©«vAaÀùuôÞiVË&1Vµé9cD<63>åJÞ {µ<>5pÁÇk‡Uâ:/~¬fŠàh½¸k6y]gÓó¯™eM†~«=<Ç0>ñcí̵{4 UoÞüýßqz?5[ø§.í ²užIà&)<0F>ýTù¹ êTçR¹8µ/oð{í4†aý?ÁÏå8FŒRÏ<52>ù>0%Ý'ó<>2pÿÞJ{€3J/nV†˜æšr7P?H™öA«ßÝNœº»Pþ¹ª‡Ïîm´€Ž<E282AC>"Fàr<72>§ÇOØdš«e»겆Aš…¾Öf=*V;J<}ç]˘ä8x3ÊXá¼E®œÔÀÊ:G8ûhÐñ`×¼©&gT<ÕŸÖV7öÙ…}pËÛµH-Z3?þy~ØÖÆ51¨EjWeœ÷œ<C3B7>ìY63Ó»YSn£Ö³ä·i3ï¼?Bþ›:[@Å9L7¡u ´ÖÝ
̵^CA†I|T“0Êçk'Çœ&ã*Tü)þß_ϳËs<C38B>æÒójoNÙ℃Íá
ú.¸(ç¤ê<C2A4>åy(Å@™7mÚ̘ʶPuÚÚªÚ­œ”P°¶<05>åJçUÒÙÃgéˆ^À’}æÉt9jËjHÉ5t]î!:>H|H>@‰êDë8p€s:¿â‰ÜlSÀ¬Ì
 ÷¨/^6“p=ëR [<1E>B°Ú ëIžf<C5BE>ŽÝ-Љµ+‡®ªÆ\q²9EÂÊ$NÅ“ƒ:=á
¸Çy§Ñ±œÝVö<EFBFBD>$8 (fóÉ~†/ç{²'<RdN87¸.O9ƒÛºû¶ÅWÜvås˜ÙvÁÖüS ²^ÚxŸÔùóëÇ'ÿþ 0B¸|
endstream
endobj
27 0 obj
<</BitsPerComponent 8/ColorSpace 28 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 39/Length 181/Width 105>>stream
8;Z]!\Ij?G$j8@t/k5dUU3<am*`9`037'CAm[R>[,!)9)1.3+''h?`o>gmB[ET8ck
@lE>-DRC'5>'BJ23HlQilI&Ga&7If\2VcDkpR`P&Ag+(rGsf,$]V4,Oi!oYPO?6G/
Ye+**Y]%)+Lu]7C/1+obTM<jR!k7bhp#"6_FO&2i!:ndgO8~>
endstream
endobj
28 0 obj
[/Indexed/DeviceRGB 255 29 0 R]
endobj
29 0 obj
<</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream
8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn
6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1
VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<
PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(
l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
endstream
endobj
22 0 obj
<</Intent 30 0 R/Name(Layer 1)/Type/OCG/Usage 31 0 R>>
endobj
30 0 obj
[/View/Design]
endobj
31 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 26.0)/Subtype/Artwork>>>>
endobj
26 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>>
endobj
25 0 obj
<</LastModified(D:20220226201114-08'00')/Private 32 0 R>>
endobj
32 0 obj
<</AIMetaData 33 0 R/AIPDFPrivateData1 34 0 R/ContainerVersion 12/CreatorVersion 26/RoundtripStreamType 2/RoundtripVersion 26>>
endobj
33 0 obj
<</Length 1444>>stream
%!PS-Adobe-3.0
%%Creator: Adobe Illustrator(R) 24.0
%%AI8_CreatorVersion: 26.0.3
%%For: (Michael Shamoon) ()
%%Title: (White logo - no background.pdf)
%%CreationDate: 2/26/22 8:11 PM
%%Canvassize: 16383
%%BoundingBox: 152 154 2263 756
%%HiResBoundingBox: 152.941359391029 154.946950299891 2262.04187549133 755.845102922764
%%DocumentProcessColors: Cyan Magenta Yellow Black
%AI5_FileFormat 14.0
%AI12_BuildNumber: 778
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%RGBProcessColor: 0 0 0 ([Registration])
%AI3_Cropmarks: 0 0 2409 909
%AI3_TemplateBox: 1203.5 454.5 1203.5 454.5
%AI3_TileBox: 826.5 166.5 1560.5 742.5
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI24_LargeCanvasScale: 1
%AI9_ColorModel: 1
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
%AI5_NumLayers: 1
%AI17_Begin_Content_if_version_gt:24 4
%AI10_OpenToVie: -2651 3020 0.25059563884769 0 7787.44597860344 8164.54751330906 2548 1389 18 0 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_Alternate_Content
%AI9_OpenToView: -2651 3020 0.25059563884769 2548 1389 18 0 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_End_Versioned_Content
%AI5_OpenViewLayers: 7
%AI17_Begin_Content_if_version_gt:24 4
%AI17_Alternate_Content
%AI17_End_Versioned_Content
%%PageOrigin:704 -46
%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments
endstream
endobj
34 0 obj
<</Length 33953>>stream
%AI24_ZStandard_Data(µ/ýX<ž¿EZ-<2D>D¤™æ£Ù¥¶[ã*DÞJï§ënYÉwzOê°@Yþ=
)œàr<C3A0>²
}€h>@>@$F"ôãr<C3A3>.)™<>
Kˆ<C38B>j…Ãq
E`ð è‡YXZª>Tü!ò¹T Õ¸A×À”Ÿ<0E>Qìt€h4îRØdBFs2Ê<32>š<EFBFBD>´µdˆ:-¢Hæd”
â€àd”ÙÅ<18>ŠKÑ™‡c4(Vš¦X‰b'S6 
“y8p+cÁa3á.±€_&ÔAõ!êl&\âeÑñáÀºPÀêpM´ ,0O´ˆbǵ@U»Œˆ:‰•Pm<>2ŸƒMK¢€Dv´X$+Q@ƒI±H"H¦”( Š
£°:­Ïš€@ìHE;ٕΤ¡€G*ŠˆŒÃf4À±ŒÒÙŒv¤À*<01>X—N«³Ù„É,¤€¸¤¤Èô¡ð0!i™],
 ±A"˜ˆ"+2‰b'ƒÒpâ`"KDÂŽ
H*2‰@´A—ÖÀ à>)¹É® DË¢d¦€x!Ù)“`Ñp°/&‘¥³ ±TuY”•f µHÊñhiùÀRAl²ËDÔÙp@#j@!âUÂ5€…¥eD‰AK†¨³É®—¡(„t6ê°‰ø‰0Àˆ:­<C2AD>ÎFÃ@s"Rél"² %Z%„¨\2xT2 )‰<>]«ÑÆAe<41>±z„ <0B>
c“ñ’‘Àép° m…“]Bl<42>F*‹„,¢ÓÙhˆX$š] ¢Ø!e'£ÌðÅÎAFgb© ²kJÉ“e>¤•AFg£±±9HP<48>}è0eVˆ
ê%ÔÙì«XVo (|6Ù•¡©i†ìZHX Ð0aàTÁHDà"j!mZÑ* ::T¡ÜÐ+¤·-»F^ž ÁÛha°qzò²d D + =L```Š
¢Vv<56><76>,NÁã˜B !­ÂȣİÉ.…Œ†ƒØ24 +bÀƒ ”†‡ÀB@J;ÛöÒÀɃ´à@­<˜ ³É.æhN®8¼³ÙPp >Ÿƒø8" Ž<C2A0><C5BD>:BŸƒç!SL§a£NüQ:%hćà“]Á­j= Xx(¾¤œ»Iˆ“+Íã¨ìCàÀ”<>ÆFBc<ˆ88 ¬xD'Ü'%³Ë<C2B3>¥Ñ±9Cf¡¡!"G¾ )XŒGv"» 4 O„ˆ„Æ ‚%UÊ@:½™3ht6YI'ÄÒ’]ûÁ2Ze´
|IvÒ°<19>]J6`úHqL x, hJ`8m<¢ËA
ËCÀÈ`G(¥  ”ŠuHm*J"ë)!­Àì:Ù@X8² ‘ñ YXZv˜(|ïµÙgc€!x ®d×Kv2Ê•<C38A>ÕYP•xŒü™<C3BC>´ì8ÑTÙ5NcÁb¡€³0*ͳm™B!m[F@vQ|
øä¢W +®@¡o`İi}$GsÓñ`mÙ•€ ¡!«!@ÁlZ
*Ö¶5˜²ÃdB
\
˜¼lÙ•ÀÑœô, œdÅÈ(Ó9XR…ˆf5Œbò&/Ë<C38B>)#H0Ù5 p'ZNéÙpd[JAÂ$³aÄhY¸P@±HŽ„LR »$X0HHH4JL":
‰Ñ™|¼<>‰ÕGƒ€‡<E282AC>V)"€r¢±Â¡:øÈhD\h#Š<01> G*È®d<0E>86.¶ .ÙI,T2±<Á#‰¨ÈÕ«´ÑTá+R€Hˆe$b¤¡/F<>ÅqVv…|F;ð¤hàã$A”¤<E2809D>þX¤X2Ç⸠I(³ c[6
^%4p1”!!-Ò 4+ž]Ò9`%T¬€tÉg°6 ¬MƒÂydq\v]t(”pTL Ù@ù ÙH² %úŽÁƒƒŒNÁɦjS´"¨áhNNlidó`1l[vYtÅÄA…Š@”'ŒlaÈ.
ˆŽPŽ„tLa€O€C†rd£%SàN^¶ìZ©
XX<Y쀬-»V<C2BB>´ñP>-ídca
=-»VÞFCJ
L‰°°AV†£9iÒ<>l ,†mcU œ„¼¬ÕÙe€u6/°‚] <20>l+o£á¥xNž@½m¬Š
'V%m Ù?Œô䃢-<02> ΀ÍSÒ­E,<¤•H7 Tüa†¢a“]ï´ Yu "P<Z `;(w6+Pˆ<50>É£ÀqU ’…ì
eóÀñ=X´\„pJ,`”ÞÈcñbqñqض<C398>f2ÊÎ †CŸ&"RrB"ãaBÂ…å<E280A6>AQ´¬€0­çUœ<>Œ‡÷¼ @Œô‚]IE,X$]¬Žòè(Áð`FD)Ÿ-$5a<35>&wAC@c|2NFÙ2á<32><C3A1>BiãJpx x޽8<C2BD> °²qe >òŒ<$r‰@AùKh™)€±á
©Y2Eàqp2NNüÐâ!Ò%Œdp"-=¼Ä-Ä<02>ª€Y<E282AC>]šÆSïAPÒæ„@%Aòá0hÑ@8¨ + P´È Y Ââ<lh"D'qä,Žcygã0q2J<32>SJÅ3á™Ë‚Ê&#Y)œˆ ø`¸ðd×XŒŠ‡<15><>ÅËzH+Âce2;!™"5lŒXD1Ñêp`
Ðk±á$·V6
'9(?ñÎ&»0ͤ*i ‡[À®V´U CêTaJ°VJ;™HèÓ
<EFBFBD>22ÃiEúDè§”S+¥™$L)ÕGâÓ"¡¨¶’'UR'Õ©EÚh¥T¡ÆByáxyj±4ОP¬T)%‘Á²©ÐÚ<-ª-ô°l$MkUJeÑZj[)©R*HhÒ&z¤
M©N
õRO
…:R(-/+Z´i)Mtú¨P<š
©E"hÒ&ÚÕP§ŽçKʨX­H[Çê´•XÈ¥ J±¤^0Ö)E…i¬ EJ<45>R‹„­”^XXé„Z­HXëE5N˜V"¡V˜ª…™4-…½¨ØTX K<>H**©SëdÒ°”
KP+ìE5€Æby`.ÊH0 ÓP§,EÂTR'H,,¥­ca˜¶Ò´<C382>„i,m¥#9À°”
3i,„Jh*¤ (£UÂíô<C3AD>¢
h±H!ˆM…ÊÓÖ±qªØH¦©Åae¬Jx´µy>ÊbÙH*ÚF‡I”¡*áùd<EU@ÓP%Hc½h(”ª”:m(ͤj1­VÕb¡Z¬Õ
Õbm«•Ém+Õß­Â{Qß'Bƒm°2´”ZJºÒR¹­•:¹Ò¾˜X+•[ ´R¥R$Ò©•"¹U´Öi«[ ”zIq¬Í´­ Z±6ŠªÀê„*¹UZZ'É™¶’6ÕÊê„Jq¦­•ÒN,%
Åm¨ÒŠÊ¦r¦­”R9±4“CQ@QµR§MK<4D>P)šÉ±´¨¬ËÖ©…¢*pJ ÅÚT)©“¦BZ¥IÓP( ű´¨¤T­¥m«U‰¤¢¥kEZµ¨JŠr¬Õ¦™H+,ÇZÀm«
…”Ò@<40>Z¹"¡6M´"€ÓP¤jUb9ÓVêèªVІê4RËÉm«Õú«ZT)Û
er­T
J@C9ÖŠQ^X®•JÙ\JŠÊm«Õ¦ pÚ@Ø(˜IK<49>R4Pf#¥N(º±[À±V,jP)-<2D>åX«MÅ­ ZqÛjUÚKʪµi¤T%·
ÀŠ”2<EFBFBD>FP­ph)<E280BA>ÒpÊÀ°\ OéÂT)) Õ"¡PV'JcmšÚA¡H(”ª¤’"mš
iåPTK•:aª_jGr`¨Jx°U Ï
µ:¡>Ê"<22>PìÔJyÆ‘<01>q82"0Õo±H3i¦MUÒ68h¬Æby4
©µ©Vˆà´J'PcP<63>T´
€†Zµ¨JØê„*¡H(˜
[j+uÚV&€jµÂÒ,°(áÑÖ˪õÁX+Dµ¼œLš<05> k;…¾þ¯Tñµ©Åqb<71>>R´ŠÝÛ­Çרs×ýR•4Žh¦”
¥}ž2Ä০»å†*©Ó
ë”"}hœZ$”¶®€åJ•<4A>i*¤©m-ê%$áaÉŒR*œ6ÕÈ7E«à@¡´w¤hºoS´ŠÕ…Ò>ÚÒX))¤–;톘»<CB9C>
K)Ru©¤NÐN°SZ+.¡o´R´ŠÖ)åÑJÑ*Fè%Ò
#]%åÑJ¤PkEå´á­˜ŠzCl**db:m¢¿Ì¢ÚZ,mSÙ´´<E280B9>©Z/LÂÃÂ"µ¨V¬
¥*µ;­LBL&Òê1 3<>6l€RJÛH+VŠ´µ°(áQÁZ/**Ųp{<7B>tB°S+EzÚ
€ÖÂ´Ô ÕzQa%JJujP/-'¶i+Ò

ÓZ$”ŠuÂrÃÛT(U‰„Eg÷'€“„§uB<75>PH­h˜&À¦±´-S•ðxHÚæY©¶RX­­¢EÕj©P2õ©gª©»Ò2<C392>ðÃÝ·®0B—ùZ+*'me€´Òö¢âÐJ'” …¥¥T)“ÆRR¡ô„¢ql$X`m$X`-CªÅz)™°Rª%¤ ¤¡V-$Ö‚ ,­õrÚZ-RJÛL¨ÓJ‰<4A>ÕÒ84
é´‘PRd"µ´–Õ­ø‚" <09>ÒLª”fR¥P-0d4“ê¢Â„ÃÀ`ÒZ%
¥q°$2Ixxp0
J¢h)Ú“L/ªF¦Vh…«€!JLQ˜Ùðdd0eÙH<03>P+Ö ”ÁZ) Îó?a|3b©µ<C2A9>.2˜´µ84“ªtJyp0<EFBFBD><EFBFBD>¤BimžŒIv%`šM©N­K•ÁL²kCÕ<43>Ò6Z©“*EÒ68X@Ú6Ï)¥:µJ§Œc “ÞÞëTq•2®K (Z«¥¡H¦T¢<E28093>8ØÅÀ¹L ˜bzm€`y™°$!€Y`©
`‰M`y
‚ååÁÓÇêõ½5£K,><3E>зß<C39F>
»ŸëoG<EFBFBD>ÿ\bþbÇþ׺û]^X^$½0°ÔP«çJÀ´ÒÓ<>´P/¤
D s-`)¨Rœ‹¥¡hq´6OÌLÅ2W1Í\šÌeJE8LÃY)½¨:0ŽfÓL¶i*¤ÉSPp­H+˜)õ¢Zµ¨>ZëôÂ<C3B4>mš)uJYi2@ZZŠ„Êh¥N¤Š<C2A4>”"¡4N,SJCA`+,”SÅJ©EǦj<C2A6>L˜§VI[±6­tZY6•
´6Okó¬´H)ŒTJ¥ê@©TØ
ëeƒÀ6­Õ"•´k
U:µ6RF ë„R¡´ À±R¡4R-*Ee0
¦m$TH³:\Ó¨¤N%ÒÖ<C392>e0M«¤@ÁÀLšNªUÒ>2—*%%±zQm¢ÔIå•a/.•Ô©•:m)Õ…Éꥥq¨¨Pª-#Ë.V+¥µ´¨>¦TD].Óâc²)­Õ"¡BÛ
]ßǸ17ª&tÛ7<C39B>6É·Öù ©“ÊiÃÐTHÙ@;}¸I31¥" µH©Mk¡<&¯Þ؃ŠEÈŠÁöÅ^ÆdVÿv×O!ÖÍùàGïvÿ±Äâû†<C3BB>ŸGO_—Xn2¦þV[ÐNªŽ”¶Â0I•ðdWc½ -LRN&­”ÖJ©>&©“ŠaòÊÚ´H-/°õ´<C2B1>¦²:”„
µ6<EFBFBD>‰£4“Ê"Jx²K±
E*qh*©Â¤W“][`µN)<29>MC½ldvs´6<C2B4>R]` ÐN<68>¹ËçO1wo!ê䮽1wœ«µH¨UÖ3b1{Y,©e¥Ú4$)Õ餢mÈ$¶!ÚȲ+±JêÔjiš
©EÂä%<Ù…-h*$–‡ Kx² þc †ÒòraB<61>^ LV%<еH-*¤¡N!—ðdK‰„À ´ŒÔJi*$ ,Eàƒ-°6Jõa±´jËh¥´
”eW¿ß<EFBFBD>ê
<EFBFBD>ûcÌÛj§Û·i„1nz|]»bqS#GgíèXÚÆi”e×óÇøíb튭„JêdJ½¨ ²ìbÈO±M'$@ŠôɲË"S]€Ø.ç-g±­E¥T'MŠiåL©Õ¦ÂR"¡`ª¦€©TT(Lk<4C>V
Õ:udÙ¥É`š”JÕBZáÀPi¥t²È` *”e»to±…”êÄÚ´(áɲ+]/¦ÙÈlªik±^P( [ØT-’¶}²ìÂÒ±†¢2y820YÙ•±¡4"2* ’…QDJVd ¤<ž“Š&#%dìiPYp4^ÖÃÒá”a<>l8³À)ÉdDØ øD€€~ (Zf—·´à‡ED(¤<>Ð'äã$¤Ñ$ÞÙ´Š…+‰w6™!<21>Mxg“v$Þ1(øH†‡MLˆÈ8PûD¼ '³«€t²,R²'+""%ïl^ CÄöK±áÞÙ¨èïl4$(<28>Mà ðΆóÑí€Ò;€¶;¸$„“Ù¥ÞÙd†I$âTðÂáQ<C3A1>"$"­ £å
B#ÍBdŒ¨"¥”áÉ.‡‚Ø`xpÐl‡w6
­°™Œ’=4JªÞÙ@ ‡r4¼³Ñf„E”4¼³qå<>ÂHó¡`¤ÎÔ*"Í®„ ”Œ „HU>+…ö% Ù¥PÚTlH­¨¡X9HÐä„|#±+‰¼gôÀ9'
\êÄ$eQÒÀɃgÙ(
æ<EFBFBD><# (Q'U!ÂPÂ"2XbˆÁÄâ< ˜>/»@6D$ŸˆD ‰€^>ÙëŒ "LšÝ8h8y6T²÷Ð9܆*P6º-¨,8Ùo”ôeÃâäIÈtlx41a€é=-*%Œñ¶Á<19><4E>È[<17>Š*°<h<¨´ÓÊ  2$<n
Võp¡"ºàh\<$ Ëê!»T¤¤K˃{HyOL<4F>¨4m ©L)†+ã`¯¼:ð`<+AQUÃJÅé§B”(,U‰8©z‰Ð¯XV[œ`8ôæAä*Dª¹È®K,àT ˜qT”Â;…ME0•Dhf¡ãB@ 6ÓMJ8'ä" ¸<C3AC>sRa”ÐàœÜP-'<27>…¥eÆ„¤%)b€§{Ta…E²FÊ"CpÂ10m0x˜3.=ïl|#šÈg™0„æƒñ˜Yœ„,>ž¡£A}´<»º@‹“.
[2)©ÅÉ̃Š4KZ%<25><EFBFBD>{-,™ÅÉš0¦dQØÄ¢%,I€„…“
,mb
'U+;[ÂI%€ÂI<C382>DŠ&Ç…Ô¤”f·CAB8iuh¡ìÁ¨'4ŸT<8©ádÛœŒ’@Ag³éðΆY8,8L ƒƒà‘ÁÉF'7ŒŠe5Di8q ´EepÒdÀ
Ov=dpòãáÀº!âäKÉjF@ !ÁÀ”NBTò•4”(†G…¡€9…OgÓ<67>] úpNkÁ#¥+2 —ƒ<14>V@K—É‚NvTTtÅ¡Ê01 mº"%FÞÁ±HÀT¬\0 &“L#È`bf!%]:­Õ‹ J+…²­¸h("((+#¬% x ÙBDR!šKÈJ<08>ˆ2bÒÙ¬j$¤b1Ê<C38A><E28093>-#¤£ÊxaÉ XÉè¸|
XZ†HɆQbÑÙ”Œ”„d¤>20(ÑJvµ´Šhd$„dÌÈÈ‚`5ó²/B ¶Ž
â ¢ð…ÒË–]"*[âJL"<1F>. ðFa@Ãा, Ƴ*Ã;ɈB<71>b ˆ˜ØÚõIGN D\&·L“. <09>Mr\g$B7£„ÎF¡3¡{ÐÙhš“#ƒÑ‹†€ˆ\y
+b€!`y°V\ˆ‡ƒ€Í¦ºèŒãZà4lÛGÛlÖ…ÄÉ<C384>„ήìZølMv1H 0ô¶aLBÙURÀ£Ã3°á°ðÙ4
±<C2B1>æôɸ<>H(OÅbò<{@â„f;(Ð( ²+¡À‰‡´¼Æ‹Â¦¢-%“õ°D2ž àppD Øxéð‡–ìÊ®ìú`áäÁ &“°mÌÄ"cb<63>¦Ã Gh0Yò€Y¸ Á„œ+m0,<2C>
¦â€£€áˆQÀ,sÚ „+m0ÙõàJL¹Ð`^‹„"¤íJëXx>X¬;„P6ª
•lptEB<>”Gñ°<C3B1>d(2sàò±Ê®ìÊ®ì:€xÈ®UÈÆÁÇà Ç%õÀqBŠÈ®ìÊ.6™À”
C*`46V. ˜Ì.J<>ò\ P%!ÕTKå
WtÕ€²Q‰ Gçmxt<78>)è‹ b?
ŸìÚh˜ <20>ñÛ–]Ù•±à°m <0F>Î(»v@vµxFíZÙ•]$­<>è`¡%ÂÀB;<Rë@ E?% Íãá<C3A3> ÎvXˆR‰H³ ³4ˆD PÇD•±Þ<>FR,ŸÏŠcðÒÙ D<>L†"»NZÄÂÃFÅ<02>I¨eBÔ1è08lbvm°L0<(IË$4@€s <1D>]+
Z]ÙuÁ:<3A> 
±N- Çãò §ŒQÄHɧÓ]& ¸D,<|HTYv‰öckÐQyaL<>|²Ë´QC'ˆ¡ÖÆyÀÈ ½€
JPPŽ<EFBFBD>B²E0¬:R.NÞÈ3`…ôJUgÂ7 Ð…FÉó<C389>«ÏEÄ€ˆMñ¡"<n ¼Œˆ³Kãå`E%0édjeÁa±TW,#šÅ `É‘Sf9&!NF ˜xt2
fåm4¬¼<C2AC>†ÍÊ€I$Â<1D>H#—_Á¼pxX¶¨1d×6à£Ã³"rÚD°'ÃÑ qĤtBŠ@}x<>`e<>ÒØ4dhiÂlÙFª<17>ƒ<EFBFBD>‡Í tú<¸„F"T²iÈpÀ(<28>6eá”TCÊg;Ùp “•“ÒfWsŒDèÊV8..ä àa óÂá™x$@™ žì:q…H20 R ‘†
t@am°4R@
êðdW… Á(Á$´ñN"0-“<>DÅǃöÁJ÷ hÊâ`X:L*@Ló mæÂ•6šŒÈ3a2IÀ„µÁ<dD6˜Žf7@ #šÅ¨Ú€'N #<11>÷ŒN¢€ƒÉf׿
…‘/<2F>@ AÃ;Þpivé…w6Ri(5€(± '5'=#Õ \xgƒ¡¢! Ÿ „(»”Â;
Ϭè˜<,'ŸÑÇw6
õHPŠMC‡DóNn2JSfÕ¡¸øpà‰Þ,$œ\ äÄIpŸ”$Á<ì<>*¤³ÙŒ6.w6­€v0j™xgã2-Nf:CË-Ä;ÒŠ<>Óá IÀŒ^6˜Ž&#r¡¡@0iÉÁ<C389>
qvq¼† <0B>„çá=.
@:©XpX ½ 8¡(çäY-NFR„Av2àŒ†¨³qïl„ÂÓ :XY
5jyˆe•'³k31¡ÎãAPGp2J<0E>€Š5ÐÅÎA<C38E>ƒ”Ê<C38A>¤ ò:<78>Í(äáÀ(.â<>
´ØˆP«8xˆ@,ÊX>$Íx AU4@RçÁÂITv2JWAᤄ€
1eV<EFBFBD>@,GÃÁ*| <0C>44¡ '£t(œÌ®g`JRBÊ wxg³a*N %M ˆ<>Ëê)O‡gEñ+ŸÏg¡Áã¥g#„“$€(ŒÐ[ù¤hJ4ÈFƒ~t#ÂI<C382>+Ðl<C390>Ku80H'Y&É*
ds#ƒ“®"ƒ“/
œLpn>±E%ÁÉ(×CÄÉìjµÄÞ¸ACÄIN…ˆGÃÁ(„D%†àd”šQveÅCÉ u*6áAÀBI„",,d2¦Ì.Lw8©Òé±úpà(<#<23><>ˆ
Ç9÷Iyg“0 )#ïlXXZf䜌“Q&˜€@lH[áxHÃÁRИ„FZ
H*!1Ê„^²ЦdÀ
«BÕâ<C395>­,ºÒRÀtP'
¦Ä€£€É`<60><>¥³ÁL <0C>2<18> /!REZˆ<5A>°<œ4¤¼ÇåKëa­@,™]--ÒÒá!Å£áðPRЦä†1@RÉiÃ(H„X¡ÍH„0¨x…ˆmt°âÏ vƒÇÉA<C389>;yÙ@©Ì”-.<2E>ŒÅ&n>J"šìbX)ÈlCq1sñRK<E280BA>Ç
G+J;¨àH±dÕGÁÂ+ àl„<˜
ˆÍJ©ÁÓªð„D@-VCO*TCÃ@³ÙµzH±à€hØ0Ë„?`H¤
G°^¸  –”*Ã;“V<E2809C>YGGȃIY¼`€@
 `ÄÈJ(‚Ì' d,+"ŽŒ Å 2@2ŒŠÉÇCJ¼aŒL'…ƒÖn[k£9™<39>&/ÛGJ_6ÕkãDlÛ¦±8Éì:á“lñ†I¦€””$Æ
Ij<D2ó¡0 _J>ÕÊ<C395>LYayäÇI¢##[p䊆ÂH•”Š4¹<34>¨H…¨!ÊŠ“*”
ŠP.(ˆHN<48>ˆˆT±œ:6Ù²Á `]: O<>‡Y`<z€1ØŒd˜<64>”ѱáÕ3! Åy8mt:N8*<!ëA°øð<07>£°àdB>­T³k´y,<ŒÄò°±% OJ&ºx'UîÄ"[5t^V^V<D8¼÷IQpŸ”ä|4¸ÌB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;}
</style>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st0" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;}
</style>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st0" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#17541F;}
</style>
<rect class="st0" width="3212.8" height="1212.8"/>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st1" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#17541F;}
</style>
<path class="st0" d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path class="st0" d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path class="st0" d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path class="st0" d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7
c-8.4-2.1-15.7-3.1-22-3.1c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path class="st0" d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path class="st0" d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path class="st0" d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path class="st0" d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path class="st0" d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path class="st0" d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path class="st0" d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2
c-4.2-5.4-6.6-11.9-7.1-19.6h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9
v-24.7c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3
c-4-7.3-6-15.5-6-24.6s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8
h19.4v107.8C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16
c-2.6-4.8-6.1-8.5-10.5-11.1c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4
c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1
C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path class="st0" d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35
L2968.8,996.6z"/>
</g>
<path class="st0" d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st1" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="900"
height="900"
id="svg3923"
sodipodi:docname="square.svg"
inkscape:export-filename="/tmp/test.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata3929">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3927" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2096"
id="namedview3925"
showgrid="false"
inkscape:zoom="1.1360927"
inkscape:cx="635.07139"
inkscape:cy="606.383"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g3921" />
<g
transform="matrix(10.638298,0,0,10.638298,106.38298,-206.38301)"
id="g3921">
<defs
id="SvgjsDefs1018" />
<g
id="SvgjsG1019"
featureKey="root"
style="fill:#ffffff" />
<g
id="SvgjsG1020"
featureKey="symbol1"
transform="matrix(0.10341565,0,0,0.10341565,-11.43874,18.048418)"
inkscape:export-filename="/tmp/test.png"
inkscape:export-xdpi="116.02285"
inkscape:export-ydpi="116.02285"
style="fill:#17541f">
<defs
id="defs3911" />
<g
id="g3915">
<path
d="M 231,798 C 227,779 219,741 218,741 49,640 69,465 125,365 c 12,126 235,213 105,367 -1,2 6,26 12,48 26,-44 65,-97 63,-102 C 145,288 645,258 749,16 c 47,234 -24,596 -426,688 -2,1 -73,126 -76,127 0,-2 -30,-1 -26,-11 2,-6 6,-14 10,-22 z M 330,625 C 267,476 452,312 544,271 356,439 324,564 330,625 Z m -104,79 c 51,-59 -9,-160 -45,-193 61,105 57,166 45,193 z"
style="fill:#17541f"
id="path3913"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1238,8 +1238,8 @@
<context context-type="linenumber">82</context>
</context-group>
</trans-unit>
<trans-unit id="7860582931776068318" datatype="html">
<source>Add document version</source>
<trans-unit id="8035757452478567832" datatype="html">
<source>Update existing document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/admin/settings/settings.component.html</context>
<context context-type="linenumber">280</context>
@@ -8411,8 +8411,8 @@
<context context-type="linenumber">832</context>
</context-group>
</trans-unit>
<trans-unit id="5203024009814367559" datatype="html">
<source>This operation will add rotated versions of the <x id="PH" equiv-text="this.list.selected.size"/> document(s).</source>
<trans-unit id="6390006284731990222" datatype="html">
<source>This operation will permanently rotate the original version of <x id="PH" equiv-text="this.list.selected.size"/> document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">833</context>

View File

@@ -277,7 +277,7 @@
<div class="col">
<select class="form-select" formControlName="pdfEditorDefaultEditMode">
<option [ngValue]="PdfEditorEditMode.Create" i18n>Create new document(s)</option>
<option [ngValue]="PdfEditorEditMode.Update" i18n>Add document version</option>
<option [ngValue]="PdfEditorEditMode.Update" i18n>Update existing document</option>
</select>
</div>
</div>

View File

@@ -8,10 +8,8 @@
[ngClass]="{ 'slim': slimSidebarEnabled, 'col-auto col-md-3 col-lg-2 col-xxxl-1' : !slimSidebarEnabled, 'py-3' : !customAppTitle?.length || slimSidebarEnabled, 'py-2': customAppTitle?.length }"
routerLink="/dashboard"
tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" height="1.5em" fill="currentColor">
<path
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1.5em" height="1.5em" fill="currentColor">
<path d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>
<div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
@if (customAppTitle?.length) {

View File

@@ -1,22 +1,22 @@
@if (customLogo) {
<img src="{{customLogo}}" [class]="getClasses()" [attr.style]="'height:'+height" />
} @else {
<svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" [attr.style]="'height:'+height">
<path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/>
<g class="text" style="fill:#000">
<path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/>
<path d="M1381,416.1c-18.1-11.5-38.3-17.3-60.5-17.4c-32,0-56.9,9.9-74.7,29.8v-20.4h-84.5v390.7h84.5v-164 c17.8,19.9,42.7,29.8,74.7,29.8c22.3,0,42.4-5.7,60.5-17.3s32.3-27.5,42.5-47.8c10.2-20.3,15.3-42.9,15.3-67.8s-5.1-47.5-15.3-67.8 C1413.2,443.6,1399.1,427.7,1381,416.1z M1337.9,575c-10.1,11.7-23.4,17.6-40,17.6s-29.9-5.9-40-17.6s-15.1-26.1-15.1-43.3 c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6s29.9,5.9,40,17.6s15.1,26.1,15.1,43.3S1347.9,563.3,1337.9,575z" transform="translate(0)"/>
<path d="M1672.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6c-20.4,11.7-36.5,27.7-48.2,48s-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S1692.6,428.8,1672.2,416.8z M1558.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H1558.3z" transform="translate(0)"/>
<path d="M1895.3,411.7c-11,5.6-20.3,13.7-28,24.4h-0.1v-28h-84.5v247.3h84.5V536.3c0-22.6,4.7-38.1,14.2-46.5 c9.5-8.5,22.7-12.7,39.6-12.7c6.2,0,13.5,1,21.8,3.1l10.7-72c-5.9-3.3-14.5-4.9-25.8-4.9C1917.1,403.3,1906.3,406.1,1895.3,411.7z" transform="translate(0)"/>
<rect x="1985" y="277.4" width="84.5" height="377.8" transform="translate(0)"/>
<path d="M2313.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6s-36.5,27.7-48.2,48c-11.7,20.3-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S2333.6,428.8,2313.2,416.8z M2199.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H2199.3z" transform="translate(0)"/>
<path d="M2583.6,507.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9 c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8 c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7 c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6 c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9 c34.7,0,62.9-7.4,84.5-22.4c21.7-15,32.5-37.3,32.5-66.9c0-19.3-5-34.2-15.1-44.9S2597.4,512.1,2583.6,507.7z" transform="translate(0)"/>
<path d="M2883.4,575.3c0-19.3-5-34.2-15.1-44.9s-22-18.3-35.8-22.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6 c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4 l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7 c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6 c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2 l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9c34.7,0,62.9-7.4,84.5-22.4 C2872.6,627.2,2883.4,604.9,2883.4,575.3z" transform="translate(0)"/>
<rect x="2460.7" y="738.7" width="59.6" height="17.2" transform="translate(0)"/>
<path d="M2596.5,706.4c-5.7,0-11,1-15.8,3s-9,5-12.5,8.9v-9.4h-19.4v93.6h19.4v-52c0-8.6,2.1-15.3,6.3-20c4.2-4.7,9.5-7.1,15.9-7.1 c7.8,0,13.4,2.3,16.8,6.7c3.4,4.5,5.1,11.3,5.1,20.5v52h19.4v-56.8c0-12.8-3.2-22.6-9.5-29.3 C2615.8,709.8,2607.3,706.4,2596.5,706.4z" transform="translate(0)"/>
<path d="M2733.8,717.7c-3.6-3.4-7.9-6.1-13.1-8.2s-10.6-3.1-16.2-3.1c-8.7,0-16.5,2.1-23.5,6.3s-12.5,10-16.5,17.3 c-4,7.3-6,15.4-6,24.4c0,8.9,2,17.1,6,24.3c4,7.3,9.5,13,16.5,17.2s14.9,6.3,23.5,6.3c5.6,0,11-1,16.2-3.1 c5.1-2.1,9.5-4.8,13.1-8.2v24.4c0,8.5-2.5,14.8-7.6,18.7c-5,3.9-11,5.9-18,5.9c-6.7,0-12.4-1.6-17.3-4.7c-4.8-3.1-7.6-7.7-8.3-13.8 h-19.4c0.6,7.7,2.9,14.2,7.1,19.5s9.6,9.3,16.2,12c6.6,2.7,13.8,4,21.7,4c12.8,0,23.5-3.4,32-10.1c8.6-6.7,12.8-17.1,12.8-31.1 V708.9h-19.2V717.7z M2732.2,770.1c-2.5,4.7-6,8.3-10.4,11.2c-4.4,2.7-9.4,4-14.9,4c-5.7,0-10.8-1.4-15.2-4.3s-7.8-6.7-10.2-11.4 c-2.3-4.8-3.5-9.8-3.5-15.2c0-5.5,1.1-10.6,3.5-15.3s5.8-8.5,10.2-11.3s9.5-4.2,15.2-4.2c5.5,0,10.5,1.4,14.9,4s7.9,6.3,10.4,11 s3.8,10,3.8,15.8S2734.7,765.4,2732.2,770.1z" transform="translate(0)"/>
<polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5 2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 " transform="translate(0)"/>
<path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/>
</g>
<svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2670 860" [attr.style]="'height:'+height">
<path class="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g class="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>
}

View File

@@ -84,7 +84,7 @@
<input type="radio" class="btn-check" [(ngModel)]="editMode" [value]="PdfEditorEditMode.Update" id="editModeUpdate" name="editmode" [disabled]="hasSplit()">
<label for="editModeUpdate" class="btn btn-outline-primary btn-sm">
<i-bs name="pencil"></i-bs>
<span class="form-check-label ms-2" i18n>Add document version</span>
<span class="form-check-label ms-2" i18n>Update existing document</span>
</label>
</div>
@if (editMode === PdfEditorEditMode.Create) {

View File

@@ -3,7 +3,6 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { DocumentService } from 'src/app/services/rest/document.service'
import { PDFEditorComponent } from './pdf-editor.component'
describe('PDFEditorComponent', () => {
@@ -140,16 +139,4 @@ describe('PDFEditorComponent', () => {
expect(component.pages[1].page).toBe(2)
expect(component.pages[2].page).toBe(3)
})
it('should include selected version in preview source when provided', () => {
const documentService = TestBed.inject(DocumentService)
const previewSpy = jest
.spyOn(documentService, 'getPreviewUrl')
.mockReturnValue('preview-version')
component.documentID = 3
component.versionID = 10
expect(component.pdfSrc).toBe('preview-version')
expect(previewSpy).toHaveBeenCalledWith(3, false, 10)
})
})

View File

@@ -46,7 +46,6 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
activeModal: NgbActiveModal = inject(NgbActiveModal)
documentID: number
versionID?: number
pages: PageOperation[] = []
totalPages = 0
editMode: PdfEditorEditMode = this.settingsService.get(
@@ -56,11 +55,7 @@ export class PDFEditorComponent extends ConfirmDialogComponent {
includeMetadata: boolean = true
get pdfSrc(): string {
return this.documentService.getPreviewUrl(
this.documentID,
false,
this.versionID
)
return this.documentService.getPreviewUrl(this.documentID)
}
pdfLoaded(pdf: PngxPdfDocumentProxy) {

View File

@@ -1,5 +1,5 @@
<pngx-page-header title="Dashboard" [subTitle]="subtitle" i18n-title tourAnchor="tour.dashboard">
<pngx-logo extra_classes="d-none d-md-block mt-n2" height="3.5rem"></pngx-logo>
<pngx-logo extra_classes="d-none d-md-block mt-n2" height="3.2rem"></pngx-logo>
</pngx-page-header>
<div class="row">

View File

@@ -1459,25 +1459,22 @@ describe('DocumentDetailComponent', () => {
const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument')
const errorSpy = jest.spyOn(toastService, 'showError')
initNormally()
component.selectedVersionId = 10
component.editPdf()
expect(modal).not.toBeUndefined()
modal.componentInstance.documentID = doc.id
expect(modal.componentInstance.versionID).toBe(10)
modal.componentInstance.pages = [{ page: 1, rotate: 0, splitAfter: false }]
modal.componentInstance.confirm()
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/bulk_edit/`
)
expect(req.request.body).toEqual({
documents: [10],
documents: [doc.id],
method: 'edit_pdf',
parameters: {
operations: [{ page: 1, rotate: 0, doc: 0 }],
delete_original: false,
update_document: false,
include_metadata: true,
source_mode: 'explicit_selection',
},
})
req.error(new ErrorEvent('failed'))
@@ -1499,7 +1496,6 @@ describe('DocumentDetailComponent', () => {
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[0]))
initNormally()
component.selectedVersionId = 10
component.password = 'secret'
component.removePassword()
const dialog =
@@ -1512,14 +1508,13 @@ describe('DocumentDetailComponent', () => {
`${environment.apiBaseUrl}documents/bulk_edit/`
)
expect(req.request.body).toEqual({
documents: [10],
documents: [doc.id],
method: 'remove_password',
parameters: {
password: 'secret',
update_document: false,
include_metadata: false,
delete_original: true,
source_mode: 'explicit_selection',
},
})
req.flush(true)

View File

@@ -73,10 +73,7 @@ import {
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import {
BulkEditSourceMode,
DocumentService,
} from 'src/app/services/rest/document.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { TagService } from 'src/app/services/rest/tag.service'
@@ -1629,23 +1626,20 @@ export class DocumentDetailComponent
size: 'xl',
scrollable: true,
})
const sourceDocumentId = this.selectedVersionId ?? this.document.id
modal.componentInstance.title = $localize`PDF Editor`
modal.componentInstance.btnCaption = $localize`Proceed`
modal.componentInstance.documentID = this.document.id
modal.componentInstance.versionID = sourceDocumentId
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.documentsService
.bulkEdit([sourceDocumentId], 'edit_pdf', {
.bulkEdit([this.document.id], 'edit_pdf', {
operations: modal.componentInstance.getOperations(),
delete_original: modal.componentInstance.deleteOriginal,
update_document:
modal.componentInstance.editMode == PdfEditorEditMode.Update,
include_metadata: modal.componentInstance.includeMetadata,
source_mode: BulkEditSourceMode.EXPLICIT_SELECTION,
})
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({
@@ -1691,18 +1685,16 @@ export class DocumentDetailComponent
modal.componentInstance.confirmClicked
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
const sourceDocumentId = this.selectedVersionId ?? this.document.id
const dialog =
modal.componentInstance as PasswordRemovalConfirmDialogComponent
dialog.buttonsEnabled = false
this.networkActive = true
this.documentsService
.bulkEdit([sourceDocumentId], 'remove_password', {
.bulkEdit([this.document.id], 'remove_password', {
password: this.password,
update_document: dialog.updateDocument,
include_metadata: dialog.includeMetadata,
delete_original: dialog.deleteOriginal,
source_mode: BulkEditSourceMode.EXPLICIT_SELECTION,
})
.pipe(first(), takeUntil(this.unsubscribeNotifier))
.subscribe({

View File

@@ -830,7 +830,7 @@ export class BulkEditorComponent
})
const rotateDialog = modal.componentInstance as RotateConfirmDialogComponent
rotateDialog.title = $localize`Rotate confirm`
rotateDialog.messageBold = $localize`This operation will add rotated versions of the ${this.list.selected.size} document(s).`
rotateDialog.messageBold = $localize`This operation will permanently rotate the original version of ${this.list.selected.size} document(s).`
rotateDialog.btnClass = 'btn-danger'
rotateDialog.btnCaption = $localize`Proceed`
rotateDialog.documentID = Array.from(this.list.selected)[0]

View File

@@ -37,11 +37,6 @@ export interface SelectionData {
selected_custom_fields: SelectionDataItem[]
}
export enum BulkEditSourceMode {
LATEST_VERSION = 'latest_version',
EXPLICIT_SELECTION = 'explicit_selection',
}
@Injectable({
providedIn: 'root',
})

View File

@@ -29,21 +29,12 @@ from documents.plugins.helpers import DocumentsStatusManager
from documents.tasks import bulk_update_documents
from documents.tasks import consume_file
from documents.tasks import update_document_content_maybe_archive_file
from documents.versioning import get_latest_version_for_root
from documents.versioning import get_root_document
if TYPE_CHECKING:
from django.contrib.auth.models import User
logger: logging.Logger = logging.getLogger("paperless.bulk_edit")
SourceMode = Literal["latest_version", "explicit_selection"]
class SourceModeChoices:
LATEST_VERSION: SourceMode = "latest_version"
EXPLICIT_SELECTION: SourceMode = "explicit_selection"
@shared_task(bind=True)
def restore_archive_serial_numbers_task(
@@ -81,21 +72,46 @@ def restore_archive_serial_numbers(backup: dict[int, int | None]) -> None:
logger.info(f"Restored archive serial numbers for documents {list(backup.keys())}")
def _resolve_root_and_source_doc(
doc: Document,
*,
source_mode: SourceMode = SourceModeChoices.LATEST_VERSION,
) -> tuple[Document, Document]:
root_doc = get_root_document(doc)
def _get_root_ids_by_doc_id(doc_ids: list[int]) -> dict[int, int]:
"""
Resolve each provided document id to its root document id.
if source_mode == SourceModeChoices.EXPLICIT_SELECTION:
return root_doc, doc
- If the id is already a root document: root id is itself.
- If the id is a version document: root id is its `root_document_id`.
"""
qs = Document.objects.filter(id__in=doc_ids).only("id", "root_document_id")
return {doc.id: doc.root_document_id or doc.id for doc in qs}
# Version IDs are explicit by default, only a selected root resolves to latest
if doc.root_document_id is not None:
return root_doc, doc
return root_doc, get_latest_version_for_root(root_doc)
def _get_root_and_current_docs_by_root_id(
root_ids: set[int],
) -> tuple[dict[int, Document], dict[int, Document]]:
"""
Returns:
- root_docs: root_id -> root Document
- current_docs: root_id -> newest version Document (or root if none)
"""
root_docs = {
doc.id: doc
for doc in Document.objects.filter(id__in=root_ids).select_related(
"owner",
)
}
latest_versions_by_root_id: dict[int, Document] = {}
for version_doc in Document.objects.filter(root_document_id__in=root_ids).order_by(
"root_document_id",
"-id",
):
root_id = version_doc.root_document_id
if root_id is None:
continue
latest_versions_by_root_id.setdefault(root_id, version_doc)
current_docs: dict[int, Document] = {
root_id: latest_versions_by_root_id.get(root_id, root_docs[root_id])
for root_id in root_docs
}
return root_docs, current_docs
def set_correspondent(
@@ -405,32 +421,21 @@ def rotate(
doc_ids: list[int],
degrees: int,
*,
source_mode: SourceMode = SourceModeChoices.LATEST_VERSION,
user: User | None = None,
) -> Literal["OK"]:
logger.info(
f"Attempting to rotate {len(doc_ids)} documents by {degrees} degrees.",
)
docs_by_id = {
doc.id: doc
for doc in Document.objects.select_related("root_document").filter(
id__in=doc_ids,
)
}
docs_by_root_id: dict[int, tuple[Document, Document]] = {}
for doc_id in doc_ids:
doc = docs_by_id.get(doc_id)
if doc is None:
continue
root_doc, source_doc = _resolve_root_and_source_doc(
doc,
source_mode=source_mode,
)
docs_by_root_id.setdefault(root_doc.id, (root_doc, source_doc))
doc_to_root_id = _get_root_ids_by_doc_id(doc_ids)
root_ids = set(doc_to_root_id.values())
root_docs_by_id, current_docs_by_root_id = _get_root_and_current_docs_by_root_id(
root_ids,
)
import pikepdf
for root_doc, source_doc in docs_by_root_id.values():
for root_id in root_ids:
root_doc = root_docs_by_id[root_id]
source_doc = current_docs_by_root_id[root_id]
if source_doc.mime_type != "application/pdf":
logger.warning(
f"Document {root_doc.id} is not a PDF, skipping rotation.",
@@ -654,17 +659,25 @@ def delete_pages(
doc_ids: list[int],
pages: list[int],
*,
source_mode: SourceMode = SourceModeChoices.LATEST_VERSION,
user: User | None = None,
) -> Literal["OK"]:
logger.info(
f"Attempting to delete pages {pages} from {len(doc_ids)} documents",
)
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
root_doc, source_doc = _resolve_root_and_source_doc(
doc,
source_mode=source_mode,
root_doc: Document
if doc.root_document_id is None or doc.root_document is None:
root_doc = doc
else:
root_doc = doc.root_document
source_doc = (
Document.objects.filter(Q(id=root_doc.id) | Q(root_document=root_doc))
.order_by("-id")
.first()
)
if source_doc is None:
source_doc = root_doc
pages = sorted(pages) # sort pages to avoid index issues
import pikepdf
@@ -709,7 +722,6 @@ def edit_pdf(
delete_original: bool = False,
update_document: bool = False,
include_metadata: bool = True,
source_mode: SourceMode = SourceModeChoices.LATEST_VERSION,
user: User | None = None,
) -> Literal["OK"]:
"""
@@ -724,10 +736,19 @@ def edit_pdf(
f"Editing PDF of document {doc_ids[0]} with {len(operations)} operations",
)
doc = Document.objects.select_related("root_document").get(id=doc_ids[0])
root_doc, source_doc = _resolve_root_and_source_doc(
doc,
source_mode=source_mode,
root_doc: Document
if doc.root_document_id is None or doc.root_document is None:
root_doc = doc
else:
root_doc = doc.root_document
source_doc = (
Document.objects.filter(Q(id=root_doc.id) | Q(root_document=root_doc))
.order_by("-id")
.first()
)
if source_doc is None:
source_doc = root_doc
import pikepdf
pdf_docs: list[pikepdf.Pdf] = []
@@ -838,7 +859,6 @@ def remove_password(
update_document: bool = False,
delete_original: bool = False,
include_metadata: bool = True,
source_mode: SourceMode = SourceModeChoices.LATEST_VERSION,
user: User | None = None,
) -> Literal["OK"]:
"""
@@ -848,10 +868,19 @@ def remove_password(
for doc_id in doc_ids:
doc = Document.objects.select_related("root_document").get(id=doc_id)
root_doc, source_doc = _resolve_root_and_source_doc(
doc,
source_mode=source_mode,
root_doc: Document
if doc.root_document_id is None or doc.root_document is None:
root_doc = doc
else:
root_doc = doc.root_document
source_doc = (
Document.objects.filter(Q(id=root_doc.id) | Q(root_document=root_doc))
.order_by("-id")
.first()
)
if source_doc is None:
source_doc = root_doc
try:
logger.info(
f"Attempting password removal from document {doc_ids[0]}",

View File

@@ -6,14 +6,11 @@ Provides automatic progress bar and multiprocessing support with minimal boilerp
from __future__ import annotations
import logging
import os
from collections.abc import Callable
from collections.abc import Iterable
from collections.abc import Sized
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures import as_completed
from contextlib import contextmanager
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Any
@@ -25,11 +22,7 @@ from django import db
from django.core.management import CommandError
from django.db.models import QuerySet
from django_rich.management import RichCommand
from rich import box
from rich.console import Console
from rich.console import Group
from rich.console import RenderableType
from rich.live import Live
from rich.progress import BarColumn
from rich.progress import MofNCompleteColumn
from rich.progress import Progress
@@ -37,11 +30,11 @@ from rich.progress import SpinnerColumn
from rich.progress import TextColumn
from rich.progress import TimeElapsedColumn
from rich.progress import TimeRemainingColumn
from rich.table import Table
from rich.text import Text
if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Generator
from collections.abc import Iterable
from collections.abc import Sequence
from django.core.management import CommandParser
@@ -50,78 +43,6 @@ T = TypeVar("T")
R = TypeVar("R")
@dataclass(slots=True, frozen=True)
class _BufferedRecord:
level: int
name: str
message: str
class BufferingLogHandler(logging.Handler):
"""Captures log records during a command run for deferred rendering.
Attach to a logger before a long operation and call ``render()``
afterwards to emit the buffered records via Rich, optionally filtered
by minimum level.
"""
def __init__(self) -> None:
super().__init__()
self._records: list[_BufferedRecord] = []
def emit(self, record: logging.LogRecord) -> None:
self._records.append(
_BufferedRecord(
level=record.levelno,
name=record.name,
message=self.format(record),
),
)
def render(
self,
console: Console,
*,
min_level: int = logging.DEBUG,
title: str = "Log Output",
) -> None:
records = [r for r in self._records if r.level >= min_level]
if not records:
return
table = Table(
title=title,
show_header=True,
header_style="bold",
show_lines=False,
box=box.SIMPLE,
)
table.add_column("Level", style="bold", width=8)
table.add_column("Logger", style="dim")
table.add_column("Message", no_wrap=False)
_level_styles: dict[int, str] = {
logging.DEBUG: "dim",
logging.INFO: "cyan",
logging.WARNING: "yellow",
logging.ERROR: "red",
logging.CRITICAL: "bold red",
}
for record in records:
style = _level_styles.get(record.level, "")
table.add_row(
Text(logging.getLevelName(record.level), style=style),
record.name,
record.message,
)
console.print(table)
def clear(self) -> None:
self._records.clear()
@dataclass(frozen=True, slots=True)
class ProcessResult(Generic[T, R]):
"""
@@ -170,23 +91,6 @@ class PaperlessCommand(RichCommand):
for result in self.process_parallel(process_doc, ids):
if result.error:
self.console.print(f"[red]Failed: {result.error}[/red]")
class Command(PaperlessCommand):
help = "Import documents with live stats"
def handle(self, *args, **options):
stats = ImportStats()
def render_stats() -> Table:
... # build Rich Table from stats
for item in self.track_with_stats(
items,
description="Importing...",
stats_renderer=render_stats,
):
result = import_item(item)
stats.imported += 1
"""
supports_progress_bar: ClassVar[bool] = True
@@ -224,11 +128,13 @@ class PaperlessCommand(RichCommand):
This is called by Django's command infrastructure after argument parsing
but before handle(). We use it to set instance attributes from options.
"""
# Set progress bar state
if self.supports_progress_bar:
self.no_progress_bar = options.get("no_progress_bar", False)
else:
self.no_progress_bar = True
# Set multiprocessing state
if self.supports_multiprocessing:
self.process_count = options.get("processes", 1)
if self.process_count < 1:
@@ -238,69 +144,9 @@ class PaperlessCommand(RichCommand):
return super().execute(*args, **options)
@contextmanager
def buffered_logging(
self,
*logger_names: str,
level: int = logging.DEBUG,
) -> Generator[BufferingLogHandler, None, None]:
"""Context manager that captures log output from named loggers.
Installs a ``BufferingLogHandler`` on each named logger for the
duration of the block, suppressing propagation to avoid interleaving
with the Rich live display. The handler is removed on exit regardless
of whether an exception occurred.
Usage::
with self.buffered_logging("paperless", "documents") as log_buf:
# ... run progress loop ...
if options["verbose"]:
log_buf.render(self.console)
"""
handler = BufferingLogHandler()
handler.setFormatter(logging.Formatter("%(message)s"))
loggers: list[logging.Logger] = []
original_propagate: dict[str, bool] = {}
for name in logger_names:
log = logging.getLogger(name)
log.addHandler(handler)
original_propagate[name] = log.propagate
log.propagate = False
loggers.append(log)
try:
yield handler
finally:
for log in loggers:
log.removeHandler(handler)
log.propagate = original_propagate[log.name]
@staticmethod
def _progress_columns() -> tuple[Any, ...]:
"""
Return the standard set of progress bar columns.
Extracted so both _create_progress (standalone) and track_with_stats
(inside Live) use identical column configuration without duplication.
"""
return (
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
MofNCompleteColumn(),
TimeElapsedColumn(),
TimeRemainingColumn(),
)
def _create_progress(self, description: str) -> Progress:
"""
Create a standalone Progress instance with its own stderr Console.
Use this for track(). For track_with_stats(), Progress is created
directly inside a Live context instead.
Create a configured Progress instance.
Progress output is directed to stderr to match the convention that
progress bars are transient UI feedback, not command output. This
@@ -315,7 +161,12 @@ class PaperlessCommand(RichCommand):
A Progress instance configured with appropriate columns.
"""
return Progress(
*self._progress_columns(),
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
MofNCompleteColumn(),
TimeElapsedColumn(),
TimeRemainingColumn(),
console=Console(stderr=True),
transient=False,
)
@@ -371,6 +222,7 @@ class PaperlessCommand(RichCommand):
yield from iterable
return
# Attempt to determine total if not provided
if total is None:
total = self._get_iterable_length(iterable)
@@ -380,87 +232,6 @@ class PaperlessCommand(RichCommand):
yield item
progress.advance(task_id)
def track_with_stats(
self,
iterable: Iterable[T],
*,
description: str = "Processing...",
stats_renderer: Callable[[], RenderableType],
total: int | None = None,
) -> Generator[T, None, None]:
"""
Iterate over items with a progress bar and a live-updating stats display.
The progress bar and stats renderable are combined in a single Live
context, so the stats panel re-renders in place below the progress bar
after each item is processed.
Respects --no-progress-bar flag. When disabled, yields items without
any display (stats are still updated by the caller's loop body, so
they will be accurate for any post-loop summary the caller prints).
Args:
iterable: The items to iterate over.
description: Text to display alongside the progress bar.
stats_renderer: Zero-argument callable that returns a Rich
renderable. Called after each item to refresh the display.
The caller typically closes over a mutable dataclass and
rebuilds a Table from it on each call.
total: Total number of items. If None, attempts to determine
automatically via .count() (for querysets) or len().
Yields:
Items from the iterable.
Example:
@dataclass
class Stats:
processed: int = 0
failed: int = 0
stats = Stats()
def render_stats() -> Table:
table = Table(box=None)
table.add_column("Processed")
table.add_column("Failed")
table.add_row(str(stats.processed), str(stats.failed))
return table
for item in self.track_with_stats(
items,
description="Importing...",
stats_renderer=render_stats,
):
try:
import_item(item)
stats.processed += 1
except Exception:
stats.failed += 1
"""
if self.no_progress_bar:
yield from iterable
return
if total is None:
total = self._get_iterable_length(iterable)
stderr_console = Console(stderr=True)
# Progress is created without its own console so Live controls rendering.
progress = Progress(*self._progress_columns())
task_id = progress.add_task(description, total=total)
with Live(
Group(progress, stats_renderer()),
console=stderr_console,
refresh_per_second=4,
) as live:
for item in iterable:
yield item
progress.advance(task_id)
live.update(Group(progress, stats_renderer()))
def process_parallel(
self,
fn: Callable[[T], R],
@@ -498,7 +269,7 @@ class PaperlessCommand(RichCommand):
total = len(items)
if self.process_count == 1:
# Sequential execution in main process - critical for testing, so we don't fork in fork, etc
# Sequential execution in main process - critical for testing
yield from self._process_sequential(fn, items, description, total)
else:
# Parallel execution with ProcessPoolExecutor
@@ -527,7 +298,6 @@ class PaperlessCommand(RichCommand):
total: int,
) -> Generator[ProcessResult[T, R], None, None]:
"""Process items in parallel using ProcessPoolExecutor."""
# Close database connections before forking - required for PostgreSQL
db.connections.close_all()

View File

@@ -1,12 +1,4 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from dataclasses import field
from typing import TYPE_CHECKING
from rich.table import Table
from rich.text import Text
from documents.classifier import load_classifier
from documents.management.commands.base import PaperlessCommand
@@ -16,162 +8,9 @@ from documents.signals.handlers import set_document_type
from documents.signals.handlers import set_storage_path
from documents.signals.handlers import set_tags
if TYPE_CHECKING:
from rich.console import RenderableType
from documents.models import Correspondent
from documents.models import DocumentType
from documents.models import StoragePath
from documents.models import Tag
logger = logging.getLogger("paperless.management.retagger")
@dataclass(slots=True)
class RetaggerStats:
"""Cumulative counters updated as the retagger processes documents.
Mutable by design -- fields are incremented in the processing loop.
slots=True reduces per-instance memory overhead and speeds attribute access.
"""
correspondents: int = 0
document_types: int = 0
tags_added: int = 0
tags_removed: int = 0
storage_paths: int = 0
documents_processed: int = 0
@dataclass(slots=True)
class DocumentSuggestion:
"""Buffered classifier suggestions for a single document (suggest mode only).
Mutable by design -- fields are assigned incrementally as each setter runs.
"""
document: Document
correspondent: Correspondent | None = None
document_type: DocumentType | None = None
tags_to_add: frozenset[Tag] = field(default_factory=frozenset)
tags_to_remove: frozenset[Tag] = field(default_factory=frozenset)
storage_path: StoragePath | None = None
@property
def has_suggestions(self) -> bool:
return bool(
self.correspondent is not None
or self.document_type is not None
or self.tags_to_add
or self.tags_to_remove
or self.storage_path is not None,
)
def _build_stats_table(stats: RetaggerStats, *, suggest: bool) -> Table:
"""
Build the live-updating stats table shown below the progress bar.
In suggest mode the labels read "would set / would add" to make clear
that nothing has been written to the database.
"""
table = Table(box=None, padding=(0, 2), show_header=True, header_style="bold")
table.add_column("Documents")
table.add_column("Correspondents")
table.add_column("Doc Types")
table.add_column("Tags (+)")
table.add_column("Tags (-)")
table.add_column("Storage Paths")
verb = "would set" if suggest else "set"
table.add_row(
str(stats.documents_processed),
f"{stats.correspondents} {verb}",
f"{stats.document_types} {verb}",
f"+{stats.tags_added}",
f"-{stats.tags_removed}",
f"{stats.storage_paths} {verb}",
)
return table
def _build_suggestion_table(
suggestions: list[DocumentSuggestion],
base_url: str | None,
) -> Table:
"""
Build the final suggestion table printed after the progress bar completes.
Only documents with at least one suggestion are included.
"""
table = Table(
title="Suggested Changes",
show_header=True,
header_style="bold cyan",
show_lines=True,
)
table.add_column("Document", style="bold", no_wrap=False, min_width=20)
table.add_column("Correspondent")
table.add_column("Doc Type")
table.add_column("Tags")
table.add_column("Storage Path")
for suggestion in suggestions:
if not suggestion.has_suggestions:
continue
doc = suggestion.document
if base_url:
doc_cell = Text()
doc_cell.append(str(doc))
doc_cell.append(f"\n{base_url}/documents/{doc.pk}", style="dim")
else:
doc_cell = Text(f"{doc} [{doc.pk}]")
tag_parts: list[str] = []
for tag in sorted(suggestion.tags_to_add, key=lambda t: t.name):
tag_parts.append(f"[green]+{tag.name}[/green]")
for tag in sorted(suggestion.tags_to_remove, key=lambda t: t.name):
tag_parts.append(f"[red]-{tag.name}[/red]")
tag_cell = Text.from_markup(", ".join(tag_parts)) if tag_parts else Text("-")
table.add_row(
doc_cell,
str(suggestion.correspondent) if suggestion.correspondent else "-",
str(suggestion.document_type) if suggestion.document_type else "-",
tag_cell,
str(suggestion.storage_path) if suggestion.storage_path else "-",
)
return table
def _build_summary_table(stats: RetaggerStats) -> Table:
"""Build the final applied-changes summary table."""
table = Table(
title="Retagger Summary",
show_header=True,
header_style="bold cyan",
)
table.add_column("Metric", style="bold")
table.add_column("Count", justify="right")
table.add_row("Documents processed", str(stats.documents_processed))
table.add_row("Correspondents set", str(stats.correspondents))
table.add_row("Document types set", str(stats.document_types))
table.add_row("Tags added", str(stats.tags_added))
table.add_row("Tags removed", str(stats.tags_removed))
table.add_row("Storage paths set", str(stats.storage_paths))
return table
class Command(PaperlessCommand):
help = (
"Using the current classification model, assigns correspondents, tags "
@@ -180,7 +19,7 @@ class Command(PaperlessCommand):
"modified) after their initial import."
)
def add_arguments(self, parser) -> None:
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument("-c", "--correspondent", default=False, action="store_true")
parser.add_argument("-T", "--tags", default=False, action="store_true")
@@ -192,9 +31,9 @@ class Command(PaperlessCommand):
default=False,
action="store_true",
help=(
"By default this command will not try to assign a correspondent "
"if more than one matches the document. Use this flag to pick "
"the first match instead."
"By default this command won't try to assign a correspondent "
"if more than one matches the document. Use this flag if "
"you'd rather it just pick the first one it finds."
),
)
parser.add_argument(
@@ -203,140 +42,91 @@ class Command(PaperlessCommand):
default=False,
action="store_true",
help=(
"Overwrite any previously set correspondent, document type, and "
"remove tags that no longer match due to changed rules."
"If set, the document retagger will overwrite any previously "
"set correspondent, document and remove correspondents, types "
"and tags that do not match anymore due to changed rules."
),
)
parser.add_argument(
"--suggest",
default=False,
action="store_true",
help="Show what would be changed without applying anything.",
help="Return the suggestion, don't change anything.",
)
parser.add_argument(
"--base-url",
help="Base URL used to build document links in suggest output.",
help="The base URL to use to build the link to the documents.",
)
parser.add_argument(
"--id-range",
help="Restrict retagging to documents within this ID range (inclusive).",
help="A range of document ids on which the retagging should be applied.",
nargs=2,
type=int,
)
def handle(self, *args, **options) -> None:
suggest: bool = options["suggest"]
overwrite: bool = options["overwrite"]
use_first: bool = options["use_first"]
base_url: str | None = options["base_url"]
do_correspondent: bool = options["correspondent"]
do_document_type: bool = options["document_type"]
do_tags: bool = options["tags"]
do_storage_path: bool = options["storage_path"]
if not any([do_correspondent, do_document_type, do_tags, do_storage_path]):
self.console.print(
"[yellow]No classifier targets specified. "
"Use -c, -T, -t, or -s to select what to retag.[/yellow]",
)
return
def handle(self, *args, **options):
if options["inbox_only"]:
queryset = Document.objects.filter(tags__is_inbox_tag=True)
else:
queryset = Document.objects.all()
if options["id_range"]:
lo, hi = options["id_range"]
queryset = queryset.filter(id__range=(lo, hi))
queryset = queryset.filter(
id__range=(options["id_range"][0], options["id_range"][1]),
)
documents = queryset.distinct()
classifier = load_classifier()
stats = RetaggerStats()
suggestions: list[DocumentSuggestion] = []
for document in self.track(documents, description="Retagging..."):
if options["correspondent"]:
set_correspondent(
sender=None,
document=document,
classifier=classifier,
replace=options["overwrite"],
use_first=options["use_first"],
suggest=options["suggest"],
base_url=options["base_url"],
stdout=self.stdout,
style_func=self.style,
)
def render_stats() -> RenderableType:
return _build_stats_table(stats, suggest=suggest)
if options["document_type"]:
set_document_type(
sender=None,
document=document,
classifier=classifier,
replace=options["overwrite"],
use_first=options["use_first"],
suggest=options["suggest"],
base_url=options["base_url"],
stdout=self.stdout,
style_func=self.style,
)
with self.buffered_logging(
"paperless",
"paperless.handlers",
"documents",
) as log_buf:
for document in self.track_with_stats(
documents,
description="Retagging...",
stats_renderer=render_stats,
):
suggestion = DocumentSuggestion(document=document)
if options["tags"]:
set_tags(
sender=None,
document=document,
classifier=classifier,
replace=options["overwrite"],
suggest=options["suggest"],
base_url=options["base_url"],
stdout=self.stdout,
style_func=self.style,
)
if do_correspondent:
correspondent = set_correspondent(
None,
document,
classifier=classifier,
replace=overwrite,
use_first=use_first,
dry_run=suggest,
)
if correspondent is not None:
stats.correspondents += 1
suggestion.correspondent = correspondent
if do_document_type:
document_type = set_document_type(
None,
document,
classifier=classifier,
replace=overwrite,
use_first=use_first,
dry_run=suggest,
)
if document_type is not None:
stats.document_types += 1
suggestion.document_type = document_type
if do_tags:
tags_to_add, tags_to_remove = set_tags(
None,
document,
classifier=classifier,
replace=overwrite,
dry_run=suggest,
)
stats.tags_added += len(tags_to_add)
stats.tags_removed += len(tags_to_remove)
suggestion.tags_to_add = frozenset(tags_to_add)
suggestion.tags_to_remove = frozenset(tags_to_remove)
if do_storage_path:
storage_path = set_storage_path(
None,
document,
classifier=classifier,
replace=overwrite,
use_first=use_first,
dry_run=suggest,
)
if storage_path is not None:
stats.storage_paths += 1
suggestion.storage_path = storage_path
stats.documents_processed += 1
if suggest:
suggestions.append(suggestion)
# Post-loop output
if suggest:
visible = [s for s in suggestions if s.has_suggestions]
if visible:
self.console.print(_build_suggestion_table(visible, base_url))
else:
self.console.print("[green]No changes suggested.[/green]")
else:
self.console.print(_build_summary_table(stats))
log_buf.render(self.console, min_level=logging.INFO, title="Retagger Log")
if options["storage_path"]:
set_storage_path(
sender=None,
document=document,
classifier=classifier,
replace=options["overwrite"],
use_first=options["use_first"],
suggest=options["suggest"],
base_url=options["base_url"],
stdout=self.stdout,
style_func=self.style,
)

View File

@@ -75,7 +75,7 @@ class MatchingModel(ModelWithOwner):
is_insensitive = models.BooleanField(_("is insensitive"), default=True)
class Meta(ModelWithOwner.Meta):
class Meta:
abstract = True
ordering = ("name",)
constraints = [

View File

@@ -1724,15 +1724,6 @@ class BulkEditSerializer(
except ValueError:
raise serializers.ValidationError("invalid rotation degrees")
def _validate_source_mode(self, parameters) -> None:
source_mode = parameters.get(
"source_mode",
bulk_edit.SourceModeChoices.LATEST_VERSION,
)
if source_mode not in bulk_edit.SourceModeChoices.__dict__.values():
raise serializers.ValidationError("Invalid source_mode")
parameters["source_mode"] = source_mode
def _validate_parameters_split(self, parameters) -> None:
if "pages" not in parameters:
raise serializers.ValidationError("pages not specified")
@@ -1833,9 +1824,6 @@ class BulkEditSerializer(
method = attrs["method"]
parameters = attrs["parameters"]
if "source_mode" in parameters:
self._validate_source_mode(parameters)
if method == bulk_edit.set_correspondent:
self._validate_parameters_correspondent(parameters)
elif method == bulk_edit.set_document_type:

View File

@@ -4,7 +4,6 @@ import logging
import shutil
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from celery import shared_task
from celery import states
@@ -33,14 +32,12 @@ from documents.file_handling import create_source_path_directory
from documents.file_handling import delete_empty_directories
from documents.file_handling import generate_filename
from documents.file_handling import generate_unique_filename
from documents.models import Correspondent
from documents.models import CustomField
from documents.models import CustomFieldInstance
from documents.models import Document
from documents.models import DocumentType
from documents.models import MatchingModel
from documents.models import PaperlessTask
from documents.models import SavedView
from documents.models import StoragePath
from documents.models import Tag
from documents.models import UiSettings
from documents.models import Workflow
@@ -84,41 +81,47 @@ def add_inbox_tags(sender, document: Document, logging_group=None, **kwargs) ->
document.add_nested_tags(inbox_tags)
def _suggestion_printer(
stdout,
style_func,
suggestion_type: str,
document: Document,
selected: MatchingModel,
base_url: str | None = None,
) -> None:
"""
Smaller helper to reduce duplication when just outputting suggestions to the console
"""
doc_str = str(document)
if base_url is not None:
stdout.write(style_func.SUCCESS(doc_str))
stdout.write(style_func.SUCCESS(f"{base_url}/documents/{document.pk}"))
else:
stdout.write(style_func.SUCCESS(f"{doc_str} [{document.pk}]"))
stdout.write(f"Suggest {suggestion_type}: {selected}")
def set_correspondent(
sender: object,
sender,
document: Document,
*,
logging_group: object = None,
logging_group=None,
classifier: DocumentClassifier | None = None,
replace: bool = False,
use_first: bool = True,
dry_run: bool = False,
**kwargs: Any,
) -> Correspondent | None:
"""
Assign a correspondent to a document based on classifier results.
Args:
document: The document to classify.
logging_group: Optional logging group for structured log output.
classifier: The trained classifier. If None, only rule-based matching runs.
replace: If True, overwrite an existing correspondent assignment.
use_first: If True, pick the first match when multiple correspondents
match. If False, skip assignment when multiple match.
dry_run: If True, compute and return the selection without saving.
**kwargs: Absorbed for Django signal compatibility (e.g. sender, signal).
Returns:
The correspondent that was (or would be) assigned, or None if no match
was found or assignment was skipped.
"""
replace=False,
use_first=True,
suggest=False,
base_url=None,
stdout=None,
style_func=None,
**kwargs,
) -> None:
if document.correspondent and not replace:
return None
return
potential_correspondents = matching.match_correspondents(document, classifier)
potential_count = len(potential_correspondents)
selected = potential_correspondents[0] if potential_correspondents else None
if potential_count > 1:
if use_first:
logger.debug(
@@ -132,53 +135,49 @@ def set_correspondent(
f"not assigning any correspondent",
extra={"group": logging_group},
)
return None
return
if (selected or replace) and not dry_run:
logger.info(
f"Assigning correspondent {selected} to {document}",
extra={"group": logging_group},
)
document.correspondent = selected
document.save(update_fields=("correspondent",))
if selected or replace:
if suggest:
_suggestion_printer(
stdout,
style_func,
"correspondent",
document,
selected,
base_url,
)
else:
logger.info(
f"Assigning correspondent {selected} to {document}",
extra={"group": logging_group},
)
return selected
document.correspondent = selected
document.save(update_fields=("correspondent",))
def set_document_type(
sender: object,
sender,
document: Document,
*,
logging_group: object = None,
logging_group=None,
classifier: DocumentClassifier | None = None,
replace: bool = False,
use_first: bool = True,
dry_run: bool = False,
**kwargs: Any,
) -> DocumentType | None:
"""
Assign a document type to a document based on classifier results.
Args:
document: The document to classify.
logging_group: Optional logging group for structured log output.
classifier: The trained classifier. If None, only rule-based matching runs.
replace: If True, overwrite an existing document type assignment.
use_first: If True, pick the first match when multiple types match.
If False, skip assignment when multiple match.
dry_run: If True, compute and return the selection without saving.
**kwargs: Absorbed for Django signal compatibility (e.g. sender, signal).
Returns:
The document type that was (or would be) assigned, or None if no match
was found or assignment was skipped.
"""
replace=False,
use_first=True,
suggest=False,
base_url=None,
stdout=None,
style_func=None,
**kwargs,
) -> None:
if document.document_type and not replace:
return None
return
potential_document_types = matching.match_document_types(document, classifier)
potential_count = len(potential_document_types)
selected = potential_document_types[0] if potential_document_types else None
potential_document_type = matching.match_document_types(document, classifier)
potential_count = len(potential_document_type)
selected = potential_document_type[0] if potential_document_type else None
if potential_count > 1:
if use_first:
@@ -193,64 +192,42 @@ def set_document_type(
f"not assigning any document type",
extra={"group": logging_group},
)
return None
return
if (selected or replace) and not dry_run:
logger.info(
f"Assigning document type {selected} to {document}",
extra={"group": logging_group},
)
document.document_type = selected
document.save(update_fields=("document_type",))
if selected or replace:
if suggest:
_suggestion_printer(
stdout,
style_func,
"document type",
document,
selected,
base_url,
)
else:
logger.info(
f"Assigning document type {selected} to {document}",
extra={"group": logging_group},
)
return selected
document.document_type = selected
document.save(update_fields=("document_type",))
def set_tags(
sender: object,
sender,
document: Document,
*,
logging_group: object = None,
logging_group=None,
classifier: DocumentClassifier | None = None,
replace: bool = False,
dry_run: bool = False,
**kwargs: Any,
) -> tuple[set[Tag], set[Tag]]:
"""
Assign tags to a document based on classifier results.
When replace=True, existing auto-matched and rule-matched tags are removed
before applying the new set (inbox tags and manually-added tags are preserved).
Args:
document: The document to classify.
logging_group: Optional logging group for structured log output.
classifier: The trained classifier. If None, only rule-based matching runs.
replace: If True, remove existing classifier-managed tags before applying
new ones. Inbox tags and manually-added tags are always preserved.
dry_run: If True, compute what would change without saving anything.
**kwargs: Absorbed for Django signal compatibility (e.g. sender, signal).
Returns:
A two-tuple of (tags_added, tags_removed). In non-replace mode,
tags_removed is always an empty set. In dry_run mode, neither set
is applied to the database.
"""
# Compute which tags would be removed under replace mode.
# The filter mirrors the .delete() call below: keep inbox tags and
# manually-added tags (match="" and not auto-matched).
replace=False,
suggest=False,
base_url=None,
stdout=None,
style_func=None,
**kwargs,
) -> None:
if replace:
tags_to_remove: set[Tag] = set(
document.tags.exclude(
is_inbox_tag=True,
).exclude(
Q(match="") & ~Q(matching_algorithm=Tag.MATCH_AUTO),
),
)
else:
tags_to_remove = set()
if replace and not dry_run:
Document.tags.through.objects.filter(document=document).exclude(
Q(tag__is_inbox_tag=True),
).exclude(
@@ -258,53 +235,65 @@ def set_tags(
).delete()
current_tags = set(document.tags.all())
matched_tags = matching.match_tags(document, classifier)
tags_to_add = set(matched_tags) - current_tags
if tags_to_add and not dry_run:
matched_tags = matching.match_tags(document, classifier)
relevant_tags = set(matched_tags) - current_tags
if suggest:
extra_tags = current_tags - set(matched_tags)
extra_tags = [
t for t in extra_tags if t.matching_algorithm == MatchingModel.MATCH_AUTO
]
if not relevant_tags and not extra_tags:
return
doc_str = style_func.SUCCESS(str(document))
if base_url:
stdout.write(doc_str)
stdout.write(f"{base_url}/documents/{document.pk}")
else:
stdout.write(doc_str + style_func.SUCCESS(f" [{document.pk}]"))
if relevant_tags:
stdout.write("Suggest tags: " + ", ".join([t.name for t in relevant_tags]))
if extra_tags:
stdout.write("Extra tags: " + ", ".join([t.name for t in extra_tags]))
else:
if not relevant_tags:
return
message = 'Tagging "{}" with "{}"'
logger.info(
f'Tagging "{document}" with "{", ".join(t.name for t in tags_to_add)}"',
message.format(document, ", ".join([t.name for t in relevant_tags])),
extra={"group": logging_group},
)
document.add_nested_tags(tags_to_add)
return tags_to_add, tags_to_remove
document.add_nested_tags(relevant_tags)
def set_storage_path(
sender: object,
sender,
document: Document,
*,
logging_group: object = None,
logging_group=None,
classifier: DocumentClassifier | None = None,
replace: bool = False,
use_first: bool = True,
dry_run: bool = False,
**kwargs: Any,
) -> StoragePath | None:
"""
Assign a storage path to a document based on classifier results.
Args:
document: The document to classify.
logging_group: Optional logging group for structured log output.
classifier: The trained classifier. If None, only rule-based matching runs.
replace: If True, overwrite an existing storage path assignment.
use_first: If True, pick the first match when multiple paths match.
If False, skip assignment when multiple match.
dry_run: If True, compute and return the selection without saving.
**kwargs: Absorbed for Django signal compatibility (e.g. sender, signal).
Returns:
The storage path that was (or would be) assigned, or None if no match
was found or assignment was skipped.
"""
replace=False,
use_first=True,
suggest=False,
base_url=None,
stdout=None,
style_func=None,
**kwargs,
) -> None:
if document.storage_path and not replace:
return None
return
potential_storage_paths = matching.match_storage_paths(document, classifier)
potential_count = len(potential_storage_paths)
selected = potential_storage_paths[0] if potential_storage_paths else None
potential_storage_path = matching.match_storage_paths(
document,
classifier,
)
potential_count = len(potential_storage_path)
selected = potential_storage_path[0] if potential_storage_path else None
if potential_count > 1:
if use_first:
@@ -319,17 +308,26 @@ def set_storage_path(
f"not assigning any storage directory",
extra={"group": logging_group},
)
return None
return
if (selected or replace) and not dry_run:
logger.info(
f"Assigning storage path {selected} to {document}",
extra={"group": logging_group},
)
document.storage_path = selected
document.save(update_fields=("storage_path",))
if selected or replace:
if suggest:
_suggestion_printer(
stdout,
style_func,
"storage directory",
document,
selected,
base_url,
)
else:
logger.info(
f"Assigning storage path {selected} to {document}",
extra={"group": logging_group},
)
return selected
document.storage_path = selected
document.save(update_fields=("storage_path",))
# see empty_trash in documents/tasks.py for signal handling

View File

@@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" {{ extra_attrs }} fill="currentColor">
<path
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" {{ extra_attrs }} fill="currentColor">
<path d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 515 B

View File

@@ -1,18 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" {{ extra_attrs }}>
<path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/>
<g class="text" style="fill:#000">
<path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/>
<path d="M1381,416.1c-18.1-11.5-38.3-17.3-60.5-17.4c-32,0-56.9,9.9-74.7,29.8v-20.4h-84.5v390.7h84.5v-164 c17.8,19.9,42.7,29.8,74.7,29.8c22.3,0,42.4-5.7,60.5-17.3s32.3-27.5,42.5-47.8c10.2-20.3,15.3-42.9,15.3-67.8s-5.1-47.5-15.3-67.8 C1413.2,443.6,1399.1,427.7,1381,416.1z M1337.9,575c-10.1,11.7-23.4,17.6-40,17.6s-29.9-5.9-40-17.6s-15.1-26.1-15.1-43.3 c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6s29.9,5.9,40,17.6s15.1,26.1,15.1,43.3S1347.9,563.3,1337.9,575z" transform="translate(0)"/>
<path d="M1672.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6c-20.4,11.7-36.5,27.7-48.2,48s-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S1692.6,428.8,1672.2,416.8z M1558.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H1558.3z" transform="translate(0)"/>
<path d="M1895.3,411.7c-11,5.6-20.3,13.7-28,24.4h-0.1v-28h-84.5v247.3h84.5V536.3c0-22.6,4.7-38.1,14.2-46.5 c9.5-8.5,22.7-12.7,39.6-12.7c6.2,0,13.5,1,21.8,3.1l10.7-72c-5.9-3.3-14.5-4.9-25.8-4.9C1917.1,403.3,1906.3,406.1,1895.3,411.7z" transform="translate(0)"/>
<rect x="1985" y="277.4" width="84.5" height="377.8" transform="translate(0)"/>
<path d="M2313.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6s-36.5,27.7-48.2,48c-11.7,20.3-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S2333.6,428.8,2313.2,416.8z M2199.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H2199.3z" transform="translate(0)"/>
<path d="M2583.6,507.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9 c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8 c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7 c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6 c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9 c34.7,0,62.9-7.4,84.5-22.4c21.7-15,32.5-37.3,32.5-66.9c0-19.3-5-34.2-15.1-44.9S2597.4,512.1,2583.6,507.7z" transform="translate(0)"/>
<path d="M2883.4,575.3c0-19.3-5-34.2-15.1-44.9s-22-18.3-35.8-22.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6 c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4 l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7 c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6 c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2 l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9c34.7,0,62.9-7.4,84.5-22.4 C2872.6,627.2,2883.4,604.9,2883.4,575.3z" transform="translate(0)"/>
<rect x="2460.7" y="738.7" width="59.6" height="17.2" transform="translate(0)"/>
<path d="M2596.5,706.4c-5.7,0-11,1-15.8,3s-9,5-12.5,8.9v-9.4h-19.4v93.6h19.4v-52c0-8.6,2.1-15.3,6.3-20c4.2-4.7,9.5-7.1,15.9-7.1 c7.8,0,13.4,2.3,16.8,6.7c3.4,4.5,5.1,11.3,5.1,20.5v52h19.4v-56.8c0-12.8-3.2-22.6-9.5-29.3 C2615.8,709.8,2607.3,706.4,2596.5,706.4z" transform="translate(0)"/>
<path d="M2733.8,717.7c-3.6-3.4-7.9-6.1-13.1-8.2s-10.6-3.1-16.2-3.1c-8.7,0-16.5,2.1-23.5,6.3s-12.5,10-16.5,17.3 c-4,7.3-6,15.4-6,24.4c0,8.9,2,17.1,6,24.3c4,7.3,9.5,13,16.5,17.2s14.9,6.3,23.5,6.3c5.6,0,11-1,16.2-3.1 c5.1-2.1,9.5-4.8,13.1-8.2v24.4c0,8.5-2.5,14.8-7.6,18.7c-5,3.9-11,5.9-18,5.9c-6.7,0-12.4-1.6-17.3-4.7c-4.8-3.1-7.6-7.7-8.3-13.8 h-19.4c0.6,7.7,2.9,14.2,7.1,19.5s9.6,9.3,16.2,12c6.6,2.7,13.8,4,21.7,4c12.8,0,23.5-3.4,32-10.1c8.6-6.7,12.8-17.1,12.8-31.1 V708.9h-19.2V717.7z M2732.2,770.1c-2.5,4.7-6,8.3-10.4,11.2c-4.4,2.7-9.4,4-14.9,4c-5.7,0-10.8-1.4-15.2-4.3s-7.8-6.7-10.2-11.4 c-2.3-4.8-3.5-9.8-3.5-15.2c0-5.5,1.1-10.6,3.5-15.3s5.8-8.5,10.2-11.3s9.5-4.2,15.2-4.2c5.5,0,10.5,1.4,14.9,4s7.9,6.3,10.4,11 s3.8,10,3.8,15.8S2734.7,765.4,2732.2,770.1z" transform="translate(0)"/>
<polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5 2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 " transform="translate(0)"/>
<path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2670 860" {{ extra_attrs }}>
<path class="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g class="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -114,14 +114,3 @@ def authenticated_rest_api_client(rest_api_client: APIClient):
user = UserModel.objects.create_user(username="testuser", password="password")
rest_api_client.force_authenticate(user=user)
yield rest_api_client
@pytest.fixture(scope="session", autouse=True)
def faker_session_locale():
"""Set Faker locale for reproducibility."""
return "en_US"
@pytest.fixture(scope="session", autouse=True)
def faker_seed():
return 12345

View File

@@ -1,67 +1,17 @@
"""
Factory-boy factories for documents app models.
"""
from __future__ import annotations
import factory
from factory import Faker
from factory.django import DjangoModelFactory
from documents.models import Correspondent
from documents.models import Document
from documents.models import DocumentType
from documents.models import MatchingModel
from documents.models import StoragePath
from documents.models import Tag
class CorrespondentFactory(DjangoModelFactory):
class Meta:
model = Correspondent
name = factory.Sequence(lambda n: f"{factory.Faker('company')} {n}")
match = ""
matching_algorithm = MatchingModel.MATCH_NONE
class DocumentTypeFactory(DjangoModelFactory):
class Meta:
model = DocumentType
name = factory.Sequence(lambda n: f"{factory.Faker('bs')} {n}")
match = ""
matching_algorithm = MatchingModel.MATCH_NONE
class TagFactory(DjangoModelFactory):
class Meta:
model = Tag
name = factory.Sequence(lambda n: f"{factory.Faker('word')} {n}")
match = ""
matching_algorithm = MatchingModel.MATCH_NONE
is_inbox_tag = False
class StoragePathFactory(DjangoModelFactory):
class Meta:
model = StoragePath
name = factory.Sequence(
lambda n: f"{factory.Faker('file_path', depth=2, extension='')} {n}",
)
path = factory.LazyAttribute(lambda o: f"{o.name}/{{title}}")
match = ""
matching_algorithm = MatchingModel.MATCH_NONE
name = Faker("name")
class DocumentFactory(DjangoModelFactory):
class Meta:
model = Document
title = factory.Faker("sentence", nb_words=4)
checksum = factory.Faker("md5")
content = factory.Faker("paragraph")
correspondent = None
document_type = None
storage_path = None

View File

@@ -1395,10 +1395,7 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
{
"documents": [self.doc2.id],
"method": "edit_pdf",
"parameters": {
"operations": [{"page": 1}],
"source_mode": "explicit_selection",
},
"parameters": {"operations": [{"page": 1}]},
},
),
content_type="application/json",
@@ -1410,7 +1407,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
args, kwargs = m.call_args
self.assertCountEqual(args[0], [self.doc2.id])
self.assertEqual(kwargs["operations"], [{"page": 1}])
self.assertEqual(kwargs["source_mode"], "explicit_selection")
self.assertEqual(kwargs["user"], self.user)
def test_edit_pdf_invalid_params(self) -> None:
@@ -1576,24 +1572,6 @@ class TestBulkEditAPI(DirectoriesMixin, APITestCase):
response.content,
)
# invalid source mode
response = self.client.post(
"/api/documents/bulk_edit/",
json.dumps(
{
"documents": [self.doc2.id],
"method": "edit_pdf",
"parameters": {
"operations": [{"page": 1}],
"source_mode": "not_a_mode",
},
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn(b"Invalid source_mode", response.content)
@mock.patch("documents.serialisers.bulk_edit.edit_pdf")
def test_edit_pdf_page_out_of_bounds(self, m) -> None:
"""

View File

@@ -405,9 +405,7 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
self.assertTrue(Document.objects.filter(id=self.doc1.id).exists())
self.assertFalse(Document.objects.filter(id=version.id).exists())
def test_resolve_root_and_source_doc_latest_version_prefers_newest_version(
self,
) -> None:
def test_get_root_and_current_doc_mapping(self) -> None:
version1 = Document.objects.create(
checksum="B-v1",
title="B version 1",
@@ -419,14 +417,18 @@ class TestBulkEdit(DirectoriesMixin, TestCase):
root_document=self.doc2,
)
root_doc, source_doc = bulk_edit._resolve_root_and_source_doc(
self.doc2,
source_mode="latest_version",
root_ids_by_doc_id = bulk_edit._get_root_ids_by_doc_id(
[self.doc2.id, version1.id, version2.id],
)
self.assertEqual(root_ids_by_doc_id[self.doc2.id], self.doc2.id)
self.assertEqual(root_ids_by_doc_id[version1.id], self.doc2.id)
self.assertEqual(root_ids_by_doc_id[version2.id], self.doc2.id)
self.assertEqual(root_doc.id, self.doc2.id)
self.assertEqual(source_doc.id, version2.id)
self.assertNotEqual(source_doc.id, version1.id)
root_docs, current_docs = bulk_edit._get_root_and_current_docs_by_root_id(
{self.doc2.id},
)
self.assertEqual(root_docs[self.doc2.id].id, self.doc2.id)
self.assertEqual(current_docs[self.doc2.id].id, version2.id)
@mock.patch("documents.tasks.bulk_update_documents.delay")
def test_set_permissions(self, m) -> None:
@@ -1039,34 +1041,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
self.assertIsNotNone(overrides)
self.assertEqual(result, "OK")
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.open")
def test_rotate_explicit_selection_uses_root_source_when_root_selected(
self,
mock_open,
mock_consume_delay,
mock_magic,
):
Document.objects.create(
checksum="B-v1",
title="B version 1",
root_document=self.doc2,
)
fake_pdf = mock.MagicMock()
fake_pdf.pages = [mock.Mock()]
mock_open.return_value.__enter__.return_value = fake_pdf
result = bulk_edit.rotate(
[self.doc2.id],
90,
source_mode="explicit_selection",
)
self.assertEqual(result, "OK")
mock_open.assert_called_once_with(self.doc2.source_path)
mock_consume_delay.assert_called_once()
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.Pdf.save")
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
@@ -1091,34 +1065,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
self.assertIsNotNone(overrides)
self.assertEqual(result, "OK")
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.open")
def test_delete_pages_explicit_selection_uses_root_source_when_root_selected(
self,
mock_open,
mock_consume_delay,
mock_magic,
):
Document.objects.create(
checksum="B-v1",
title="B version 1",
root_document=self.doc2,
)
fake_pdf = mock.MagicMock()
fake_pdf.pages = [mock.Mock(), mock.Mock()]
mock_open.return_value.__enter__.return_value = fake_pdf
result = bulk_edit.delete_pages(
[self.doc2.id],
[1],
source_mode="explicit_selection",
)
self.assertEqual(result, "OK")
mock_open.assert_called_once_with(self.doc2.source_path)
mock_consume_delay.assert_called_once()
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.Pdf.save")
def test_delete_pages_with_error(self, mock_pdf_save, mock_consume_delay):
@@ -1267,40 +1213,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
self.assertTrue(str(consumable.original_file).endswith("_edited.pdf"))
self.assertIsNotNone(overrides)
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.new")
@mock.patch("pikepdf.open")
def test_edit_pdf_explicit_selection_uses_root_source_when_root_selected(
self,
mock_open,
mock_new,
mock_consume_delay,
mock_magic,
):
Document.objects.create(
checksum="B-v1",
title="B version 1",
root_document=self.doc2,
)
fake_pdf = mock.MagicMock()
fake_pdf.pages = [mock.Mock()]
mock_open.return_value.__enter__.return_value = fake_pdf
output_pdf = mock.MagicMock()
output_pdf.pages = []
mock_new.return_value = output_pdf
result = bulk_edit.edit_pdf(
[self.doc2.id],
operations=[{"page": 1}],
update_document=True,
source_mode="explicit_selection",
)
self.assertEqual(result, "OK")
mock_open.assert_called_once_with(self.doc2.source_path)
mock_consume_delay.assert_called_once()
@mock.patch("documents.bulk_edit.group")
@mock.patch("documents.tasks.consume_file.s")
def test_edit_pdf_without_metadata(
@@ -1421,34 +1333,6 @@ class TestPDFActions(DirectoriesMixin, TestCase):
self.assertEqual(consumable.root_document_id, doc.id)
self.assertIsNotNone(overrides)
@mock.patch("documents.data_models.magic.from_file", return_value="application/pdf")
@mock.patch("documents.tasks.consume_file.delay")
@mock.patch("pikepdf.open")
def test_remove_password_explicit_selection_uses_root_source_when_root_selected(
self,
mock_open,
mock_consume_delay,
mock_magic,
) -> None:
Document.objects.create(
checksum="A-v1",
title="A version 1",
root_document=self.doc1,
)
fake_pdf = mock.MagicMock()
mock_open.return_value.__enter__.return_value = fake_pdf
result = bulk_edit.remove_password(
[self.doc1.id],
password="secret",
update_document=True,
source_mode="explicit_selection",
)
self.assertEqual(result, "OK")
mock_open.assert_called_once_with(self.doc1.source_path, password="secret")
mock_consume_delay.assert_called_once()
@mock.patch("documents.bulk_edit.chord")
@mock.patch("documents.bulk_edit.group")
@mock.patch("documents.tasks.consume_file.s")

View File

@@ -1,442 +1,298 @@
"""
Tests for the document_retagger management command.
"""
from __future__ import annotations
import pytest
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase
from documents.models import Correspondent
from documents.models import Document
from documents.models import DocumentType
from documents.models import MatchingModel
from documents.models import StoragePath
from documents.models import Tag
from documents.tests.factories import CorrespondentFactory
from documents.tests.factories import DocumentFactory
from documents.tests.factories import DocumentTypeFactory
from documents.tests.factories import StoragePathFactory
from documents.tests.factories import TagFactory
from documents.tests.utils import DirectoriesMixin
# ---------------------------------------------------------------------------
# Module-level type aliases
# ---------------------------------------------------------------------------
StoragePathTuple = tuple[StoragePath, StoragePath, StoragePath]
TagTuple = tuple[Tag, Tag, Tag, Tag, Tag]
CorrespondentTuple = tuple[Correspondent, Correspondent]
DocumentTypeTuple = tuple[DocumentType, DocumentType]
DocumentTuple = tuple[Document, Document, Document, Document]
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def storage_paths(db) -> StoragePathTuple:
"""Three storage paths with varying match rules."""
sp1 = StoragePathFactory(
path="{created_data}/{title}",
match="auto document",
matching_algorithm=MatchingModel.MATCH_LITERAL,
)
sp2 = StoragePathFactory(
path="{title}",
match="^first|^unrelated",
matching_algorithm=MatchingModel.MATCH_REGEX,
)
sp3 = StoragePathFactory(
path="{title}",
match="^blah",
matching_algorithm=MatchingModel.MATCH_REGEX,
)
return sp1, sp2, sp3
@pytest.fixture()
def tags(db) -> TagTuple:
"""Tags covering the common matching scenarios."""
tag_first = TagFactory(match="first", matching_algorithm=Tag.MATCH_ANY)
tag_second = TagFactory(match="second", matching_algorithm=Tag.MATCH_ANY)
tag_inbox = TagFactory(is_inbox_tag=True)
tag_no_match = TagFactory()
tag_auto = TagFactory(matching_algorithm=Tag.MATCH_AUTO)
return tag_first, tag_second, tag_inbox, tag_no_match, tag_auto
@pytest.fixture()
def correspondents(db) -> CorrespondentTuple:
"""Two correspondents matching 'first' and 'second' content."""
c_first = CorrespondentFactory(
match="first",
matching_algorithm=MatchingModel.MATCH_ANY,
)
c_second = CorrespondentFactory(
match="second",
matching_algorithm=MatchingModel.MATCH_ANY,
)
return c_first, c_second
@pytest.fixture()
def document_types(db) -> DocumentTypeTuple:
"""Two document types matching 'first' and 'second' content."""
dt_first = DocumentTypeFactory(
match="first",
matching_algorithm=MatchingModel.MATCH_ANY,
)
dt_second = DocumentTypeFactory(
match="second",
matching_algorithm=MatchingModel.MATCH_ANY,
)
return dt_first, dt_second
@pytest.fixture()
def documents(storage_paths: StoragePathTuple, tags: TagTuple) -> DocumentTuple:
"""Four documents with varied content used across most retagger tests."""
_, _, sp3 = storage_paths
_, _, tag_inbox, tag_no_match, tag_auto = tags
d1 = DocumentFactory(checksum="A", title="A", content="first document")
d2 = DocumentFactory(checksum="B", title="B", content="second document")
d3 = DocumentFactory(
checksum="C",
title="C",
content="unrelated document",
storage_path=sp3,
)
d4 = DocumentFactory(checksum="D", title="D", content="auto document")
d3.tags.add(tag_inbox, tag_no_match)
d4.tags.add(tag_auto)
return d1, d2, d3, d4
def _get_docs() -> DocumentTuple:
return (
Document.objects.get(title="A"),
Document.objects.get(title="B"),
Document.objects.get(title="C"),
Document.objects.get(title="D"),
)
# ---------------------------------------------------------------------------
# Tag assignment
# ---------------------------------------------------------------------------
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerTags(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
def test_add_tags(self, tags: TagTuple) -> None:
tag_first, tag_second, *_ = tags
class TestRetagger(DirectoriesMixin, TestCase):
def make_models(self) -> None:
self.sp1 = StoragePath.objects.create(
name="dummy a",
path="{created_data}/{title}",
match="auto document",
matching_algorithm=StoragePath.MATCH_LITERAL,
)
self.sp2 = StoragePath.objects.create(
name="dummy b",
path="{title}",
match="^first|^unrelated",
matching_algorithm=StoragePath.MATCH_REGEX,
)
self.sp3 = StoragePath.objects.create(
name="dummy c",
path="{title}",
match="^blah",
matching_algorithm=StoragePath.MATCH_REGEX,
)
self.d1 = Document.objects.create(
checksum="A",
title="A",
content="first document",
)
self.d2 = Document.objects.create(
checksum="B",
title="B",
content="second document",
)
self.d3 = Document.objects.create(
checksum="C",
title="C",
content="unrelated document",
storage_path=self.sp3,
)
self.d4 = Document.objects.create(
checksum="D",
title="D",
content="auto document",
)
self.tag_first = Tag.objects.create(
name="tag1",
match="first",
matching_algorithm=Tag.MATCH_ANY,
)
self.tag_second = Tag.objects.create(
name="tag2",
match="second",
matching_algorithm=Tag.MATCH_ANY,
)
self.tag_inbox = Tag.objects.create(name="test", is_inbox_tag=True)
self.tag_no_match = Tag.objects.create(name="test2")
self.tag_auto = Tag.objects.create(
name="tagauto",
matching_algorithm=Tag.MATCH_AUTO,
)
self.d3.tags.add(self.tag_inbox)
self.d3.tags.add(self.tag_no_match)
self.d4.tags.add(self.tag_auto)
self.correspondent_first = Correspondent.objects.create(
name="c1",
match="first",
matching_algorithm=Correspondent.MATCH_ANY,
)
self.correspondent_second = Correspondent.objects.create(
name="c2",
match="second",
matching_algorithm=Correspondent.MATCH_ANY,
)
self.doctype_first = DocumentType.objects.create(
name="dt1",
match="first",
matching_algorithm=DocumentType.MATCH_ANY,
)
self.doctype_second = DocumentType.objects.create(
name="dt2",
match="second",
matching_algorithm=DocumentType.MATCH_ANY,
)
def get_updated_docs(self):
return (
Document.objects.get(title="A"),
Document.objects.get(title="B"),
Document.objects.get(title="C"),
Document.objects.get(title="D"),
)
def setUp(self) -> None:
super().setUp()
self.make_models()
def test_add_tags(self) -> None:
call_command("document_retagger", "--tags")
d_first, d_second, d_unrelated, d_auto = _get_docs()
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
assert d_first.tags.count() == 1
assert d_second.tags.count() == 1
assert d_unrelated.tags.count() == 2
assert d_auto.tags.count() == 1
assert d_first.tags.first() == tag_first
assert d_second.tags.first() == tag_second
self.assertEqual(d_first.tags.count(), 1)
self.assertEqual(d_second.tags.count(), 1)
self.assertEqual(d_unrelated.tags.count(), 2)
self.assertEqual(d_auto.tags.count(), 1)
def test_overwrite_removes_stale_tags_and_preserves_inbox(
self,
documents: DocumentTuple,
tags: TagTuple,
) -> None:
d1, *_ = documents
tag_first, tag_second, tag_inbox, tag_no_match, _ = tags
d1.tags.add(tag_second)
self.assertEqual(d_first.tags.first(), self.tag_first)
self.assertEqual(d_second.tags.first(), self.tag_second)
def test_add_type(self) -> None:
call_command("document_retagger", "--document_type")
d_first, d_second, _, _ = self.get_updated_docs()
self.assertEqual(d_first.document_type, self.doctype_first)
self.assertEqual(d_second.document_type, self.doctype_second)
def test_add_correspondent(self) -> None:
call_command("document_retagger", "--correspondent")
d_first, d_second, _, _ = self.get_updated_docs()
self.assertEqual(d_first.correspondent, self.correspondent_first)
self.assertEqual(d_second.correspondent, self.correspondent_second)
def test_overwrite_preserve_inbox(self) -> None:
self.d1.tags.add(self.tag_second)
call_command("document_retagger", "--tags", "--overwrite")
d_first, d_second, d_unrelated, d_auto = _get_docs()
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
assert Tag.objects.filter(id=tag_second.id).exists()
assert list(d_first.tags.values_list("id", flat=True)) == [tag_first.id]
assert list(d_second.tags.values_list("id", flat=True)) == [tag_second.id]
assert set(d_unrelated.tags.values_list("id", flat=True)) == {
tag_inbox.id,
tag_no_match.id,
}
assert d_auto.tags.count() == 0
self.assertIsNotNone(Tag.objects.get(id=self.tag_second.id))
@pytest.mark.usefixtures("documents")
@pytest.mark.parametrize(
"extra_args",
[
pytest.param([], id="no_base_url"),
pytest.param(["--base-url=http://localhost"], id="with_base_url"),
],
)
def test_suggest_does_not_apply_tags(self, extra_args: list[str]) -> None:
call_command("document_retagger", "--tags", "--suggest", *extra_args)
d_first, d_second, _, d_auto = _get_docs()
assert d_first.tags.count() == 0
assert d_second.tags.count() == 0
assert d_auto.tags.count() == 1
# ---------------------------------------------------------------------------
# Document type assignment
# ---------------------------------------------------------------------------
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerDocumentType(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
def test_add_type(self, document_types: DocumentTypeTuple) -> None:
dt_first, dt_second = document_types
call_command("document_retagger", "--document_type")
d_first, d_second, _, _ = _get_docs()
assert d_first.document_type == dt_first
assert d_second.document_type == dt_second
@pytest.mark.usefixtures("documents", "document_types")
@pytest.mark.parametrize(
"extra_args",
[
pytest.param([], id="no_base_url"),
pytest.param(["--base-url=http://localhost"], id="with_base_url"),
],
)
def test_suggest_does_not_apply_document_type(self, extra_args: list[str]) -> None:
call_command("document_retagger", "--document_type", "--suggest", *extra_args)
d_first, d_second, _, _ = _get_docs()
assert d_first.document_type is None
assert d_second.document_type is None
@pytest.mark.parametrize(
("use_first_flag", "expects_assignment"),
[
pytest.param(["--use-first"], True, id="use_first_assigns_first_match"),
pytest.param([], False, id="no_use_first_skips_ambiguous_match"),
],
)
def test_use_first_with_multiple_matches(
self,
use_first_flag: list[str],
*,
expects_assignment: bool,
) -> None:
DocumentTypeFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
self.assertCountEqual(
[tag.id for tag in d_first.tags.all()],
[self.tag_first.id],
)
DocumentTypeFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
self.assertCountEqual(
[tag.id for tag in d_second.tags.all()],
[self.tag_second.id],
)
doc = DocumentFactory(content="ambiguous content")
call_command("document_retagger", "--document_type", *use_first_flag)
doc.refresh_from_db()
assert (doc.document_type is not None) is expects_assignment
# ---------------------------------------------------------------------------
# Correspondent assignment
# ---------------------------------------------------------------------------
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerCorrespondent(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
def test_add_correspondent(self, correspondents: CorrespondentTuple) -> None:
c_first, c_second = correspondents
call_command("document_retagger", "--correspondent")
d_first, d_second, _, _ = _get_docs()
assert d_first.correspondent == c_first
assert d_second.correspondent == c_second
@pytest.mark.usefixtures("documents", "correspondents")
@pytest.mark.parametrize(
"extra_args",
[
pytest.param([], id="no_base_url"),
pytest.param(["--base-url=http://localhost"], id="with_base_url"),
],
)
def test_suggest_does_not_apply_correspondent(self, extra_args: list[str]) -> None:
call_command("document_retagger", "--correspondent", "--suggest", *extra_args)
d_first, d_second, _, _ = _get_docs()
assert d_first.correspondent is None
assert d_second.correspondent is None
@pytest.mark.parametrize(
("use_first_flag", "expects_assignment"),
[
pytest.param(["--use-first"], True, id="use_first_assigns_first_match"),
pytest.param([], False, id="no_use_first_skips_ambiguous_match"),
],
)
def test_use_first_with_multiple_matches(
self,
use_first_flag: list[str],
*,
expects_assignment: bool,
) -> None:
CorrespondentFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
self.assertCountEqual(
[tag.id for tag in d_unrelated.tags.all()],
[self.tag_inbox.id, self.tag_no_match.id],
)
CorrespondentFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
self.assertEqual(d_auto.tags.count(), 0)
def test_add_tags_suggest(self) -> None:
call_command("document_retagger", "--tags", "--suggest")
d_first, d_second, _, d_auto = self.get_updated_docs()
self.assertEqual(d_first.tags.count(), 0)
self.assertEqual(d_second.tags.count(), 0)
self.assertEqual(d_auto.tags.count(), 1)
def test_add_type_suggest(self) -> None:
call_command("document_retagger", "--document_type", "--suggest")
d_first, d_second, _, _ = self.get_updated_docs()
self.assertIsNone(d_first.document_type)
self.assertIsNone(d_second.document_type)
def test_add_correspondent_suggest(self) -> None:
call_command("document_retagger", "--correspondent", "--suggest")
d_first, d_second, _, _ = self.get_updated_docs()
self.assertIsNone(d_first.correspondent)
self.assertIsNone(d_second.correspondent)
def test_add_tags_suggest_url(self) -> None:
call_command(
"document_retagger",
"--tags",
"--suggest",
"--base-url=http://localhost",
)
doc = DocumentFactory(content="ambiguous content")
d_first, d_second, _, d_auto = self.get_updated_docs()
call_command("document_retagger", "--correspondent", *use_first_flag)
self.assertEqual(d_first.tags.count(), 0)
self.assertEqual(d_second.tags.count(), 0)
self.assertEqual(d_auto.tags.count(), 1)
doc.refresh_from_db()
assert (doc.correspondent is not None) is expects_assignment
def test_add_type_suggest_url(self) -> None:
call_command(
"document_retagger",
"--document_type",
"--suggest",
"--base-url=http://localhost",
)
d_first, d_second, _, _ = self.get_updated_docs()
self.assertIsNone(d_first.document_type)
self.assertIsNone(d_second.document_type)
# ---------------------------------------------------------------------------
# Storage path assignment
# ---------------------------------------------------------------------------
def test_add_correspondent_suggest_url(self) -> None:
call_command(
"document_retagger",
"--correspondent",
"--suggest",
"--base-url=http://localhost",
)
d_first, d_second, _, _ = self.get_updated_docs()
self.assertIsNone(d_first.correspondent)
self.assertIsNone(d_second.correspondent)
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerStoragePath(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
def test_add_storage_path(self, storage_paths: StoragePathTuple) -> None:
def test_add_storage_path(self) -> None:
"""
GIVEN documents matching various storage path rules
WHEN document_retagger --storage_path is called
THEN matching documents get the correct path; existing path is unchanged
GIVEN:
- 2 storage paths with documents which match them
- 1 document which matches but has a storage path
WHEN:
- document retagger is called
THEN:
- Matching document's storage paths updated
- Non-matching documents have no storage path
- Existing storage patch left unchanged
"""
sp1, sp2, sp3 = storage_paths
call_command("document_retagger", "--storage_path")
d_first, d_second, d_unrelated, d_auto = _get_docs()
call_command(
"document_retagger",
"--storage_path",
)
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
assert d_first.storage_path == sp2
assert d_auto.storage_path == sp1
assert d_second.storage_path is None
assert d_unrelated.storage_path == sp3
self.assertEqual(d_first.storage_path, self.sp2)
self.assertEqual(d_auto.storage_path, self.sp1)
self.assertIsNone(d_second.storage_path)
self.assertEqual(d_unrelated.storage_path, self.sp3)
@pytest.mark.usefixtures("documents")
def test_overwrite_storage_path(self, storage_paths: StoragePathTuple) -> None:
def test_overwrite_storage_path(self) -> None:
"""
GIVEN a document with an existing storage path that matches a different rule
WHEN document_retagger --storage_path --overwrite is called
THEN the existing path is replaced by the newly matched path
GIVEN:
- 2 storage paths with documents which match them
- 1 document which matches but has a storage path
WHEN:
- document retagger is called with overwrite
THEN:
- Matching document's storage paths updated
- Non-matching documents have no storage path
- Existing storage patch overwritten
"""
sp1, sp2, _ = storage_paths
call_command("document_retagger", "--storage_path", "--overwrite")
d_first, d_second, d_unrelated, d_auto = _get_docs()
d_first, d_second, d_unrelated, d_auto = self.get_updated_docs()
assert d_first.storage_path == sp2
assert d_auto.storage_path == sp1
assert d_second.storage_path is None
assert d_unrelated.storage_path == sp2
self.assertEqual(d_first.storage_path, self.sp2)
self.assertEqual(d_auto.storage_path, self.sp1)
self.assertIsNone(d_second.storage_path)
self.assertEqual(d_unrelated.storage_path, self.sp2)
@pytest.mark.parametrize(
("use_first_flag", "expects_assignment"),
[
pytest.param(["--use-first"], True, id="use_first_assigns_first_match"),
pytest.param([], False, id="no_use_first_skips_ambiguous_match"),
],
)
def test_use_first_with_multiple_matches(
self,
use_first_flag: list[str],
*,
expects_assignment: bool,
) -> None:
StoragePathFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
def test_id_range_parameter(self) -> None:
commandOutput = ""
Document.objects.create(
checksum="E",
title="E",
content="NOT the first document",
)
StoragePathFactory(
match="ambiguous",
matching_algorithm=MatchingModel.MATCH_ANY,
)
doc = DocumentFactory(content="ambiguous content")
call_command("document_retagger", "--tags", "--id-range", "1", "2")
# The retagger shouldn`t apply the 'first' tag to our new document
self.assertEqual(Document.objects.filter(tags__id=self.tag_first.id).count(), 1)
call_command("document_retagger", "--storage_path", *use_first_flag)
try:
commandOutput = call_command("document_retagger", "--tags", "--id-range")
except CommandError:
# Just ignore the error
None
self.assertIn(commandOutput, "Error: argument --id-range: expected 2 arguments")
doc.refresh_from_db()
assert (doc.storage_path is not None) is expects_assignment
try:
commandOutput = call_command(
"document_retagger",
"--tags",
"--id-range",
"a",
"b",
)
except CommandError:
# Just ignore the error
None
self.assertIn(commandOutput, "error: argument --id-range: invalid int value:")
# ---------------------------------------------------------------------------
# ID range filtering
# ---------------------------------------------------------------------------
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerIdRange(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
@pytest.mark.parametrize(
("id_range_args", "expected_count"),
[
pytest.param(["1", "2"], 1, id="narrow_range_limits_scope"),
pytest.param(["1", "9999"], 2, id="wide_range_tags_all_matches"),
],
)
def test_id_range_limits_scope(
self,
tags: TagTuple,
id_range_args: list[str],
expected_count: int,
) -> None:
DocumentFactory(content="NOT the first document")
call_command("document_retagger", "--tags", "--id-range", *id_range_args)
tag_first, *_ = tags
assert Document.objects.filter(tags__id=tag_first.id).count() == expected_count
@pytest.mark.usefixtures("documents")
@pytest.mark.parametrize(
"args",
[
pytest.param(["--tags", "--id-range"], id="missing_both_values"),
pytest.param(["--tags", "--id-range", "a", "b"], id="non_integer_values"),
],
)
def test_id_range_invalid_arguments_raise(self, args: list[str]) -> None:
with pytest.raises((CommandError, SystemExit)):
call_command("document_retagger", *args)
# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------
@pytest.mark.management
@pytest.mark.django_db
class TestRetaggerEdgeCases(DirectoriesMixin):
@pytest.mark.usefixtures("documents")
def test_no_targets_exits_cleanly(self) -> None:
"""Calling the retagger with no classifier targets should not raise."""
call_command("document_retagger")
@pytest.mark.usefixtures("documents")
def test_inbox_only_skips_non_inbox_documents(self) -> None:
"""--inbox-only must restrict processing to documents with an inbox tag."""
call_command("document_retagger", "--tags", "--inbox-only")
d_first, _, d_unrelated, _ = _get_docs()
assert d_first.tags.count() == 0
assert d_unrelated.tags.count() == 2
call_command("document_retagger", "--tags", "--id-range", "1", "9999")
# Now we should have 2 documents
self.assertEqual(Document.objects.filter(tags__id=self.tag_first.id).count(), 2)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

10
uv.lock generated
View File

@@ -1342,11 +1342,11 @@ wheels = [
[[package]]
name = "faker"
version = "40.5.1"
version = "40.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/2a/96fff3edcb10f6505143448a4b91535f77b74865cec45be52690ee280443/faker-40.5.1.tar.gz", hash = "sha256:70222361cd82aa10cb86066d1a4e8f47f2bcdc919615c412045a69c4e6da0cd3", size = 1952684, upload-time = "2026-02-23T21:34:38.362Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/77/1c3ff07b6739b9a1d23ca01ec0a90a309a33b78e345a3eb52f9ce9240e36/faker-40.1.2.tar.gz", hash = "sha256:b76a68163aa5f171d260fc24827a8349bc1db672f6a665359e8d0095e8135d30", size = 1949802, upload-time = "2026-01-13T20:51:49.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/a9/1eed4db92d0aec2f9bfdf1faae0ab0418b5e121dda5701f118a7a4f0cd6a/faker-40.5.1-py3-none-any.whl", hash = "sha256:c69640c1e13bad49b4bcebcbf1b52f9f1a872b6ea186c248ada34d798f1661bf", size = 1987053, upload-time = "2026-02-23T21:34:36.418Z" },
{ url = "https://files.pythonhosted.org/packages/46/ec/91a434c8a53d40c3598966621dea9c50512bec6ce8e76fa1751015e74cef/faker-40.1.2-py3-none-any.whl", hash = "sha256:93503165c165d330260e4379fd6dc07c94da90c611ed3191a0174d2ab9966a42", size = 1985633, upload-time = "2026-01-13T20:51:47.982Z" },
]
[[package]]
@@ -3121,7 +3121,6 @@ webserver = [
dev = [
{ name = "daphne", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "factory-boy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "faker", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "imagehash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "prek", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -3146,7 +3145,6 @@ lint = [
testing = [
{ name = "daphne", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "factory-boy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "faker", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "imagehash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
{ name = "pytest-cov", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
@@ -3259,7 +3257,6 @@ provides-extras = ["mariadb", "postgres", "webserver"]
dev = [
{ name = "daphne" },
{ name = "factory-boy", specifier = "~=3.3.1" },
{ name = "faker", specifier = "~=40.5.1" },
{ name = "imagehash" },
{ name = "prek", specifier = "~=0.3.0" },
{ name = "pytest", specifier = "~=9.0.0" },
@@ -3282,7 +3279,6 @@ lint = [
testing = [
{ name = "daphne" },
{ name = "factory-boy", specifier = "~=3.3.1" },
{ name = "faker", specifier = "~=40.5.1" },
{ name = "imagehash" },
{ name = "pytest", specifier = "~=9.0.0" },
{ name = "pytest-cov", specifier = "~=7.0.0" },

View File

@@ -36,7 +36,7 @@ extra_css = ["assets/extra.css"]
[project.theme]
logo = "assets/logo.svg"
logo = "assets/logo_leaf_white.svg"
favicon = "assets/favicon.png"
language = "en"