Merge branch 'main' into aggresive-redact

# Conflicts:
#	app/core/src/main/java/stirling/software/SPDF/pdf/TextFinder.java
This commit is contained in:
Balázs Szücs 2025-08-25 23:43:41 +02:00
commit 9fe3f1b7fa
53 changed files with 1988 additions and 841 deletions

8
.github/scripts/requirements_dev.in vendored Normal file
View File

@ -0,0 +1,8 @@
pip
setuptools
WeasyPrint
pdf2image
pillow
unoserver
opencv-python-headless
pre-commit

638
.github/scripts/requirements_dev.txt vendored Normal file
View File

@ -0,0 +1,638 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --allow-unsafe --generate-hashes --output-file='.github\scripts\requirements_dev.txt' --strip-extras '.github\scripts\requirements_dev.in'
#
brotli==1.1.0 \
--hash=sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208 \
--hash=sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48 \
--hash=sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354 \
--hash=sha256:0737ddb3068957cf1b054899b0883830bb1fec522ec76b1098f9b6e0f02d9419 \
--hash=sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a \
--hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \
--hash=sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c \
--hash=sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088 \
--hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \
--hash=sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a \
--hash=sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3 \
--hash=sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757 \
--hash=sha256:1e9a65b5736232e7a7f91ff3d02277f11d339bf34099a56cdab6a8b3410a02b2 \
--hash=sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438 \
--hash=sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578 \
--hash=sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b \
--hash=sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b \
--hash=sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68 \
--hash=sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0 \
--hash=sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d \
--hash=sha256:2feb1d960f760a575dbc5ab3b1c00504b24caaf6986e2dc2b01c09c87866a943 \
--hash=sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd \
--hash=sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409 \
--hash=sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28 \
--hash=sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da \
--hash=sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50 \
--hash=sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f \
--hash=sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0 \
--hash=sha256:3ee8a80d67a4334482d9712b8e83ca6b1d9bc7e351931252ebef5d8f7335a547 \
--hash=sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180 \
--hash=sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0 \
--hash=sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d \
--hash=sha256:4410f84b33374409552ac9b6903507cdb31cd30d2501fc5ca13d18f73548444a \
--hash=sha256:494994f807ba0b92092a163a0a283961369a65f6cbe01e8891132b7a320e61eb \
--hash=sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112 \
--hash=sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc \
--hash=sha256:4f3607b129417e111e30637af1b56f24f7a49e64763253bbc275c75fa887d4b2 \
--hash=sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265 \
--hash=sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327 \
--hash=sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95 \
--hash=sha256:58d4b711689366d4a03ac7957ab8c28890415e267f9b6589969e74b6e42225ec \
--hash=sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd \
--hash=sha256:5dab0844f2cf82be357a0eb11a9087f70c5430b2c241493fc122bb6f2bb0917c \
--hash=sha256:5e55da2c8724191e5b557f8e18943b1b4839b8efc3ef60d65985bcf6f587dd38 \
--hash=sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914 \
--hash=sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0 \
--hash=sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a \
--hash=sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7 \
--hash=sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368 \
--hash=sha256:6974f52a02321b36847cd19d1b8e381bf39939c21efd6ee2fc13a28b0d99348c \
--hash=sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0 \
--hash=sha256:6c6e0c425f22c1c719c42670d561ad682f7bfeeef918edea971a79ac5252437f \
--hash=sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451 \
--hash=sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f \
--hash=sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8 \
--hash=sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e \
--hash=sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248 \
--hash=sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c \
--hash=sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91 \
--hash=sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724 \
--hash=sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7 \
--hash=sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966 \
--hash=sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9 \
--hash=sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97 \
--hash=sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d \
--hash=sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5 \
--hash=sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf \
--hash=sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac \
--hash=sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b \
--hash=sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951 \
--hash=sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74 \
--hash=sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648 \
--hash=sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60 \
--hash=sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c \
--hash=sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1 \
--hash=sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8 \
--hash=sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d \
--hash=sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc \
--hash=sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61 \
--hash=sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460 \
--hash=sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751 \
--hash=sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9 \
--hash=sha256:a7e53012d2853a07a4a79c00643832161a910674a893d296c9f1259859a289d2 \
--hash=sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0 \
--hash=sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1 \
--hash=sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474 \
--hash=sha256:aea440a510e14e818e67bfc4027880e2fb500c2ccb20ab21c7a7c8b5b4703d75 \
--hash=sha256:af6fa6817889314555aede9a919612b23739395ce767fe7fcbea9a80bf140fe5 \
--hash=sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f \
--hash=sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2 \
--hash=sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f \
--hash=sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb \
--hash=sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6 \
--hash=sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9 \
--hash=sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111 \
--hash=sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2 \
--hash=sha256:cb1dac1770878ade83f2ccdf7d25e494f05c9165f5246b46a621cc849341dc01 \
--hash=sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467 \
--hash=sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619 \
--hash=sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf \
--hash=sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408 \
--hash=sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579 \
--hash=sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84 \
--hash=sha256:d2b35ca2c7f81d173d2fadc2f4f31e88cc5f7a39ae5b6db5513cf3383b0e0ec7 \
--hash=sha256:d342778ef319e1026af243ed0a07c97acf3bad33b9f29e7ae6a1f68fd083e90c \
--hash=sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284 \
--hash=sha256:d7702622a8b40c49bffb46e1e3ba2e81268d5c04a34f460978c6b5517a34dd52 \
--hash=sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b \
--hash=sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59 \
--hash=sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752 \
--hash=sha256:e4fe605b917c70283db7dfe5ada75e04561479075761a0b3866c081d035b01c1 \
--hash=sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80 \
--hash=sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839 \
--hash=sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0 \
--hash=sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2 \
--hash=sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3 \
--hash=sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64 \
--hash=sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089 \
--hash=sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643 \
--hash=sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b \
--hash=sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e \
--hash=sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985 \
--hash=sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596 \
--hash=sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2 \
--hash=sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064
# via fonttools
cffi==1.17.1 \
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
--hash=sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1 \
--hash=sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15 \
--hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \
--hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \
--hash=sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8 \
--hash=sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36 \
--hash=sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17 \
--hash=sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf \
--hash=sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc \
--hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \
--hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \
--hash=sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702 \
--hash=sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1 \
--hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \
--hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \
--hash=sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6 \
--hash=sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d \
--hash=sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b \
--hash=sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e \
--hash=sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be \
--hash=sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c \
--hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \
--hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \
--hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \
--hash=sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8 \
--hash=sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1 \
--hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \
--hash=sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655 \
--hash=sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67 \
--hash=sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595 \
--hash=sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0 \
--hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \
--hash=sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41 \
--hash=sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6 \
--hash=sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401 \
--hash=sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6 \
--hash=sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3 \
--hash=sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16 \
--hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \
--hash=sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e \
--hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \
--hash=sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964 \
--hash=sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c \
--hash=sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576 \
--hash=sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0 \
--hash=sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3 \
--hash=sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662 \
--hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \
--hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \
--hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \
--hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \
--hash=sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f \
--hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \
--hash=sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14 \
--hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \
--hash=sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9 \
--hash=sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7 \
--hash=sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382 \
--hash=sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a \
--hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \
--hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a \
--hash=sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4 \
--hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99 \
--hash=sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87 \
--hash=sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b
# via weasyprint
cfgv==3.4.0 \
--hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \
--hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560
# via pre-commit
cssselect2==0.8.0 \
--hash=sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e \
--hash=sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a
# via weasyprint
distlib==0.4.0 \
--hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \
--hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d
# via virtualenv
filelock==3.18.0 \
--hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \
--hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de
# via virtualenv
fonttools==4.59.0 \
--hash=sha256:052444a5d0151878e87e3e512a1aa1a0ab35ee4c28afde0a778e23b0ace4a7de \
--hash=sha256:169b99a2553a227f7b5fea8d9ecd673aa258617f466b2abc6091fe4512a0dcd0 \
--hash=sha256:209b75943d158f610b78320eacb5539aa9e920bee2c775445b2846c65d20e19d \
--hash=sha256:21e606b2d38fed938dde871c5736822dd6bda7a4631b92e509a1f5cd1b90c5df \
--hash=sha256:241313683afd3baacb32a6bd124d0bce7404bc5280e12e291bae1b9bba28711d \
--hash=sha256:26731739daa23b872643f0e4072d5939960237d540c35c14e6a06d47d71ca8fe \
--hash=sha256:2e7cf8044ce2598bb87e44ba1d2c6e45d7a8decf56055b92906dc53f67c76d64 \
--hash=sha256:31003b6a10f70742a63126b80863ab48175fb8272a18ca0846c0482968f0588e \
--hash=sha256:332bfe685d1ac58ca8d62b8d6c71c2e52a6c64bc218dc8f7825c9ea51385aa01 \
--hash=sha256:37c377f7cb2ab2eca8a0b319c68146d34a339792f9420fca6cd49cf28d370705 \
--hash=sha256:37e01c6ec0c98599778c2e688350d624fa4770fbd6144551bd5e032f1199171c \
--hash=sha256:401b1941ce37e78b8fd119b419b617277c65ae9417742a63282257434fd68ea2 \
--hash=sha256:4536f2695fe5c1ffb528d84a35a7d3967e5558d2af58b4775e7ab1449d65767b \
--hash=sha256:4c908a7036f0f3677f8afa577bcd973e3e20ddd2f7c42a33208d18bee95cdb6f \
--hash=sha256:51ab1ff33c19e336c02dee1e9fd1abd974a4ca3d8f7eef2a104d0816a241ce97 \
--hash=sha256:524133c1be38445c5c0575eacea42dbd44374b310b1ffc4b60ff01d881fabb96 \
--hash=sha256:57bb7e26928573ee7c6504f54c05860d867fd35e675769f3ce01b52af38d48e2 \
--hash=sha256:60f6665579e909b618282f3c14fa0b80570fbf1ee0e67678b9a9d43aa5d67a37 \
--hash=sha256:62224a9bb85b4b66d1b46d45cbe43d71cbf8f527d332b177e3b96191ffbc1e64 \
--hash=sha256:6770d7da00f358183d8fd5c4615436189e4f683bdb6affb02cad3d221d7bb757 \
--hash=sha256:6801aeddb6acb2c42eafa45bc1cb98ba236871ae6f33f31e984670b749a8e58e \
--hash=sha256:70d6b3ceaa9cc5a6ac52884f3b3d9544e8e231e95b23f138bdb78e6d4dc0eae3 \
--hash=sha256:78813b49d749e1bb4db1c57f2d4d7e6db22c253cb0a86ad819f5dc197710d4b2 \
--hash=sha256:841b2186adce48903c0fef235421ae21549020eca942c1da773ac380b056ab3c \
--hash=sha256:84fc186980231a287b28560d3123bd255d3c6b6659828c642b4cf961e2b923d0 \
--hash=sha256:885bde7d26e5b40e15c47bd5def48b38cbd50830a65f98122a8fb90962af7cd1 \
--hash=sha256:8b4309a2775e4feee7356e63b163969a215d663399cce1b3d3b65e7ec2d9680e \
--hash=sha256:8d77f92438daeaddc05682f0f3dac90c5b9829bcac75b57e8ce09cb67786073c \
--hash=sha256:902425f5afe28572d65d2bf9c33edd5265c612ff82c69e6f83ea13eafc0dcbea \
--hash=sha256:9bcc1e77fbd1609198966ded6b2a9897bd6c6bcbd2287a2fc7d75f1a254179c5 \
--hash=sha256:a408c3c51358c89b29cfa5317cf11518b7ce5de1717abb55c5ae2d2921027de6 \
--hash=sha256:a9bf8adc9e1f3012edc8f09b08336272aec0c55bc677422273e21280db748f7c \
--hash=sha256:b818db35879d2edf7f46c7e729c700a0bce03b61b9412f5a7118406687cb151d \
--hash=sha256:b8974b2a266b54c96709bd5e239979cddfd2dbceed331aa567ea1d7c4a2202db \
--hash=sha256:be392ec3529e2f57faa28709d60723a763904f71a2b63aabe14fee6648fe3b14 \
--hash=sha256:d3972b13148c1d1fbc092b27678a33b3080d1ac0ca305742b0119b75f9e87e38 \
--hash=sha256:d40dcf533ca481355aa7b682e9e079f766f35715defa4929aeb5597f9604272e \
--hash=sha256:e93df708c69a193fc7987192f94df250f83f3851fda49413f02ba5dded639482 \
--hash=sha256:efd7e6660674e234e29937bc1481dceb7e0336bfae75b856b4fb272b5093c5d4 \
--hash=sha256:f9b3a78f69dcbd803cf2fb3f972779875b244c1115481dfbdd567b2c22b31f6b \
--hash=sha256:fa39475eaccb98f9199eccfda4298abaf35ae0caec676ffc25b3a5e224044464 \
--hash=sha256:fbce6dae41b692a5973d0f2158f782b9ad05babc2c2019a970a1094a23909b1b
# via weasyprint
identify==2.6.13 \
--hash=sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b \
--hash=sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32
# via pre-commit
nodeenv==1.9.1 \
--hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \
--hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9
# via pre-commit
numpy==2.2.6 \
--hash=sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff \
--hash=sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47 \
--hash=sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84 \
--hash=sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d \
--hash=sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6 \
--hash=sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f \
--hash=sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b \
--hash=sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49 \
--hash=sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163 \
--hash=sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571 \
--hash=sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42 \
--hash=sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff \
--hash=sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491 \
--hash=sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4 \
--hash=sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566 \
--hash=sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf \
--hash=sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40 \
--hash=sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd \
--hash=sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06 \
--hash=sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282 \
--hash=sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680 \
--hash=sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db \
--hash=sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3 \
--hash=sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90 \
--hash=sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1 \
--hash=sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289 \
--hash=sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab \
--hash=sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c \
--hash=sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d \
--hash=sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb \
--hash=sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d \
--hash=sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a \
--hash=sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf \
--hash=sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1 \
--hash=sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2 \
--hash=sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a \
--hash=sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543 \
--hash=sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00 \
--hash=sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c \
--hash=sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f \
--hash=sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd \
--hash=sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868 \
--hash=sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303 \
--hash=sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83 \
--hash=sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3 \
--hash=sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d \
--hash=sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87 \
--hash=sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa \
--hash=sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f \
--hash=sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae \
--hash=sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda \
--hash=sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915 \
--hash=sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249 \
--hash=sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de \
--hash=sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8
# via opencv-python-headless
opencv-python-headless==4.12.0.88 \
--hash=sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e \
--hash=sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9 \
--hash=sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154 \
--hash=sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528 \
--hash=sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0 \
--hash=sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09 \
--hash=sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd
# via -r .github\scripts\requirements_dev.in
pdf2image==1.17.0 \
--hash=sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57 \
--hash=sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2
# via -r .github\scripts\requirements_dev.in
pillow==11.3.0 \
--hash=sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2 \
--hash=sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214 \
--hash=sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e \
--hash=sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59 \
--hash=sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50 \
--hash=sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632 \
--hash=sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06 \
--hash=sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a \
--hash=sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 \
--hash=sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced \
--hash=sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f \
--hash=sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12 \
--hash=sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 \
--hash=sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6 \
--hash=sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580 \
--hash=sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f \
--hash=sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac \
--hash=sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860 \
--hash=sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd \
--hash=sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722 \
--hash=sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8 \
--hash=sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4 \
--hash=sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673 \
--hash=sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788 \
--hash=sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542 \
--hash=sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e \
--hash=sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd \
--hash=sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8 \
--hash=sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523 \
--hash=sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967 \
--hash=sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809 \
--hash=sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477 \
--hash=sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027 \
--hash=sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae \
--hash=sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b \
--hash=sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c \
--hash=sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f \
--hash=sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e \
--hash=sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b \
--hash=sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7 \
--hash=sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27 \
--hash=sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361 \
--hash=sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae \
--hash=sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d \
--hash=sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc \
--hash=sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58 \
--hash=sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad \
--hash=sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6 \
--hash=sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024 \
--hash=sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978 \
--hash=sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb \
--hash=sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d \
--hash=sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0 \
--hash=sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9 \
--hash=sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f \
--hash=sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874 \
--hash=sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa \
--hash=sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081 \
--hash=sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149 \
--hash=sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6 \
--hash=sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d \
--hash=sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd \
--hash=sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f \
--hash=sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c \
--hash=sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31 \
--hash=sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e \
--hash=sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db \
--hash=sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6 \
--hash=sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f \
--hash=sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494 \
--hash=sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69 \
--hash=sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94 \
--hash=sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77 \
--hash=sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d \
--hash=sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7 \
--hash=sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a \
--hash=sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438 \
--hash=sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288 \
--hash=sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b \
--hash=sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635 \
--hash=sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3 \
--hash=sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d \
--hash=sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe \
--hash=sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0 \
--hash=sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe \
--hash=sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a \
--hash=sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805 \
--hash=sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8 \
--hash=sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36 \
--hash=sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a \
--hash=sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b \
--hash=sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e \
--hash=sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25 \
--hash=sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12 \
--hash=sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada \
--hash=sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c \
--hash=sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71 \
--hash=sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d \
--hash=sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c \
--hash=sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6 \
--hash=sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1 \
--hash=sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50 \
--hash=sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653 \
--hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c \
--hash=sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4 \
--hash=sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3
# via
# -r .github\scripts\requirements_dev.in
# pdf2image
# weasyprint
platformdirs==4.3.8 \
--hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \
--hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4
# via virtualenv
pre-commit==4.3.0 \
--hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \
--hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16
# via -r .github\scripts\requirements_dev.in
pycparser==2.22 \
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
--hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc
# via cffi
pydyf==0.11.0 \
--hash=sha256:0aaf9e2ebbe786ec7a78ec3fbffa4cdcecde53fd6f563221d53c6bc1328848a3 \
--hash=sha256:394dddf619cca9d0c55715e3c55ea121a9bf9cbc780cdc1201a2427917b86b64
# via weasyprint
pyphen==0.17.2 \
--hash=sha256:3a07fb017cb2341e1d9ff31b8634efb1ae4dc4b130468c7c39dd3d32e7c3affd \
--hash=sha256:f60647a9c9b30ec6c59910097af82bc5dd2d36576b918e44148d8b07ef3b4aa3
# via weasyprint
pyyaml==6.0.2 \
--hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
--hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
--hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
--hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
--hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
--hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
--hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
--hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
--hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
--hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
--hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
--hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
--hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
--hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
--hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
--hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
--hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
--hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
--hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
--hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
--hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
--hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
--hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
--hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
--hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
--hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
--hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
--hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
--hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
--hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
--hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
--hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
--hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
--hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
--hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
--hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
--hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
--hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
--hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
--hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
--hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
--hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
--hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
--hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
--hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
--hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
--hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
--hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
--hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
--hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
--hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
--hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
--hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
# via pre-commit
tinycss2==1.4.0 \
--hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \
--hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289
# via
# cssselect2
# weasyprint
tinyhtml5==2.0.0 \
--hash=sha256:086f998833da24c300c414d9fe81d9b368fd04cb9d2596a008421cbc705fcfcc \
--hash=sha256:13683277c5b176d070f82d099d977194b7a1e26815b016114f581a74bbfbf47e
# via weasyprint
unoserver==3.3.2 \
--hash=sha256:1eeb7467cf6b56b8eff3b576e2d1b2b2ff4e0eb2052e995ac80a1456de300639 \
--hash=sha256:87e144f903ee21951b2e06a97549450c13ed7eca5bcebad942d3352d4e882616
# via -r .github\scripts\requirements_dev.in
virtualenv==20.33.1 \
--hash=sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67 \
--hash=sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8
# via pre-commit
weasyprint==66.0 \
--hash=sha256:82b0783b726fcd318e2c977dcdddca76515b30044bc7a830cc4fbe717582a6d0 \
--hash=sha256:da71dc87dc129ac9cffdc65e5477e90365ab9dbae45c744014ec1d06303dde40
# via -r .github\scripts\requirements_dev.in
webencodings==0.5.1 \
--hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
--hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
# via
# cssselect2
# tinycss2
# tinyhtml5
zopfli==0.2.3.post1 \
--hash=sha256:0aa5f90d6298bda02a95bc8dc8c3c19004d5a4e44bda00b67ca7431d857b4b54 \
--hash=sha256:0cc20b02a9531559945324c38302fd4ba763311632d0ec8a1a0aa9c10ea363e6 \
--hash=sha256:1d8cc06605519e82b16df090e17cb3990d1158861b2872c3117f1168777b81e4 \
--hash=sha256:1f990634fd5c5c8ced8edddd8bd45fab565123b4194d6841e01811292650acae \
--hash=sha256:2345e713260a350bea0b01a816a469ea356bc2d63d009a0d777691ecbbcf7493 \
--hash=sha256:2768c877f76c8a0e7519b1c86c93757f3c01492ddde55751e9988afb7eff64e1 \
--hash=sha256:29ea74e72ffa6e291b8c6f2504ce6c146b4fe990c724c1450eb8e4c27fd31431 \
--hash=sha256:34a99592f3d9eb6f737616b5bd74b48a589fdb3cb59a01a50d636ea81d6af272 \
--hash=sha256:3654bfc927bc478b1c3f3ff5056ed7b20a1a37fa108ca503256d0a699c03bbb1 \
--hash=sha256:3657e416ffb8f31d9d3424af12122bb251befae109f2e271d87d825c92fc5b7b \
--hash=sha256:37d011e92f7b9622742c905fdbed9920a1d0361df84142807ea2a528419dea7f \
--hash=sha256:3827170de28faf144992d3d4dcf8f3998fe3c8a6a6f4a08f1d42c2ec6119d2bb \
--hash=sha256:39e576f93576c5c223b41d9c780bbb91fd6db4babf3223d2a4fe7bf568e2b5a8 \
--hash=sha256:3a89277ed5f8c0fb2d0b46d669aa0633123aa7381f1f6118c12f15e0fb48f8ca \
--hash=sha256:3c163911f8bad94b3e1db0a572e7c28ba681a0c91d0002ea1e4fa9264c21ef17 \
--hash=sha256:3f0197b6aa6eb3086ae9e66d6dd86c4d502b6c68b0ec490496348ae8c05ecaef \
--hash=sha256:48dba9251060289101343110ab47c0756f66f809bb4d1ddbb6d5c7e7752115c5 \
--hash=sha256:4915a41375bdee4db749ecd07d985a0486eb688a6619f713b7bf6fbfd145e960 \
--hash=sha256:4c1226a7e2c7105ac31503a9bb97454743f55d88164d6d46bc138051b77f609b \
--hash=sha256:4e50ffac74842c1c1018b9b73875a0d0a877c066ab06bf7cccbaa84af97e754f \
--hash=sha256:518f1f4ed35dd69ce06b552f84e6d081f07c552b4c661c5312d950a0b764a58a \
--hash=sha256:5aad740b4d4fcbaaae4887823925166ffd062db3b248b3f432198fc287381d1a \
--hash=sha256:5f272186e03ad55e7af09ab78055535c201b1a0bcc2944edb1768298d9c483a4 \
--hash=sha256:5fcfc0dc2761e4fcc15ad5d273b4d58c2e8e059d3214a7390d4d3c8e2aee644e \
--hash=sha256:60db20f06c3d4c5934b16cfa62a2cc5c3f0686bffe0071ed7804d3c31ab1a04e \
--hash=sha256:615a8ac9dda265e9cc38b2a76c3142e4a9f30fea4a79c85f670850783bc6feb4 \
--hash=sha256:6482db9876c68faac2d20a96b566ffbf65ddaadd97b222e4e73641f4f8722fc4 \
--hash=sha256:6617fb10f9e4393b331941861d73afb119cd847e88e4974bdbe8068ceef3f73f \
--hash=sha256:676919fba7311125244eb0c4393679ac5fe856e5864a15d122bd815205369fa0 \
--hash=sha256:6c2d2bc8129707e34c51f9352c4636ca313b52350bbb7e04637c46c1818a2a70 \
--hash=sha256:71390dbd3fbf6ebea9a5d85ffed8c26ee1453ee09248e9b88486e30e0397b775 \
--hash=sha256:716cdbfc57bfd3d3e31a58e6246e8190e6849b7dbb7c4ce39ef8bbf0edb8f6d5 \
--hash=sha256:75a26a2307b10745a83b660c404416e984ee6fca515ec7f0765f69af3ce08072 \
--hash=sha256:7be5cc6732eb7b4df17305d8a7b293223f934a31783a874a01164703bc1be6cd \
--hash=sha256:7cce242b5df12b2b172489daf19c32e5577dd2fac659eb4b17f6a6efb446fd5c \
--hash=sha256:81c341d9bb87a6dbbb0d45d6e272aca80c7c97b4b210f9b6e233bf8b87242f29 \
--hash=sha256:89899641d4de97dbad8e0cde690040d078b6aea04066dacaab98e0b5a23573f2 \
--hash=sha256:8d5ab297d660b75c159190ce6d73035502310e40fd35170aed7d1a1aea7ddd65 \
--hash=sha256:8fbe5bcf10d01aab3513550f284c09fef32f342b36f56bfae2120a9c4d12c130 \
--hash=sha256:91a2327a4d7e77471fa4fbb26991c6de4a738c6fc6a33e09bb25f56a870a4b7b \
--hash=sha256:95a260cafd56b8fffa679918937401c80bb38e1681c448b988022e4c3610965d \
--hash=sha256:96484dc0f48be1c5d7ae9f38ed1ce41e3675fd506b27c11a6607f14b49101e99 \
--hash=sha256:9a6aec38a989bad7ddd1ef53f1265699e49e294d08231b5313d61293f3cd6237 \
--hash=sha256:9ba214f4f45bec195ee8559651154d3ac2932470b9d91c5715fc29c013349f8c \
--hash=sha256:9f4a7ec2770e6af05f5a02733fd3900f30a9cd58e5d6d3727e14c5bcd6e7d587 \
--hash=sha256:a1cf720896d2ce998bc8e051d4b4ce0d8bec007aab6243102e8e1d22a0b2fb3f \
--hash=sha256:a241a68581d34d67b40c425cce3d1fd211c092f99d9250947824ccba9f491949 \
--hash=sha256:a53b18797cdef27e019db595d66c4b077325afe2fd62145953275f53d84ce40c \
--hash=sha256:a82fc2dbebe6eb908b9c665e71496f8525c1bc4d2e3a7a7722ef2b128b6227c8 \
--hash=sha256:a86eb88e06bd87e1fff31dac878965c26b0c26db59ddcf78bb0379a954b120de \
--hash=sha256:aa588b21044f8a74e423d8c8a4c7fc9988501878aacced793467010039c50734 \
--hash=sha256:b05296e8bc88c92e2b21e0a9bae4740c1551ee613c1d93a51fd28a7a0b2b6fbb \
--hash=sha256:b0ec13f352ea5ae0fc91f98a48540512eed0767d0ec4f7f3cb92d92797983d18 \
--hash=sha256:b3df42f52502438ee973042cc551877d24619fa1cd38ef7b7e9ac74200daca8b \
--hash=sha256:b78008a69300d929ca2efeffec951b64a312e9a811e265ea4a907ab546d79fa6 \
--hash=sha256:b9026a21b6d41eb0e2e63f5bc1242c3fcc43ecb770963cda99a4307863dac12e \
--hash=sha256:bbe429fc50686bb2a2608a30843e36fbaa123462a5284f136c7d9e0145220bfd \
--hash=sha256:bfa1eb759e07d8b7aa7a310a2bc535e127ee70addf90dc8d4b946b593c3e51a8 \
--hash=sha256:c1e0ed5d84ffa2d677cc9582fc01e61dab2e7ef8b8996e055f0a76167b1b94df \
--hash=sha256:c4278d1873ce6e803e5d4f8d702fd3026bd67fca744aa98881324d1157ddf748 \
--hash=sha256:cac2b37ab21c2b36a10b685b1893ebd6b0f83ae26004838ac817680881576567 \
--hash=sha256:cbe6df25807227519debd1a57ab236f5f6bad441500e85b13903e51f93a43214 \
--hash=sha256:cd2c002f160502608dcc822ed2441a0f4509c52e86fcfd1a09e937278ed1ca14 \
--hash=sha256:e0137dd64a493ba6a4be37405cfd6febe650a98cc1e9dca8f6b8c63b1db11b41 \
--hash=sha256:e63d558847166543c2c9789e6f985400a520b7eacc4b99181668b2c3aeadd352 \
--hash=sha256:eb45a34f23da4f8bc712b6376ca5396914b0b7c09adbb001dad964eb7f3132f8 \
--hash=sha256:ecb7572df5372abce8073df078207d9d1749f20b8b136089916a4a0868d56051 \
--hash=sha256:f12000a6accdd4bf0a3fa6eaa1b1c7a7bc80af0a2edf3f89d770d3dcce1d0e22 \
--hash=sha256:f7d69c1a7168ad0e9cb864e8663acb232986a0c9c9cb9801f56bf6214f53a54d \
--hash=sha256:f815fcc2b2a457977724bad97fb4854022980f51ce7b136925e336b530545ae1 \
--hash=sha256:fc39f5c27f962ec8660d8d20c24762431131b5d8c672b44b0a54cf2b5bcde9b9
# via fonttools
# The following packages are considered to be unsafe in a requirements file:
pip==25.2 \
--hash=sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2 \
--hash=sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717
# via -r .github\scripts\requirements_dev.in
setuptools==80.9.0 \
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
# via -r .github\scripts\requirements_dev.in

View File

@ -33,8 +33,6 @@ jobs:
)
outputs:
pr_number: ${{ steps.get-pr.outputs.pr_number }}
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
comment_id: ${{ github.event.comment.id }}
disable_security: ${{ steps.check-security-flag.outputs.disable_security }}
enable_pro: ${{ steps.check-pro-flag.outputs.enable_pro }}
@ -66,29 +64,6 @@ jobs:
console.log(`PR Number: ${prNumber}`);
core.setOutput('pr_number', prNumber);
- name: Get PR repository and ref
id: get-pr-info
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber,
});
// For forks, use the full repository name, for internal PRs use the current repo
const repository = pr.head.repo.fork ? pr.head.repo.full_name : `${owner}/${repo}`;
console.log(`PR Repository: ${repository}`);
console.log(`PR Branch: ${pr.head.ref}`);
core.setOutput('repository', repository);
core.setOutput('ref', pr.head.ref);
- name: Check for security/login flag
id: check-security-flag
env:
@ -171,8 +146,7 @@ jobs:
- name: Checkout PR
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
with:
repository: ${{ needs.check-comment.outputs.pr_repository }}
ref: ${{ needs.check-comment.outputs.pr_ref }}
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
token: ${{ steps.setup-bot.outputs.token }}
- name: Set up JDK

View File

@ -175,10 +175,13 @@ jobs:
java-version: "17"
distribution: "temurin"
- name: check the licenses for compatibility
- name: Check licenses for compatibility
run: ./gradlew clean checkLicense
env:
DISABLE_ADDITIONAL_FEATURES: false
STIRLING_PDF_DESKTOP_UI: true
- name: FAILED - check the licenses for compatibility
- name: FAILED - Check licenses for compatibility
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:

View File

@ -58,6 +58,9 @@ jobs:
- name: Check licenses for compatibility
run: ./gradlew clean checkLicense
env:
DISABLE_ADDITIONAL_FEATURES: false
STIRLING_PDF_DESKTOP_UI: true
- name: Upload artifact on failure
if: failure()

View File

@ -74,6 +74,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.5
uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.5
with:
sarif_file: results.sarif

View File

@ -34,10 +34,10 @@ ENV SETUPTOOLS_USE_DISTUTILS=local \
TMP=/tmp/stirling-pdf
# Installation der benötigten Python-Pakete
COPY .github/scripts/requirements_dev.txt /tmp/requirements_dev.txt
RUN python3 -m venv --system-site-packages /opt/venv \
&& . /opt/venv/bin/activate \
&& pip install --no-cache-dir --upgrade pip setuptools \
&& pip install --no-cache-dir WeasyPrint pdf2image pillow unoserver opencv-python-headless pre-commit
&& pip install --no-cache-dir --require-hashes -r /tmp/requirements_dev.txt
# Füge den venv-Pfad zur globalen PATH-Variable hinzu, damit die Tools verfügbar sind
ENV PATH="/opt/venv/bin:$PATH"

View File

@ -39,7 +39,7 @@ dependencies {
api "org.apache.pdfbox:pdfbox:$pdfboxVersion"
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
api 'org.snakeyaml:snakeyaml-engine:2.10'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9"
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.11"
api 'jakarta.mail:jakarta.mail-api:2.1.3'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.4'
}

View File

@ -23,10 +23,30 @@ import stirling.software.common.util.YamlHelper;
@Slf4j
public class ConfigInitializer {
private static final int MIN_SETTINGS_FILE_LINES = 31;
public void ensureConfigExists() throws IOException, URISyntaxException {
// 1) If settings file doesn't exist, create from template
Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());
if (Files.notExists(destPath)) {
boolean settingsFileExists = Files.exists(destPath);
long lineCount = settingsFileExists ? Files.readAllLines(destPath).size() : 0;
log.info("Current settings file line count: {}", lineCount);
if (!settingsFileExists || lineCount < MIN_SETTINGS_FILE_LINES) {
if (settingsFileExists) {
// move settings.yml to settings.yml.{timestamp}.bak
Path backupPath =
Paths.get(
InstallationPathConfig.getSettingsPath()
+ "."
+ System.currentTimeMillis()
+ ".bak");
Files.move(destPath, backupPath, StandardCopyOption.REPLACE_EXISTING);
log.info("Moved existing settings file to backup: {}", backupPath);
}
Files.createDirectories(destPath.getParent());
try (InputStream in =
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {

View File

@ -14,18 +14,22 @@ public class InstallationPathConfig {
private static final String CONFIG_PATH;
private static final String CUSTOM_FILES_PATH;
private static final String CLIENT_WEBUI_PATH;
private static final String SCRIPTS_PATH;
private static final String PIPELINE_PATH;
// Config paths
private static final String SETTINGS_PATH;
private static final String CUSTOM_SETTINGS_PATH;
private static final String SCRIPTS_PATH;
private static final String BACKUP_PATH;
// Backup paths
private static final String BACKUP_DB_PATH;
private static final String BACKUP_PRIVATE_KEY_PATH;
// Custom file paths
private static final String STATIC_PATH;
private static final String TEMPLATES_PATH;
private static final String SIGNATURES_PATH;
private static final String PRIVATE_KEY_PATH;
static {
BASE_PATH = initializeBasePath();
@ -41,12 +45,16 @@ public class InstallationPathConfig {
SETTINGS_PATH = CONFIG_PATH + "settings.yml";
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
BACKUP_PATH = CONFIG_PATH + "backup" + File.separator;
// Initialize backup paths
BACKUP_DB_PATH = BACKUP_PATH + "db" + File.separator;
BACKUP_PRIVATE_KEY_PATH = BACKUP_PATH + "keys" + File.separator;
// Initialize custom file paths
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
PRIVATE_KEY_PATH = CONFIG_PATH + "db" + File.separator + "keys" + File.separator;
}
private static String initializeBasePath() {
@ -124,6 +132,10 @@ public class InstallationPathConfig {
}
public static String getPrivateKeyPath() {
return PRIVATE_KEY_PATH;
return BACKUP_PRIVATE_KEY_PATH;
}
public static String getBackupPath() {
return BACKUP_DB_PATH;
}
}

View File

@ -1,5 +1,7 @@
package stirling.software.common.service;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
@ -51,16 +53,12 @@ public class SsrfProtectionService {
SsrfProtectionLevel level = parseProtectionLevel(config.getLevel());
switch (level) {
case OFF:
return true;
case MAX:
return isMaxSecurityAllowed(trimmedUrl, config);
case MEDIUM:
return isMediumSecurityAllowed(trimmedUrl, config);
default:
return false;
}
return switch (level) {
case OFF -> true;
case MAX -> isMaxSecurityAllowed(trimmedUrl, config);
case MEDIUM -> isMediumSecurityAllowed(trimmedUrl, config);
default -> false;
};
}
private SsrfProtectionLevel parseProtectionLevel(String level) {
@ -172,15 +170,61 @@ public class SsrfProtectionService {
}
private boolean isPrivateAddress(InetAddress address) {
return address.isSiteLocalAddress()
|| address.isAnyLocalAddress()
|| isPrivateIPv4Range(address.getHostAddress());
if (address.isAnyLocalAddress() || address.isLoopbackAddress()) {
return true;
}
if (address instanceof Inet4Address) {
return isPrivateIPv4Range(address.getHostAddress());
}
if (address instanceof Inet6Address addr6) {
if (addr6.isLinkLocalAddress() || addr6.isSiteLocalAddress()) {
return true;
}
byte[] bytes = addr6.getAddress();
if (isIpv4MappedAddress(bytes)) {
String ipv4 =
(bytes[12] & 0xff)
+ "."
+ (bytes[13] & 0xff)
+ "."
+ (bytes[14] & 0xff)
+ "."
+ (bytes[15] & 0xff);
return isPrivateIPv4Range(ipv4);
}
int firstByte = bytes[0] & 0xff;
// Check for IPv6 unique local addresses (fc00::/7)
if ((firstByte & 0xfe) == 0xfc) {
return true;
}
}
return false;
}
private boolean isIpv4MappedAddress(byte[] addr) {
if (addr.length != 16) {
return false;
}
for (int i = 0; i < 10; i++) {
if (addr[i] != 0) {
return false;
}
}
// For IPv4-mapped IPv6 addresses, bytes 10 and 11 must be 0xff (i.e., address is ::ffff:w.x.y.z)
return addr[10] == (byte) 0xff && addr[11] == (byte) 0xff;
}
private boolean isPrivateIPv4Range(String ip) {
// Includes RFC1918, loopback, link-local, and unspecified addresses
return ip.startsWith("10.")
|| ip.startsWith("192.168.")
|| (ip.startsWith("172.") && isInRange172(ip))
|| ip.startsWith("169.254.")
|| ip.startsWith("127.")
|| "0.0.0.0".equals(ip);
}
@ -192,17 +236,31 @@ public class SsrfProtectionService {
int secondOctet = Integer.parseInt(parts[1]);
return secondOctet >= 16 && secondOctet <= 31;
} catch (NumberFormatException e) {
return false;
}
}
return false;
}
private boolean isCloudMetadataAddress(String ip) {
String normalizedIp = normalizeIpv4MappedAddress(ip);
// Cloud metadata endpoints for AWS, GCP, Azure, Oracle Cloud, and IBM Cloud
return ip.startsWith("169.254.169.254") // AWS/GCP/Azure
|| ip.startsWith("fd00:ec2::254") // AWS IPv6
|| ip.startsWith("169.254.169.253") // Oracle Cloud
|| ip.startsWith("169.254.169.250"); // IBM Cloud
return normalizedIp.startsWith("169.254.169.254") // AWS/GCP/Azure
|| normalizedIp.startsWith("fd00:ec2::254") // AWS IPv6
|| normalizedIp.startsWith("169.254.169.253") // Oracle Cloud
|| normalizedIp.startsWith("169.254.169.250"); // IBM Cloud
}
private String normalizeIpv4MappedAddress(String ip) {
if (ip == null) {
return "";
}
if (ip.startsWith("::ffff:")) {
return ip.substring(7);
}
int lastColon = ip.lastIndexOf(':');
if (lastColon >= 0 && ip.indexOf('.') > lastColon) {
return ip.substring(lastColon + 1);
}
return ip;
}
}

View File

@ -179,7 +179,7 @@ class ApplicationPropertiesLogicTest {
assertEquals(30, t.getOcrMyPdfTimeoutMinutes());
}
@Deprecated
@Deprecated(since = "0.45.0")
@Test
void enterprise_metadata_defaults() {
ApplicationProperties.EnterpriseEdition ee = new ApplicationProperties.EnterpriseEdition();

View File

@ -58,7 +58,7 @@ dependencies {
implementation 'commons-io:commons-io:2.20.0'
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
implementation 'io.micrometer:micrometer-core:1.15.2'
implementation 'io.micrometer:micrometer-core:1.15.3'
implementation 'com.google.zxing:core:3.5.3'
implementation "org.commonmark:commonmark:$commonmarkVersion" // https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation "org.commonmark:commonmark-ext-gfm-tables:$commonmarkVersion"

View File

@ -10,23 +10,23 @@ import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.PDFText;
@Slf4j
public class TextFinder extends PDFTextStripper {
private final String searchTerm;
private final boolean useRegex;
private final boolean wholeWordSearch;
@Getter private final List<PDFText> foundTexts = new ArrayList<>();
private final List<PDFText> foundTexts = new ArrayList<>();
private final List<TextPosition> pageTextPositions = new ArrayList<>();
private final StringBuilder pageTextBuilder = new StringBuilder();
public TextFinder(String searchTerm, boolean useRegex, boolean wholeWordSearch)
throws IOException {
super();
this.searchTerm = searchTerm;
this.useRegex = useRegex;
this.wholeWordSearch = wholeWordSearch;
@ -42,39 +42,20 @@ public class TextFinder extends PDFTextStripper {
@Override
protected void writeString(String text, List<TextPosition> textPositions) {
for (TextPosition tp : textPositions) {
if (tp == null) continue;
String u = tp.getUnicode();
if (u == null) continue;
for (int i = 0; i < u.length(); ) {
int cp = u.codePointAt(i);
pageTextBuilder.append(Character.toChars(cp));
// Add one position per code unit appended (1-2 chars depending on surrogate)
int codeUnits = Character.charCount(cp);
for (int k = 0; k < codeUnits; k++) {
pageTextPositions.add(tp);
}
i += codeUnits;
}
}
pageTextBuilder.append(text);
pageTextPositions.addAll(textPositions);
}
@Override
protected void writeWordSeparator() {
String sep = getWordSeparator();
pageTextBuilder.append(sep);
for (int i = 0; i < sep.length(); i++) {
pageTextPositions.add(null);
}
pageTextBuilder.append(getWordSeparator());
pageTextPositions.add(null); // Placeholder for separator
}
@Override
protected void writeLineSeparator() {
String sep = getLineSeparator();
pageTextBuilder.append(sep);
for (int i = 0; i < sep.length(); i++) {
pageTextPositions.add(null);
}
pageTextBuilder.append(getLineSeparator());
pageTextPositions.add(null); // Placeholder for separator
}
@Override
@ -86,12 +67,10 @@ public class TextFinder extends PDFTextStripper {
}
String processedSearchTerm = this.searchTerm.trim();
if (processedSearchTerm.isEmpty()) {
super.endPage(page);
return;
}
String regex = this.useRegex ? processedSearchTerm : "\\Q" + processedSearchTerm + "\\E";
if (this.wholeWordSearch) {
if (processedSearchTerm.length() == 1
@ -107,10 +86,27 @@ public class TextFinder extends PDFTextStripper {
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Matcher matcher = pattern.matcher(text);
log.debug(
"Searching for '{}' in page {} with regex '{}' (wholeWord: {}, useRegex: {})",
processedSearchTerm,
getCurrentPageNo(),
regex,
wholeWordSearch,
useRegex);
int matchCount = 0;
while (matcher.find()) {
matchCount++;
int matchStart = matcher.start();
int matchEnd = matcher.end();
log.debug(
"Found match #{} at positions {}-{}: '{}'",
matchCount,
matchStart,
matchEnd,
matcher.group());
float minX = Float.MAX_VALUE;
float minY = Float.MAX_VALUE;
float maxX = Float.MIN_VALUE;
@ -118,7 +114,13 @@ public class TextFinder extends PDFTextStripper {
boolean foundPosition = false;
for (int i = matchStart; i < matchEnd; i++) {
if (i >= pageTextPositions.size()) continue;
if (i >= pageTextPositions.size()) {
log.debug(
"Position index {} exceeds available positions ({})",
i,
pageTextPositions.size());
continue;
}
TextPosition pos = pageTextPositions.get(i);
if (pos != null) {
foundPosition = true;
@ -130,6 +132,11 @@ public class TextFinder extends PDFTextStripper {
}
if (!foundPosition && matchStart < pageTextPositions.size()) {
log.debug(
"Attempting to find nearby positions for match at {}-{}",
matchStart,
matchEnd);
for (int i = Math.max(0, matchStart - 5);
i < Math.min(pageTextPositions.size(), matchEnd + 5);
i++) {
@ -154,14 +161,36 @@ public class TextFinder extends PDFTextStripper {
maxX,
maxY,
matcher.group()));
log.debug(
"Added PDFText for match: page={}, bounds=({},{},{},{}), text='{}'",
getCurrentPageNo() - 1,
minX,
minY,
maxX,
maxY,
matcher.group());
} else {
// no position info
log.warn(
"Found text match '{}' but no valid position data at {}-{}",
matcher.group(),
matchStart,
matchEnd);
}
}
log.debug(
"Page {} search complete: found {} matches for '{}'",
getCurrentPageNo(),
matchCount,
processedSearchTerm);
super.endPage(page);
}
public List<PDFText> getFoundTexts() {
return foundTexts;
}
public String getDebugInfo() {
StringBuilder debug = new StringBuilder();
debug.append("Extracted text length: ").append(pageTextBuilder.length()).append("\n");

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=الصفحات المراد ترقيمها
addPageNumbers.selectText.6=نص مخصص
addPageNumbers.customTextDesc=نص مخصص
addPageNumbers.numberPagesDesc=أي الصفحات المراد ترقيمها، الافتراضي 'الكل'، يقبل أيضًا 1-5 أو 2,5,9 إلخ
addPageNumbers.customNumberDesc=الافتراضي هو {n}، يقبل أيضًا 'الصفحة {n} من {total}'، 'نص-{n}'، '{filename}-{n}
addPageNumbers.customNumberDesc=الافتراضي هو {n}، يقبل أيضًا 'الصفحة {n} من {total}'، 'نص-{n}'، '{filename}-{n}'
addPageNumbers.submit=إضافة أرقام الصفحات

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Səhifələrə nömrə əlavə edin
addPageNumbers.selectText.6=Fərdi Mətn
addPageNumbers.customTextDesc=Fərdi Mətn
addPageNumbers.numberPagesDesc=Hansı səhifələrin nömrələnəcəyini seçin, default 'all', və ya 1-5, 2,5,9 kimi yazılış qəbul olunur
addPageNumbers.customNumberDesc=Defolt olaraq {n}, və ya 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Defolt olaraq {n}, və ya 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Səhifə Nömrələri əlavə edin

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Страници към номер
addPageNumbers.selectText.6=Персонализиран текст
addPageNumbers.customTextDesc=Персонализиран текст
addPageNumbers.numberPagesDesc=Кои страници да номерирате, по подразбиране 'всички', също приема 1-5 или 2,5,9 и т.н.
addPageNumbers.customNumberDesc=По подразбиране е {n}, също приема 'Страница {n} от {total}', 'Текст-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=По подразбиране е {n}, също приема 'Страница {n} от {total}', 'Текст-{n}', '{filename}-{n}'
addPageNumbers.submit=Добавяне на номера на страници

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sider at nummerere
addPageNumbers.selectText.6=Brugerdefineret Tekst
addPageNumbers.customTextDesc=Brugerdefineret Tekst
addPageNumbers.numberPagesDesc=Hvilke sider der skal nummereres, standard 'alle', accepterer også 1-5 eller 2,5,9 osv.
addPageNumbers.customNumberDesc=Standard er {n}, accepterer også 'Side {n} af {total}', 'Tekst-{n}', '{filnavn}-{n}
addPageNumbers.customNumberDesc=Standard er {n}, accepterer også 'Side {n} af {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Tilføj Sidenumre

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Σελίδες προς αρίθμηση
addPageNumbers.selectText.6=Προσαρμοσμένο κείμενο
addPageNumbers.customTextDesc=Προσαρμοσμένο κείμενο
addPageNumbers.numberPagesDesc=Ποιες σελίδες να αριθμηθούν, προεπιλογή 'all', δέχεται επίσης 1-5 ή 2,5,9 κλπ
addPageNumbers.customNumberDesc=Προεπιλογή σε {n}, δέχεται επίσης 'Σελίδα {n} από {total}', 'Κείμενο-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Προεπιλογή σε {n}, δέχεται επίσης 'Σελίδα {n} από {total}', 'Κείμενο-{n}', '{filename}-{n}'
addPageNumbers.submit=Προσθήκη αριθμών σελίδων

View File

@ -1090,7 +1090,7 @@ addPageNumbers.selectText.5=Pages to Number
addPageNumbers.selectText.6=Custom Text
addPageNumbers.customTextDesc=Custom Text
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Add Page Numbers

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pages to Number
addPageNumbers.selectText.6=Custom Text
addPageNumbers.customTextDesc=Custom Text
addPageNumbers.numberPagesDesc=Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Add Page Numbers

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a numerar
addPageNumbers.selectText.6=Texto personalizado
addPageNumbers.customTextDesc=Texto personalizado
addPageNumbers.numberPagesDesc=Qué páginas numerar, por defecto 'todas', también acepta 1-5 o 2,5,9 etc
addPageNumbers.customNumberDesc=Por defecto a {n}, también acepta 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Por defecto a {n}, también acepta 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}'
addPageNumbers.submit=Añadir Números de Página

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Leathanaigh go hUimhir
addPageNumbers.selectText.6=Téacs Saincheaptha
addPageNumbers.customTextDesc=Téacs Saincheaptha
addPageNumbers.numberPagesDesc=Cé na leathanaigh le huimhriú, réamhshocraithe 'gach duine', a ghlacann freisin 1-5 nó 2,5,9 etc
addPageNumbers.customNumberDesc=Réamhshocrú go {n}, glacann sé freisin le 'Leathanach {n} de {total}', 'Text-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Réamhshocrú go {n}, glacann sé freisin le 'Leathanach {n} de {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Cuir Uimhreacha Leathanaigh leis

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Brojanje stranica
addPageNumbers.selectText.6=Prilagođeni tekst
addPageNumbers.customTextDesc=Prilagođeni tekst
addPageNumbers.numberPagesDesc=Koje stranice numerirati, zadano je 'sve', također prihvaća 1-5 ili 2,5,9 itd.
addPageNumbers.customNumberDesc=Zadano je {n}, također prihvaća 'Stranica {n} od {total}', 'Tekst-{n}', '{ime datoteke}-{n}'
addPageNumbers.customNumberDesc=Zadano je {n}, također prihvaća 'Stranica {n} od {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Dodaj brojeve stranica

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Halaman ke Nomor
addPageNumbers.selectText.6=Teks Khusus
addPageNumbers.customTextDesc=Teks Khusus
addPageNumbers.numberPagesDesc=Halaman mana yang akan diberi nomor, default 'semua', juga menerima 1-5 atau 2,5,9, dll.
addPageNumbers.customNumberDesc=Default untuk {n}, juga menerima 'Halaman {n} dari {total}', 'Teks-{n}', '{nama berkas}-{n}'
addPageNumbers.customNumberDesc=Default untuk {n}, juga menerima 'Halaman {n} dari {total}', 'Teks-{n}', '{filename}-{n}'
addPageNumbers.submit=Tambahkan Nomor Halaman

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagine da numerare
addPageNumbers.selectText.6=Testo personalizzato
addPageNumbers.customTextDesc=Testo personalizzato
addPageNumbers.numberPagesDesc=Quali pagine numerare, impostazione predefinita "tutte", accetta anche 1-5 o 2,5,9 ecc
addPageNumbers.customNumberDesc=Il valore predefinito è {n}, accetta anche 'Pagina {n} di {total}', 'Testo-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Il valore predefinito è {n}, accetta anche 'Pagina {n} di {total}', 'Testo-{n}', '{filename}-{n}'
addPageNumbers.submit=Aggiungi numeri di pagina

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagina's om te nummeren
addPageNumbers.selectText.6=Aangepaste tekst
addPageNumbers.customTextDesc=Aangepaste tekst
addPageNumbers.numberPagesDesc=Welke pagina's genummerd moeten worden, standaard 'all', accepteert ook 1-5 of 2,5,9 etc
addPageNumbers.customNumberDesc=Standaard {n}, accepteert ook 'Pagina {n} van {total}', 'Tekst-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Standaard {n}, accepteert ook 'Pagina {n} van {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Paginanummers toevoegen

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sider å nummerere
addPageNumbers.selectText.6=Tilpasset Tekst
addPageNumbers.customTextDesc=Tilpasset Tekst
addPageNumbers.numberPagesDesc=Hvilke sider som skal nummereres, standard 'alle', aksepterer også 1-5 eller 2,5,9 osv.
addPageNumbers.customNumberDesc=Standard til {n}, aksepterer også 'Side {n} av {total}', 'Tekst-{n}', '{filnavn}-{n}
addPageNumbers.customNumberDesc=Standard til {n}, aksepterer også 'Side {n} av {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Legg til Sidetall

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Ilość stron do ponumerowania
addPageNumbers.selectText.6=Tekst własny
addPageNumbers.customTextDesc=Tekst własny
addPageNumbers.numberPagesDesc=Strony do numeracji, wszystkie (all), 1-5, 2, 5, 9
addPageNumbers.customNumberDesc=Domyślnie do {n}, również akceptuje 'Strona {n} z {total},Teskt-{n},'{filename}-{n}
addPageNumbers.customNumberDesc=Domyślnie do {n}, również akceptuje 'Strona {n} z {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Dodaj numerację stron

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a numerar:
addPageNumbers.selectText.6=Texto personalizado:
addPageNumbers.customTextDesc=Texto personalizado:
addPageNumbers.numberPagesDesc=Quais páginas numerar, padrão 'todas', também aceita 1-5 ou 2,5,9,etc.
addPageNumbers.customNumberDesc=O padrão é {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{nome do arquivo}-{n}'
addPageNumbers.customNumberDesc=O padrão é {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}'
addPageNumbers.submit=Adicionar Números de Página

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Páginas a Numerar
addPageNumbers.selectText.6=Texto Personalizado
addPageNumbers.customTextDesc=Texto Personalizado
addPageNumbers.numberPagesDesc=Quais páginas a numerar, predefinição 'todas', também aceita 1-5 ou 2,5,9 etc
addPageNumbers.customNumberDesc=Predefinição {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Predefinição {n}, também aceita 'Página {n} de {total}', 'Texto-{n}', '{filename}-{n}'
addPageNumbers.submit=Adicionar Números de Página

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Pagini de Numerotat
addPageNumbers.selectText.6=Text Personalizat
addPageNumbers.customTextDesc=Text Personalizat
addPageNumbers.numberPagesDesc=Ce pagini să numeroteze, implicit 'toate', acceptă și 1-5 sau 2,5,9 etc
addPageNumbers.customNumberDesc=Implicit la {n}, acceptă și 'Pagina {n} din {total}', 'Text-{n}', '{nume_fisier}-{n}
addPageNumbers.customNumberDesc=Implicit la {n}, acceptă și 'Pagina {n} din {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Adaugă Numere de Pagină

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Stránky na číslovanie
addPageNumbers.selectText.6=Vlastný text
addPageNumbers.customTextDesc=Vlastný text
addPageNumbers.numberPagesDesc=Ktoré stránky číslovať, predvolené 'všetky', tiež akceptuje 1-5 alebo 2,5,9 atď.
addPageNumbers.customNumberDesc=Predvolené {n}, tiež akceptuje 'Strana {n} z {total}', 'Text-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Predvolené {n}, tiež akceptuje 'Strana {n} z {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Pridať čísla stránok

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Strani v številko
addPageNumbers.selectText.6=Besedilo po meri
addPageNumbers.customTextDesc=Besedilo po meri
addPageNumbers.numberPagesDesc=Katere strani oštevilčiti, privzeto 'vse', sprejema tudi 1-5 ali 2,5,9 itd.
addPageNumbers.customNumberDesc=Privzeto na {n}, sprejema tudi 'Stran {n} od {total}', 'Besedilo-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Privzeto na {n}, sprejema tudi 'Stran {n} od {total}', 'Besedilo-{n}', '{filename}-{n}'
addPageNumbers.submit=Dodaj številke strani

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Stranice za numerisanje:
addPageNumbers.selectText.6=Prilagođeni tekst:
addPageNumbers.customTextDesc=Prilagođeni tekst
addPageNumbers.numberPagesDesc=Koje stranice brojati, podrazumevano 'sve', takođe prihvata 1-5 ili 2,5,9 itd.
addPageNumbers.customNumberDesc=Podrazumevano je {n}, takođe prihvata 'Stranica {n} od {ukupno}', 'Tekst-{n}', '{ime_fajla}-{n}'
addPageNumbers.customNumberDesc=Podrazumevano je {n}, takođe prihvata 'Stranica {n} od {total}', 'Tekst-{n}', '{filename}-{n}'
addPageNumbers.submit=Numeriši

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Sidor att numrera
addPageNumbers.selectText.6=Anpassad text
addPageNumbers.customTextDesc=Anpassad text
addPageNumbers.numberPagesDesc=Vilka sidor som ska numreras, standard 'all', accepterar även 1-5 eller 2,5,9 etc
addPageNumbers.customNumberDesc=Standard är {n}, accepterar även 'Sida {n} av {total}', 'Text-{n}', '{filnamn}-{n}
addPageNumbers.customNumberDesc=Standard är {n}, accepterar även 'Sida {n} av {total}', 'Text-{n}', '{filename}-{n}'
addPageNumbers.submit=Lägg till sidnummer

View File

@ -1,138 +1,138 @@
###########
# Generic #
###########
# the direction that the language is written (ltr = left to right, rtl = right to left)
# the direction that the language is written (ltr=left to right, rtl=right to left)
language.direction=ltr
# Language names for reuse throughout the application
lang.afr=Afrikaans
lang.amh=Amharic
lang.ara=Arabic
lang.asm=Assamese
lang.aze=Azerbaijani
lang.aze_cyrl=Azerbaijani (Cyrillic)
lang.bel=Belarusian
lang.ben=Bengali
lang.bod=Tibetan
lang.bos=Bosnian
lang.bre=Breton
lang.bul=Bulgarian
lang.cat=Catalan
lang.ceb=Cebuano
lang.ces=Czech
lang.chi_sim=Chinese (Simplified)
lang.chi_sim_vert=Chinese (Simplified, Vertical)
lang.chi_tra=Chinese (Traditional)
lang.chi_tra_vert=Chinese (Traditional, Vertical)
lang.chr=Cherokee
lang.cos=Corsican
lang.cym=Welsh
lang.dan=Danish
lang.dan_frak=Danish (Fraktur)
lang.deu=German
lang.deu_frak=German (Fraktur)
lang.div=Divehi
lang.dzo=Dzongkha
lang.ell=Greek
lang.eng=English
lang.enm=English, Middle (1100-1500)
lang.epo=Esperanto
lang.equ=Math / equation detection module
lang.est=Estonian
lang.eus=Basque
lang.fao=Faroese
lang.fas=Persian
lang.fil=Filipino
lang.fin=Finnish
lang.fra=French
lang.frk=Frankish
lang.frm=French, Middle (ca.1400-1600)
lang.fry=Western Frisian
lang.gla=Scottish Gaelic
lang.gle=Irish
lang.glg=Galician
lang.grc=Ancient Greek
lang.guj=Gujarati
lang.hat=Haitian, Haitian Creole
lang.heb=Hebrew
lang.hin=Hindi
lang.hrv=Croatian
lang.hun=Hungarian
lang.hye=Armenian
lang.iku=Inuktitut
lang.ind=Indonesian
lang.isl=Icelandic
lang.ita=Italian
lang.ita_old=Italian (Old)
lang.jav=Javanese
lang.jpn=Japanese
lang.jpn_vert=Japanese (Vertical)
lang.kan=Kannada
lang.kat=Georgian
lang.kat_old=Georgian (Old)
lang.kaz=Kazakh
lang.khm=Central Khmer
lang.kir=Kirghiz, Kyrgyz
lang.kmr=Northern Kurdish
lang.kor=Korean
lang.kor_vert=Korean (Vertical)
lang.lao=Lao
lang.lat=Latin
lang.lav=Latvian
lang.lit=Lithuanian
lang.ltz=Luxembourgish
lang.mal=Malayalam
lang.mar=Marathi
lang.mkd=Macedonian
lang.mlt=Maltese
lang.mon=Mongolian
lang.mri=Maori
lang.msa=Malay
lang.mya=Burmese
lang.nep=Nepali
lang.nld=Dutch; Flemish
lang.nor=Norwegian
lang.oci=Occitan (post 1500)
lang.ori=Oriya
lang.osd=Orientation and script detection module
lang.pan=Panjabi, Punjabi
lang.pol=Polish
lang.por=Portuguese
lang.pus=Pushto, Pashto
lang.que=Quechua
lang.ron=Romanian, Moldavian, Moldovan
lang.rus=Russian
lang.san=Sanskrit
lang.sin=Sinhala, Sinhalese
lang.slk=Slovak
lang.slk_frak=Slovak (Fraktur)
lang.slv=Slovenian
lang.snd=Sindhi
lang.spa=Spanish
lang.spa_old=Spanish (Old)
lang.sqi=Albanian
lang.srp=Serbian
lang.srp_latn=Serbian (Latin)
lang.sun=Sundanese
lang.swa=Swahili
lang.swe=Swedish
lang.syr=Syriac
lang.tam=Tamil
lang.tat=Tatar
lang.tel=Telugu
lang.tgk=Tajik
lang.tgl=Tagalog
lang.tha=Thai
lang.tir=Tigrinya
lang.ton=Tonga (Tonga Islands)
lang.tur=Turkish
lang.uig=Uighur, Uyghur
lang.ukr=Ukrainian
lang.urd=Urdu
lang.uzb=Uzbek
lang.uzb_cyrl=Uzbek (Cyrillic)
lang.vie=Vietnamese
lang.yid=Yiddish
lang.afr=Afrikaanca
lang.amh=Amharca
lang.ara=Arapça
lang.asm=Assamca
lang.aze=Azerice
lang.aze_cyrl=Azerice (Kiril)
lang.bel=Beyaz Rusça (Belarusça)
lang.ben=Bengalce
lang.bod=Tibetçe
lang.bos=Boşnakça
lang.bre=Bretonca
lang.bul=Bulgarca
lang.cat=Katalanca
lang.ceb=Cebuano
lang.ces=Çekçe
lang.chi_sim=Çince (Basitleştirilmiş)
lang.chi_sim_vert=Çince (Basitleştirilmiş, Dikey)
lang.chi_tra=Çince (Geleneksel)
lang.chi_tra_vert=Çince (Geleneksel, Dikey)
lang.chr=Çerokice
lang.cos=Korsikaca
lang.cym=Gallerce (Galce)
lang.dan=Danca
lang.dan_frak=Danca (Fraktur)
lang.deu=Almanca
lang.deu_frak=Almanca (Fraktur)
lang.div=Maldivce (Divehi)
lang.dzo=Dzongkha
lang.ell=Yunanca
lang.eng=İngilizce
lang.enm=İngilizce, Orta Çağ (1100-1500)
lang.epo=Esperanto
lang.equ=Matematik / denklem tanıma modülü
lang.est=Estonca
lang.eus=Baskça
lang.fao=Faroece
lang.fas=Farsça
lang.fil=Filipince
lang.fin=Fince
lang.fra=Fransızca
lang.frk=Frankça
lang.frm=Fransızca, Orta Çağ (yaklaşık 1400-1600)
lang.fry=Batı Frizce
lang.gla=İskoç Galcesi
lang.gle=İrlandaca
lang.glg=Galiçyaca
lang.grc=Antik Yunanca
lang.guj=Gujaratça
lang.hat=Haiti Creole
lang.heb=İbranice
lang.hin=Hintçe
lang.hrv=Hırvatça
lang.hun=Macarca
lang.hye=Ermenice
lang.iku=İnuktitut
lang.ind=Endonezce
lang.isl=İzlandaca
lang.ita=İtalyanca
lang.ita_old=İtalyanca (Eski)
lang.jav=Cava dili
lang.jpn=Japonca
lang.jpn_vert=Japonca (Dikey)
lang.kan=Kannada
lang.kat=Gürcüce
lang.kat_old=Gürcüce (Eski)
lang.kaz=Kazakça
lang.khm=Merkez Khmer dili
lang.kir=Kırgızca
lang.kmr=Kuzey Kürtçesi
lang.kor=Korece
lang.kor_vert=Korece (Dikey)
lang.lao=Laosça
lang.lat=Latince
lang.lav=Letonca
lang.lit=Litvanca
lang.ltz=Lüksemburgca
lang.mal=Malayalamca
lang.mar=Marathi
lang.mkd=Makedonca
lang.mlt=Maltaca
lang.mon=Moğolca
lang.mri=Maorice
lang.msa=Malayca
lang.mya=Birmanca (Burma)
lang.nep=Nepalce
lang.nld=Hollandaca; Flamanca
lang.nor=Norveççe
lang.oci=Oksitanca (1500 sonrası)
lang.ori=Oriya
lang.osd=Yönlendirme ve yazı tipi algılama modülü
lang.pan=Pencapça
lang.pol=Lehçe (Polonyaca)
lang.por=Portekizce
lang.pus=Peştuca
lang.que=Keçuva dili
lang.ron=Rumence, Moldovca
lang.rus=Rusça
lang.san=Sanskritçe
lang.sin=Seylanca (Sinhala)
lang.slk=Slovakça
lang.slk_frak=Slovakça (Fraktur)
lang.slv=Slovence
lang.snd=Sindhice
lang.spa=İspanyolca
lang.spa_old=İspanyolca (Eski)
lang.sqi=Arnavutça
lang.srp=Sırpça
lang.srp_latn=Sırpça (Latin alfabesiyle)
lang.sun=Sundaca
lang.swa=Svahili dili
lang.swe=İsveççe
lang.syr=Süryanice
lang.tam=Tamilce
lang.tat=Tatarca
lang.tel=Telugu
lang.tgk=Tacikçe
lang.tgl=Tagalog
lang.tha=Tayca
lang.tir=Tigrinya
lang.ton=Tonga dili (Tonga Adaları)
lang.tur=Türkçe
lang.uig=Uygurca
lang.ukr=Ukraynaca
lang.urd=Urduca
lang.uzb=Özbekçe
lang.uzb_cyrl=Özbekçe (Kiril)
lang.vie=Vietnamca
lang.yid=Yid
lang.yor=Yoruba
addPageNumbers.fontSize=Font Büyüklüğü
@ -146,8 +146,8 @@ uploadLimit=Maksimum dosya boyutu:
uploadLimitExceededSingular=çok büyük. İzin verilen maksimum boyut:
uploadLimitExceededPlural=çok büyük. İzin verilen maksimum boyut:
processTimeWarning=Uyarı: Bu işlem, dosya boyutuna bağlı olarak bir dakikaya kadar sürebilir.
pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin) :
pageSelectionPrompt=Özel Sayfa Seçimi (1,5,6 sayfa numaralarının virgülle ayrılmış bir listesini veya 2n+1 gibi bir fonksiyon girin) :
pageOrderPrompt=Özel Sayfa Sırası (Virgülle ayrılmış sayfa numaraları veya 2n+1 gibi bir fonksiyon girin):
pageSelectionPrompt=Özel Sayfa Seçimi (1,5,6 sayfa numaralarının virgülle ayrılmış bir listesini veya 2n+1 gibi bir fonksiyon girin):
goToPage=Sayfaya Git
true=Doğru
false=Yanlış
@ -170,67 +170,67 @@ sizes.medium=Orta
sizes.large=Büyük
sizes.x-large=Çok Büyük
error.pdfPassword=PDF belgesi şifreli ve şifre ya sağlanmadı ya da yanlış.
error.pdfCorrupted=PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.
error.pdfCorruptedMultiple=One or more PDF files appear to be corrupted or damaged. Please try using the 'Repair PDF' feature on each file first before attempting to merge them.
error.pdfCorruptedDuring=Error {0}: PDF file appears to be corrupted or damaged. Please try using the 'Repair PDF' feature first to fix the file before proceeding with this operation.
error.pdfCorrupted=PDF dosyası bozuk veya hasarlı görünüyor. Lütfen bu işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin.
error.pdfCorruptedMultiple=Bir veya daha fazla PDF dosyası bozuk veya hasarlı görünüyor. Lütfen birleştirmeye çalışmadan önce her dosya için 'PDF Onar' özelliğini kullanmayı deneyin.
error.pdfCorruptedDuring=Hata {0}: PDF dosyası bozuk veya hasarlı görünüyor. Lütfen bu işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin.
# Frontend corruption error messages
error.pdfInvalid=The PDF file "{0}" appears to be corrupted or has an invalid structure. Please try using the 'Repair PDF' feature to fix the file before proceeding.
error.tryRepair=Try using the Repair PDF feature to fix corrupted files.
error.pdfInvalid="{0}" adlı PDF dosyası bozuk görünüyor veya geçersiz bir yapıya sahip. Lütfen işlemi gerçekleştirmeden önce dosyayı düzeltmek için 'PDF Onar' özelliğini kullanmayı deneyin.
error.tryRepair=Bozuk dosyaları düzeltmek için PDF Onar özelliğini kullanmayı deneyin.
# Additional error messages
error.pdfEncryption=The PDF appears to have corrupted encryption data. This can happen when the PDF was created with incompatible encryption methods. Please try using the 'Repair PDF' feature first, or contact the document creator for a new copy.
error.fileProcessing=An error occurred while processing the file during {0} operation: {1}
error.pdfEncryption=PDF dosyasının şifreleme verileri bozulmuş görünüyor. Bu, PDF uyumsuz şifreleme yöntemleriyle oluşturulduğunda meydana gelebilir. Lütfen önce 'PDF Onar' özelliğini kullanmayı deneyin veya belgenin oluşturucusuyla iletişime geçerek yeni bir kopya isteyin.
error.fileProcessing={0} işlemi sırasında dosya işlenirken bir hata oluştu: {1}
# Generic error message templates
error.toolNotInstalled={0} is not installed
error.toolRequired={0} is required for {1}
error.conversionFailed={0} conversion failed
error.commandFailed={0} command failed
error.algorithmNotAvailable={0} algorithm not available
error.optionsNotSpecified={0} options are not specified
error.fileFormatRequired=File must be in {0} format
error.invalidFormat=Invalid {0} format: {1}
error.endpointDisabled=This endpoint has been disabled by the admin
error.urlNotReachable=URL is not reachable, please provide a valid URL
error.toolNotInstalled={0} yüklü değil
error.toolRequired={1} işlemi için {0} gereklidir
error.conversionFailed={0} dönüştürme işlemi başarısız oldu
error.commandFailed={0} komutu başarısız oldu
error.algorithmNotAvailable={0} algoritması kullanılamıyor
error.optionsNotSpecified={0} seçenekleri belirtilmemiş
error.fileFormatRequired=Dosya {0} formatında olmalıdır
error.invalidFormat=Geçersiz {0} formatı: {1}
error.endpointDisabled=Bu uç nokta yönetici tarafından devre dışı bırakılmıştır
error.urlNotReachable=URL erişilebilir değil, lütfen geçerli bir URL sağlayın
# DPI and image rendering messages - used by frontend for dynamic translation
# Backend sends: [TRANSLATE:messageKey:arg1,arg2] English message
# Frontend parses this and replaces with localized versions using these keys
error.dpiExceedsLimit=DPI value {0} exceeds maximum safe limit of {1}. High DPI values can cause memory issues and crashes. Please use a lower DPI value.
error.pageTooBigForDpi=PDF page {0} is too large to render at {1} DPI. Please try a lower DPI value (recommended: 150 or less).
error.pageTooBigExceedsArray=PDF page {0} is too large to render at {1} DPI. The resulting image would exceed Java's maximum array size. Please try a lower DPI value (recommended: 150 or less).
error.pageTooBigFor300Dpi=PDF page {0} is too large to render at 300 DPI. The resulting image would exceed Java's maximum array size. Please use a lower DPI value for PDF-to-image conversion.
error.dpiExceedsLimit=DPI değeri {0}, maksimum güvenli sınır olan {1} değerini aşıyor. Yüksek DPI değerleri bellek sorunlarına ve çökme hatalarına neden olabilir. Lütfen daha düşük bir DPI değeri kullanın.
error.pageTooBigForDpi=PDF sayfası {0}, {1} DPI değerinde işlenemeyecek kadar büyük. Lütfen daha düşük bir DPI değeri deneyin (önerilen: 150 veya daha az).
error.pageTooBigExceedsArray=PDF sayfası {0}, {1} DPI değerinde işlenemeyecek kadar büyük. Ortaya çıkan görüntü Java'nın maksimum dizi boyutunu aşacaktır. Lütfen daha düşük bir DPI değeri deneyin (önerilen: 150 veya daha az).
error.pageTooBigFor300Dpi=PDF sayfası {0}, 300 DPI değerinde işlenemeyecek kadar büyük. Ortaya çıkan görüntü Java'nın maksimum dizi boyutunu aşacaktır. Lütfen PDF'den görüntüye dönüştürme işlemi için daha düşük bir DPI değeri kullanın.
# URL and website conversion messages
# System requirements messages
# Authentication and security messages
error.apiKeyInvalid=API key is not valid.
error.userNotFound=User not found.
error.passwordRequired=Password must not be null.
error.accountLocked=Your account has been locked due to too many failed login attempts.
error.invalidEmail=Invalid email addresses provided.
error.emailAttachmentRequired=An attachment is required to send the email.
error.signatureNotFound=Signature file not found.
error.apiKeyInvalid=API anahtarı geçerli değil.
error.userNotFound=Kullanıcı bulunamadı.
error.passwordRequired=Parola boş bırakılamaz.
error.accountLocked=Çok fazla başarısız giriş denemesi nedeniyle hesabınız kilitlendi.
error.invalidEmail=Geçersiz e-posta adresleri sağlandı.
error.emailAttachmentRequired=E-posta gönderebilmek için bir ek dosya gereklidir.
error.signatureNotFound=İmza dosyası bulunamadı.
# File processing messages
error.fileNotFound=File not found with ID: {0}
error.fileNotFound=Dosya bulunamadı. Dosya kimliği: {0}
# Database and configuration messages
error.noBackupScripts=No backup scripts were found.
error.unsupportedProvider={0} is not currently supported.
error.pathTraversalDetected=Path traversal detected for security reasons.
error.noBackupScripts=Yedekleme betikleri bulunamadı.
error.unsupportedProvider={0} şu anda desteklenmiyor.
error.pathTraversalDetected=Güvenlik nedeniyle yol geçişi (path traversal) tespit edildi.
# Validation messages
error.invalidArgument=Invalid argument: {0}
error.argumentRequired={0} must not be null
error.operationFailed=Operation failed: {0}
error.angleNotMultipleOf90=Angle must be a multiple of 90
error.pdfBookmarksNotFound=No PDF bookmarks/outline found in document
error.fontLoadingFailed=Error processing font file
error.fontDirectoryReadFailed=Failed to read font directory
error.invalidArgument=Geçersiz argüman: {0}
error.argumentRequired={0} boş olamaz
error.operationFailed=İşlem başarısız oldu: {0}
error.angleNotMultipleOf90=Açı 90'ın katı olmalıdır
error.pdfBookmarksNotFound=Belgede herhangi bir PDF yer imi / içindekiler bulunamadı
error.fontLoadingFailed=Yazı tipi dosyası işlenirken hata oluştu
error.fontDirectoryReadFailed=Yazı tipi dizini okunamadı
delete=Sil
username=Kullanıcı Adı
password=Parola
@ -260,7 +260,7 @@ disabledCurrentUserMessage=Mevcut kullanıcı devre dışı bırakılamaz
downgradeCurrentUserLongMessage=Mevcut kullanıcının rolü düşürülemiyor. Bu nedenle, mevcut kullanıcı gösterilmeyecektir.
userAlreadyExistsOAuthMessage=Kullanıcı zaten bir OAuth2 kullanıcısı olarak mevcut.
userAlreadyExistsWebMessage=Kullanıcı zaten bir web kullanıcısı olarak mevcut.
invalidRoleMessage=Invalid role.
invalidRoleMessage=Geçersiz rol.
error=Hata
oops=Tüh!
help=Yardım
@ -273,7 +273,7 @@ color=Renk
sponsor=Bağış
info=Bilgi
pro=Pro
proFeatures=Pro Features
proFeatures=Pro Özellikler
page=Sayfa
pages=Sayfalar
loading=Yükleniyor...
@ -281,11 +281,11 @@ addToDoc=Dökümana Ekle
reset=Sıfırla
apply=Uygula
noFileSelected=Hiçbir dosya seçilmedi. Lütfen bir dosya yükleyin.
view=View
view=Görüntüle
cancel=İptal
back.toSettings=Ayarlara Geri Dön
back.toHome=Ana Sayfaya Geri Dön
back.toSettings=Ayarlar'a Geri Dön
back.toHome=Ana Sayfa'ya Geri Dön
back.toAdmin=Yönetim Paneline Geri Dön
legal.privacy=Gizlilik Politikası
@ -327,15 +327,15 @@ enterpriseEdition.button=Pro Sürümüne Yükselt
enterpriseEdition.warning=Bu özellik yalnızca Pro kullanıcılarına sunulmaktadır.
enterpriseEdition.yamlAdvert=Stirling PDF Pro, YAML yapılandırma dosyalarını ve diğer SSO özelliklerini destekler.
enterpriseEdition.ssoAdvert=Daha fazla kullanıcı yönetimi özelliği mi arıyorsunuz? Stirling PDF Pro'ya göz atın
enterpriseEdition.proTeamFeatureDisabled=Team management features require a Pro licence or higher
enterpriseEdition.proTeamFeatureDisabled=Takım yönetimi özellikleri Pro lisansı veya daha üstü gerektirir
#################
# Analytics #
#################
analytics.title=Stirling PDFi daha iyi hale getirmek ister misiniz?
analytics.title=Stirling PDF'i daha iyi hale getirmek ister misiniz?
analytics.paragraph1=Stirling PDF, ürünü geliştirmemize yardımcı olmak için isteğe bağlı analizleri içerir. Kişisel bilgileri veya dosya içeriklerini asla takip etmiyoruz.
analytics.paragraph2=Stirling PDFin büyümesine destek olmak ve kullanıcılarımızı daha iyi anlayabilmemiz için analizleri etkinleştirmeyi düşünebilirsiniz.
analytics.paragraph2=Stirling PDF'in büyümesine destek olmak ve kullanıcılarımızı daha iyi anlayabilmemiz için analizleri etkinleştirmeyi düşünebilirsiniz.
analytics.enable=Analizi Etkinleştir
analytics.disable=Analizi Devre Dışı Bırak
analytics.settings=Analiz ayarlarını config/settings.yml dosyasından değiştirebilirsiniz
@ -345,7 +345,7 @@ analytics.settings=Analiz ayarlarını config/settings.yml dosyasından değişt
# NAVBAR #
#############
navbar.favorite=Favoriler
navbar.recent=New and recently updated
navbar.recent=Yeni ve son güncellenenler
navbar.darkmode=Karanlık Mod
navbar.language=Diller
navbar.settings=Ayarlar
@ -368,36 +368,36 @@ settings.update=Güncelleme mevcut
settings.updateAvailable={0} mevcut kurulu sürümdür. Yeni bir sürüm ({1}) mevcuttur.
# Update modal and notification strings
update.urgentUpdateAvailable=🚨 Update Available
update.updateAvailable=Update Available
update.modalTitle=Update Available
update.current=Current
update.latest=Latest
update.latestStable=Latest Stable
update.priority=Priority
update.recommendedAction=Recommended Action
update.breakingChangesDetected=⚠️ Breaking Changes Detected
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
update.migrationGuides=Migration Guides:
update.viewGuide=View Guide
update.loadingDetailedInfo=Loading detailed version information...
update.close=Close
update.viewAllReleases=View All Releases
update.downloadLatest=Download Latest
update.availableUpdates=Available Updates:
update.unableToLoadDetails=Unable to load detailed version information.
update.version=Version
update.urgentUpdateAvailable=🚨 Güncelleme Mevcut
update.updateAvailable=Güncelleme Mevcut
update.modalTitle=Güncelleme Mevcut
update.current=Mevcut
update.latest=En Yeni
update.latestStable=En Yeni Kararlı
update.priority=Öncelik
update.recommendedAction=Önerilen İşlem
update.breakingChangesDetected=⚠️ Kırıcı Değişiklikler Tespit Edildi
update.breakingChangesMessage=Bu güncelleme kırıcı değişiklikler içeriyor. Lütfen aşağıdaki geçiş kılavuzlarını inceleyin.
update.migrationGuides=Geçiş Kılavuzları:
update.viewGuide=Kılavuzu Görüntüle
update.loadingDetailedInfo=Ayrıntılı sürüm bilgileri yükleniyor...
update.close=Kapat
update.viewAllReleases=Tüm Sürümleri Görüntüle
update.downloadLatest=En Yeniyi İndir
update.availableUpdates=Mevcut Güncellemeler:
update.unableToLoadDetails=Ayrıntılı sürüm bilgileri yüklenemedi.
update.version=Sürüm
# Update priority levels
update.priority.urgent=URGENT
update.priority.urgent=ACİL
update.priority.normal=NORMAL
update.priority.minor=MINOR
update.priority.low=LOW
update.priority.minor=ÖNEMSİZ
update.priority.low=DÜŞÜK
# Breaking changes text
update.breakingChanges=Breaking Changes:
update.breakingChangesDefault=This version contains breaking changes
update.migrationGuide=Migration Guide
update.breakingChanges=Kırıcı Değişiklikler:
update.breakingChangesDefault=Bu sürüm kırıcı değişiklikler içeriyor
update.migrationGuide=Geçiş Kılavuzu
settings.appVersion=Uygulama Sürümü:
settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için):
settings.downloadOption.1=Aynı pencerede aç
@ -472,18 +472,18 @@ adminUserSettings.disabledUsers=Devre Dışı Kullanıcılar:
adminUserSettings.totalUsers=Toplam Kullanıcılar:
adminUserSettings.lastRequest=Son İstek
adminUserSettings.usage=Kullanımı Görüntüle
adminUserSettings.teams=View/Edit Teams
adminUserSettings.team=Team
adminUserSettings.manageTeams=Manage Teams
adminUserSettings.createTeam=Create Team
adminUserSettings.viewTeam=View Team
adminUserSettings.deleteTeam=Delete Team
adminUserSettings.teamName=Team Name
adminUserSettings.teamExists=Team already exists
adminUserSettings.teamCreated=Team created successfully
adminUserSettings.teamChanged=User's team was updated
adminUserSettings.teamHidden=Hidden
adminUserSettings.totalMembers=Total Members
adminUserSettings.teams=Takımları Görüntüle/Düzenle
adminUserSettings.team=Takım
adminUserSettings.manageTeams=Takımları Yönet
adminUserSettings.createTeam=Takım Oluştur
adminUserSettings.viewTeam=Takımı Görüntüle
adminUserSettings.deleteTeam=Takımı Sil
adminUserSettings.teamName=Takım Adı
adminUserSettings.teamExists=Takım zaten mevcut
adminUserSettings.teamCreated=Takım başarıyla oluşturuldu
adminUserSettings.teamChanged=Kullanıcının takımı güncellendi
adminUserSettings.teamHidden=Gizli
adminUserSettings.totalMembers=Toplam Üye
adminUserSettings.confirmDeleteTeam=Bu takımı silmek istediğinizden emin misiniz?
teamCreated=Takım başarıyla oluşturuldu
@ -538,7 +538,7 @@ endpointStatistics.home=Ana Sayfa
endpointStatistics.login=Giriş
endpointStatistics.top=En Çok
endpointStatistics.numberOfVisits=Ziyaret Sayısı
endpointStatistics.visitsTooltip=Ziyaret: {0} (toplamın %{1}i)
endpointStatistics.visitsTooltip=Ziyaret: {0} (toplamın %{1}'i)
endpointStatistics.retry=Yeniden Dene
database.title=Veri Tabanını İçe/Dışa Aktar
@ -617,9 +617,9 @@ home.addImage.title=Resim Ekle
home.addImage.desc=PDF'e belirli bir konuma resim ekler
addImage.tags=img,jpg,fotoğraf,resim
home.attachments.title=Add Attachments
home.attachments.desc=Add or remove embedded files (attachments) to/from a PDF
attachments.tags=embed,attach,file,attachment,attachments
home.attachments.title=Ekleri Ekle
home.attachments.desc=PDF'ye gömülü dosyalar (ekler) ekle veya kaldır
attachments.tags=gömme,ekle,dosya,ek,ekler
home.watermark.title=Filigran Ekle
home.watermark.desc=PDF belgenize özel bir filigran ekleyin.
@ -772,21 +772,21 @@ home.HTMLToPDF.desc=Herhangi bir HTML dosyasını veya zip'i PDF'e dönüştür
HTMLToPDF.tags=biçimlendirme,web-içeriği,dönüşüm,dönüştür
#eml-to-pdf
home.EMLToPDF.title=Email to PDF
home.EMLToPDF.desc=Converts email (EML) files to PDF format including headers, body, and inline images
EMLToPDF.tags=email,conversion,eml,message,transformation,convert,mail
home.EMLToPDF.title=E-postayı PDF'ye Dönüştür
home.EMLToPDF.desc=Başlıklar, gövde ve satır içi resimler dahil olmak üzere e-posta (EML) dosyalarını PDF formatına dönüştürür
EMLToPDF.tags=e-posta, dönüşüm, eml, mesaj, dönüşüm, dönüştür, posta
EMLToPDF.title=Email To PDF
EMLToPDF.header=Email To PDF
EMLToPDF.submit=Convert
EMLToPDF.downloadHtml=Download HTML intermediate file instead of PDF
EMLToPDF.downloadHtmlHelp=This allows you to see the HTML version before PDF conversion and can help debug formatting issues
EMLToPDF.includeAttachments=Include attachments in PDF
EMLToPDF.maxAttachmentSize=Maximum attachment size (MB)
EMLToPDF.help=Converts email (EML) files to PDF format including headers, body, and inline images
EMLToPDF.troubleshootingTip1=Email to HTML is a more reliable process, so with batch-processing it is recommended to save both
EMLToPDF.troubleshootingTip2=With a small number of Emails, if the PDF is malformed, you can download HTML and override some of the problematic HTML/CSS code.
EMLToPDF.troubleshootingTip3=Embeddings, however, do not work with HTMLs
EMLToPDF.title=E-postayı PDF'ye Dönüştür
EMLToPDF.header=E-postayı PDF'ye Dönüştür
EMLToPDF.submit=Dönüştür
EMLToPDF.downloadHtml=PDF yerine HTML ara dosyasını indir
EMLToPDF.downloadHtmlHelp=Bu, PDF dönüşümünden önce HTML sürümünü görmenizi sağlar ve biçimlendirme sorunlarını çözmeye yardımcı olabilir
EMLToPDF.includeAttachments=PDF'ye ekleri dahil et
EMLToPDF.maxAttachmentSize=Maksimum ek boyutu (MB)
EMLToPDF.help=Başlıklar, gövde ve satır içi resimler dahil olmak üzere e-posta (EML) dosyalarını PDF formatına dönüştürür
EMLToPDF.troubleshootingTip1=E-postayı HTML'ye dönüştürmek daha güvenilir bir işlemdir, bu nedenle toplu işleme yaparken her ikisini de kaydetmek önerilir
EMLToPDF.troubleshootingTip2=Az sayıda e-posta için, PDF bozuksa HTML dosyasını indirip bazı sorunlu HTML/CSS kodlarını değiştirebilirsiniz
EMLToPDF.troubleshootingTip3=Ancak, gömülü içerikler HTML ile çalışmaz
home.MarkdownToPDF.title=Markdown'dan PDF'e
home.MarkdownToPDF.desc=Herhangi bir Markdown dosyasını PDF'e dönüştürür
@ -907,8 +907,8 @@ login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullan
login.alreadyLoggedIn=Zaten şu cihazlarda oturum açılmış:
login.alreadyLoggedIn2=Lütfen bu cihazlardan çıkış yaparak tekrar deneyin.
login.toManySessions=Çok fazla aktif oturumunuz var
login.logoutMessage=You have been logged out.
login.invalidInResponseTo=The requested SAML response is invalid or has expired. Please contact the administrator.
login.logoutMessage=Oturumunuz kapatıldı.
login.invalidInResponseTo=İstenen SAML yanıtı geçersiz veya süresi dolmuş. Lütfen yöneticiyle iletişime geçin.
#auto-redact
autoRedact.title=Otomatik Karartma
@ -974,28 +974,28 @@ getPdfInfo.title=PDF Hakkında Bilgi Al
getPdfInfo.header=PDF Hakkında Bilgi Al
getPdfInfo.submit=Bilgi Al
getPdfInfo.downloadJson=JSON İndir
getPdfInfo.summary=PDF Summary
getPdfInfo.summary.encrypted=This PDF is encrypted so may face issues with some applications
getPdfInfo.summary.permissions=This PDF has {0} restricted permissions which may limit what you can do with it
getPdfInfo.summary.compliance=This PDF complies with the {0} standard
getPdfInfo.summary.basicInfo=Basic Information
getPdfInfo.summary.docInfo=Document Information
getPdfInfo.summary.encrypted.alert=Encrypted PDF - This document is password protected
getPdfInfo.summary.not.encrypted.alert=Unencrypted PDF - No password protection
getPdfInfo.summary.permissions.alert=Restricted Permissions - {0} actions are not allowed
getPdfInfo.summary.all.permissions.alert=All Permissions Allowed
getPdfInfo.summary.compliance.alert={0} Compliant
getPdfInfo.summary.no.compliance.alert=No Compliance Standards
getPdfInfo.summary.security.section=Security Status
getPdfInfo.section.BasicInfo=Basic Information about the PDF document including file size, page count, and language
getPdfInfo.section.Metadata=Document metadata including title, author, creation date and other document properties
getPdfInfo.section.DocumentInfo=Technical details about the PDF document structure and version
getPdfInfo.section.Compliancy=PDF standards compliance information (PDF/A, PDF/X, etc.)
getPdfInfo.section.Encryption=Security and encryption details of the document
getPdfInfo.section.Permissions=Document permission settings that control what actions can be performed
getPdfInfo.section.Other=Additional document components like bookmarks, layers, and embedded files
getPdfInfo.section.FormFields=Interactive form fields present in the document
getPdfInfo.section.PerPageInfo=Detailed information about each page in the document
getPdfInfo.summary=PDF Özeti
getPdfInfo.summary.encrypted=Bu PDF şifreli olduğu için bazı uygulamalarda sorun yaşanabilir
getPdfInfo.summary.permissions=Bu PDF'de {0} kısıtlanmış izinler var, bu da yapabileceklerinizi sınırlayabilir
getPdfInfo.summary.compliance=Bu PDF {0} standardına uygundur
getPdfInfo.summary.basicInfo=Temel Bilgiler
getPdfInfo.summary.docInfo=Belge Bilgileri
getPdfInfo.summary.encrypted.alert=Şifreli PDF - Bu belge parola korumalıdır
getPdfInfo.summary.not.encrypted.alert=Şifresiz PDF - Parola koruması yok
getPdfInfo.summary.permissions.alert=Kısıtlanmış İzinler - {0} işlem izin verilmemiştir
getPdfInfo.summary.all.permissions.alert=Tüm İzinler Verildi
getPdfInfo.summary.compliance.alert={0} Uygun
getPdfInfo.summary.no.compliance.alert=Uygunluk Standardı Yok
getPdfInfo.summary.security.section=Güvenlik Durumu
getPdfInfo.section.BasicInfo=PDF belgesinin dosya boyutu, sayfa sayısı ve dili dahil temel bilgileri
getPdfInfo.section.Metadata=Başlık, yazar, oluşturulma tarihi ve diğer belge özelliklerini içeren belge meta verisi
getPdfInfo.section.DocumentInfo=PDF belge yapısı ve sürümü hakkında teknik detaylar
getPdfInfo.section.Compliancy=PDF standartlarına uygunluk bilgisi (PDF/A, PDF/X, vb.)
getPdfInfo.section.Encryption=Belgenin güvenlik ve şifreleme detayları
getPdfInfo.section.Permissions=Hangi işlemlerin yapılabileceğini kontrol eden belge izin ayarları
getPdfInfo.section.Other=Yer imleri, katmanlar ve gömülü dosyalar gibi ek belge bileşenleri
getPdfInfo.section.FormFields=Belgede bulunan etkileşimli form alanları
getPdfInfo.section.PerPageInfo=Belgedeki her sayfa hakkında ayrıntılı bilgiler
#markdown-to-pdf
@ -1007,9 +1007,9 @@ MarkdownToPDF.credit=WeasyPrint Kullanıyor
#pdf-to-markdown
PDFToMarkdown.title=PDF To Markdown
PDFToMarkdown.header=PDF To Markdown
PDFToMarkdown.submit=Convert
PDFToMarkdown.title=PDF'den Markdown'a
PDFToMarkdown.header=PDF'den Markdown'a
PDFToMarkdown.submit=Dönüştür
#url-to-pdf
@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Numaralandırılacak Sayfalar
addPageNumbers.selectText.6=Özel Metin
addPageNumbers.customTextDesc=Özel Metin
addPageNumbers.numberPagesDesc=Hangi sayfaların numaralandırılacağını, varsayılan 'all', ayrıca 1-5 veya 2,5,9 vb. kabul eder
addPageNumbers.customNumberDesc=Varsayılan {n}, ayrıca 'Sayfa {n} / {total}', 'Metin-{n}', '{filename}-{n} kabul eder
addPageNumbers.customNumberDesc=Varsayılan {n}, ayrıca 'Sayfa {n} / {total}', 'Metin-{n}', '{filename}-{n}' kabul eder
addPageNumbers.submit=Sayfa Numaraları Ekle
@ -1136,7 +1136,7 @@ pageLayout.submit=Gönder
scalePages.title=Sayfa Ölçeğini Ayarla
scalePages.header=Sayfa Ölçeğini Ayarla
scalePages.pageSize=Belgenin bir sayfa boyutu.
scalePages.keepPageSize=Original Size
scalePages.keepPageSize=Orijinal Boyut
scalePages.scaleFactor=Bir sayfanın yakınlaştırma seviyesi (kırpma).
scalePages.submit=Gönder
@ -1156,7 +1156,7 @@ certSign.showSig=İmzayı Göster
certSign.reason=Neden
certSign.location=Konum
certSign.name=İsim
certSign.showLogo=Show Logo
certSign.showLogo=Logoyu Göster
certSign.submit=PDF'i İmzala
@ -1171,7 +1171,7 @@ removeCertSign.submit=İmzayı Kaldır
removeBlanks.title=Boşları Kaldır
removeBlanks.header=Boş Sayfaları Kaldır
removeBlanks.threshold=Pixel Beyazlık Eşiği:
removeBlanks.thresholdDesc=Bir beyaz pixelin 'Beyaz' olarak sınıflandırılması için ne kadar beyaz olması gerektiğini belirlemek için eşik. 0 = Siyah, 255 saf beyaz.
removeBlanks.thresholdDesc=Bir beyaz pixelin 'Beyaz' olarak sınıflandırılması için ne kadar beyaz olması gerektiğini belirlemek için eşik. 0=Siyah, 255 saf beyaz.
removeBlanks.whitePercent=Beyaz Yüzde (%):
removeBlanks.whitePercentDesc=Bir sayfanın 'beyaz' pixel olması gereken yüzdesi
removeBlanks.submit=Boşları Kaldır
@ -1287,8 +1287,8 @@ compress.title=Sıkıştır
compress.header=PDF'i Sıkıştır
compress.credit=Bu hizmet PDF Sıkıştırma/Optimizasyonu için qpdf kullanır.
compress.grayscale.label=Sıkıştırma için Gri Ton Uygula
compress.selectText.1=Compression Settings
compress.selectText.1.1=1-3 PDF compression,</br> 4-6 lite image compression,</br> 7-9 intense image compression Will dramatically reduce image quality
compress.selectText.1=Sıkıştırma Ayarları
compress.selectText.1.1=1-3 PDF sıkıştırma,</br>4-6 hafif görüntü sıkıştırma,</br>7-9 yoğun görüntü sıkıştırma Görüntü kalitesini ciddi şekilde düşürecektir
compress.selectText.2=Optimizasyon seviyesi:
compress.selectText.4=Otomatik mod - PDF'in tam boyutuna ulaşmak için kaliteyi otomatik ayarlar
compress.selectText.5=Beklenen PDF Boyutu (örn. 25MB, 10.8MB, 25KB)
@ -1303,11 +1303,11 @@ addImage.upload=Resim ekle
addImage.submit=Resim ekle
#attachments
attachments.title=Add Attachments
attachments.header=Add attachments
attachments.description=Allows you to add attachments to the PDF
attachments.descriptionPlaceholder=Enter a description for the attachments...
attachments.addButton=Add Attachments
attachments.title=Ekler Ekle
attachments.header=Ekler Ekle
attachments.description=PDF'ye ekler eklemenizi sağlar
attachments.descriptionPlaceholder=Ekler için bir açıklama girin...
attachments.addButton=Ekleri Ekle
#merge
merge.title=Birleştir
@ -1315,7 +1315,7 @@ merge.header=Çoklu PDF'leri Birleştir (2+)
merge.sortByName=İsme göre sırala
merge.sortByDate=Tarihe göre sırala
merge.removeCertSign=Birleştirilen dosyadaki dijital imza kaldırılsın mı?
merge.generateToc=Generate table of contents in the merged file?
merge.generateToc=Birleştirilen dosyada içindekiler tablosu oluşturulsun mu?
merge.submit=Birleştir
@ -1435,7 +1435,7 @@ pdfToImage.colorType=Renk türü
pdfToImage.color=Renk
pdfToImage.grey=Gri tonlama
pdfToImage.blackwhite=Siyah ve Beyaz (Veri kaybolabilir!)
pdfToImage.dpi=DPI (The server limit is {0} dpi)
pdfToImage.dpi=DPI (Sunucu limiti {0} dpi)
pdfToImage.submit=Dönüştür
pdfToImage.info=Python kurulu değil. WebP dönüşümü için gereklidir.
pdfToImage.placeholder=(örneğin 1,2,8 veya 4,7,12-16 ya da 2n-1)
@ -1652,9 +1652,9 @@ survey.meeting.1=Eğer Stirling PDF'i iş yerinizde kullanıyorsanız, sizinle g
survey.meeting.2=Bu fırsat sayesinde:
survey.meeting.3=Kurulum, entegrasyonlar veya sorun giderme konularında yardım alabilirsiniz
survey.meeting.4=Performans, uç durumlar ve eksik özellikler hakkında doğrudan geri bildirim sağlayabilirsiniz
survey.meeting.5=Stirling PDFi gerçek dünya kurumsal kullanımı için daha iyi hale getirmemize yardımcı olabilirsiniz
survey.meeting.5=Stirling PDF'i gerçek dünya kurumsal kullanımı için daha iyi hale getirmemize yardımcı olabilirsiniz
survey.meeting.6=İlgileniyorsanız, ekibimizden doğrudan zaman ayırabilirsiniz. (Yalnızca İngilizce)
survey.meeting.7=Kullanım senaryolarınızı dinlemeyi ve Stirling PDFi daha da iyi hale getirmeyi sabırsızlıkla bekliyoruz!
survey.meeting.7=Kullanım senaryolarınızı dinlemeyi ve Stirling PDF'i daha da iyi hale getirmeyi sabırsızlıkla bekliyoruz!
survey.meeting.notInterested=Kurumsal kullanıcı değilseniz ve/veya görüşmeye ilgi duymuyorsanız
survey.meeting.button=Görüşme Planla
@ -1740,23 +1740,23 @@ validateSignature.cert.keySize=Anahtar Boyutu
validateSignature.cert.version=Sürüm
validateSignature.cert.keyUsage=Anahtar Kullanımı
validateSignature.cert.selfSigned=Kendi Kendine İmzalı
validateSignature.cert.bits=bits
validateSignature.cert.bits=bit
# Audit Dashboard
audit.dashboard.title=Audit Dashboard
audit.dashboard.systemStatus=Audit System Status
audit.dashboard.title=Denetim Kontrol Paneli
audit.dashboard.systemStatus=Denetim Sistemi Durumu
audit.dashboard.status=Durum
audit.dashboard.enabled=Etkin
audit.dashboard.disabled=Devre Dışı
audit.dashboard.currentLevel=Current Level
audit.dashboard.retentionPeriod=Retention Period
audit.dashboard.days=days
audit.dashboard.totalEvents=Total Events
audit.dashboard.currentLevel=Mevcut Seviye
audit.dashboard.retentionPeriod=Saklama Süresi
audit.dashboard.days=gün
audit.dashboard.totalEvents=Toplam Olay
# Audit Dashboard Tabs
audit.dashboard.tab.dashboard=Dashboard
audit.dashboard.tab.events=Audit Events
audit.dashboard.tab.export=Export
audit.dashboard.tab.dashboard=Gösterge Paneli
audit.dashboard.tab.events=Denetim Olayları
audit.dashboard.tab.export=Dışa Aktar
# Dashboard Charts
audit.dashboard.eventsByType=Türüne Göre Olaylar
audit.dashboard.eventsByUser=Kullanıcıya Göre Olaylar
@ -1766,23 +1766,23 @@ audit.dashboard.period.30days=30 Gün
audit.dashboard.period.90days=90 Gün
# Events Tab
audit.dashboard.auditEvents=Audit Events
audit.dashboard.filter.eventType=Event Type
audit.dashboard.filter.allEventTypes=All event types
audit.dashboard.filter.user=User
audit.dashboard.filter.userPlaceholder=Filter by user
audit.dashboard.filter.startDate=Start Date
audit.dashboard.filter.endDate=End Date
audit.dashboard.filter.apply=Apply Filters
audit.dashboard.filter.reset=Reset Filters
audit.dashboard.auditEvents=Denetim Olayları
audit.dashboard.filter.eventType=Olay Türü
audit.dashboard.filter.allEventTypes=Tüm olay türleri
audit.dashboard.filter.user=Kullanıcı
audit.dashboard.filter.userPlaceholder=Kullanıcıya göre filtrele
audit.dashboard.filter.startDate=Başlangıç Tarihi
audit.dashboard.filter.endDate=Bitiş Tarihi
audit.dashboard.filter.apply=Filtreleri Uygula
audit.dashboard.filter.reset=Filtreleri Sıfırla
# Table Headers
audit.dashboard.table.id=ID
audit.dashboard.table.time=Time
audit.dashboard.table.user=User
audit.dashboard.table.type=Type
audit.dashboard.table.details=Details
audit.dashboard.table.viewDetails=View Details
audit.dashboard.table.time=Zaman
audit.dashboard.table.user=Kullanıcı
audit.dashboard.table.type=Tür
audit.dashboard.table.details=Detaylar
audit.dashboard.table.viewDetails=Detayları Görüntüle
# Pagination
audit.dashboard.pagination.show=Göster
@ -1792,10 +1792,10 @@ audit.dashboard.pagination.pageInfo2=/
audit.dashboard.pagination.totalRecords=Toplam kayıt:
# Modal
audit.dashboard.modal.eventDetails=Event Details
audit.dashboard.modal.eventDetails=Olay Detayları
audit.dashboard.modal.id=ID
audit.dashboard.modal.user=Kullanıcı
audit.dashboard.modal.type=Type
audit.dashboard.modal.type=Tip
audit.dashboard.modal.time=Zaman
audit.dashboard.modal.data=Veri
@ -1824,8 +1824,8 @@ audit.dashboard.js.loadingPage=Sayfa yükleniyor
# Cookie banner #
####################
cookieBanner.popUp.title=Çerezleri Nasıl Kullanıyoruz
cookieBanner.popUp.description.1=Stirling PDFyi sizin için daha iyi çalıştırmak için çerezler ve diğer teknolojileri kullanıyoruz — araçlarımızı geliştirmemize ve seveceğiniz özellikler oluşturmamıza yardımcı oluyorlar.
cookieBanner.popUp.description.2=İstemiyorsanız, Hayır Teşekkürler butonuna tıklayarak yalnızca temel, gerekli çerezleri etkinleştirebilirsiniz.
cookieBanner.popUp.description.1=Stirling PDF'yi sizin için daha iyi çalıştırmak için çerezler ve diğer teknolojileri kullanıyoruz — araçlarımızı geliştirmemize ve seveceğiniz özellikler oluşturmamıza yardımcı oluyorlar.
cookieBanner.popUp.description.2=İstemiyorsanız, 'Hayır Teşekkürler' butonuna tıklayarak yalnızca temel, gerekli çerezleri etkinleştirebilirsiniz.
cookieBanner.popUp.acceptAllBtn=Tamam
cookieBanner.popUp.acceptNecessaryBtn=Hayır Teşekkürler
cookieBanner.popUp.showPreferencesBtn=Tercihleri Yönet
@ -1846,20 +1846,20 @@ cookieBanner.preferencesModal.analytics.title=Analitik
cookieBanner.preferencesModal.analytics.description=Bu çerezler, araçlarımızın nasıl kullanıldığını anlamamıza yardımcı olur, böylece topluluğumuzun en çok değer verdiği özellikleri geliştirmeye odaklanabiliriz. İçiniz rahat olsun — Stirling PDF, belgelerinizin içeriğini asla takip etmez ve etmeyecektir.
#scannerEffect
scannerEffect.title=Scanner Effect
scannerEffect.header=Scanner Effect
scannerEffect.description=Create a PDF that looks like it was scanned
scannerEffect.selectPDF=Select PDF:
scannerEffect.quality=Scan Quality
scannerEffect.quality.low=Low
scannerEffect.quality.medium=Medium
scannerEffect.quality.high=High
scannerEffect.rotation=Rotation Angle
scannerEffect.rotation.none=None
scannerEffect.rotation.slight=Slight
scannerEffect.rotation.moderate=Moderate
scannerEffect.rotation.severe=Severe
scannerEffect.submit=Create Scanner Effect
scannerEffect.title=Tarayıcı Efekti
scannerEffect.header=Tarayıcı Efekti
scannerEffect.description=Taranmış gibi görünen bir PDF oluştur
scannerEffect.selectPDF=PDF Seç:
scannerEffect.quality=Tarama Kalitesi
scannerEffect.quality.low=Düşük
scannerEffect.quality.medium=Orta
scannerEffect.quality.high=Yüksek
scannerEffect.rotation=Döndürme Açısı
scannerEffect.rotation.none=Yok
scannerEffect.rotation.slight=Hafif
scannerEffect.rotation.moderate=Orta
scannerEffect.rotation.severe=Şiddetli
scannerEffect.submit=Tarayıcı Efekti Oluştur
#home.scannerEffect
home.scannerEffect.title=Sahte Tarama
@ -1893,12 +1893,12 @@ editTableOfContents.replaceExisting=Mevcut yer işaretlerini değiştir (var ola
editTableOfContents.editorTitle=Yer İşareti Düzenleyici
editTableOfContents.editorDesc=Aşağıdan yer işaretleri ekleyin ve düzenleyin. Alt yer işareti eklemek için + simgesine tıklayın.
editTableOfContents.addBookmark=Yeni Yer İşareti Ekle
editTableOfContents.importBookmarksDefault=Import
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
editTableOfContents.exportBookmarksDefault=Export
editTableOfContents.exportBookmarksAsJson=Download as JSON
editTableOfContents.exportBookmarksAsText=Copy as text
editTableOfContents.importBookmarksDefault=İçe Aktar
editTableOfContents.importBookmarksFromJsonFile=JSON dosyası yükle
editTableOfContents.importBookmarksFromClipboard=Panodan yapıştır
editTableOfContents.exportBookmarksDefault=Dışa Aktar
editTableOfContents.exportBookmarksAsJson=JSON olarak indir
editTableOfContents.exportBookmarksAsText=Metin olarak kopyala
editTableOfContents.desc.1=Bu araç, bir PDF belgesine içindekiler tablosu (yer işaretleri) eklemenizi veya mevcut olanları düzenlemenizi sağlar.
editTableOfContents.desc.2=Alt yer işaretleri ekleyerek hiyerarşik bir yapı oluşturabilirsiniz.
editTableOfContents.desc.3=Her yer işareti bir başlık ve hedef sayfa numarası gerektirir.

View File

@ -1081,7 +1081,7 @@ addPageNumbers.selectText.5=Trang cần đánh số
addPageNumbers.selectText.6=Văn bản tùy chỉnh
addPageNumbers.customTextDesc=Văn bản tùy chỉnh
addPageNumbers.numberPagesDesc=Những trang cần đánh số, mặc định là 'all', cũng chấp nhận 1-5 hoặc 2,5,9 v.v.
addPageNumbers.customNumberDesc=Mặc định là {n}, cũng chấp nhận 'Trang {n} / {total}', 'Văn bản-{n}', '{filename}-{n}
addPageNumbers.customNumberDesc=Mặc định là {n}, cũng chấp nhận 'Trang {n} / {total}', 'Văn bản-{n}', '{filename}-{n}'
addPageNumbers.submit=Thêm số trang

View File

@ -325,7 +325,7 @@ pipelineOptions.validateButton=驗證
########################
enterpriseEdition.button=升級至專業版
enterpriseEdition.warning=此功能僅提供給專業版使用者使用。
enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他單一登入 (SSO) 功能。
enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他 SSO 登入功能。
enterpriseEdition.ssoAdvert=需要更多使用者管理功能嗎?請參考 Stirling PDF 專業版
enterpriseEdition.proTeamFeatureDisabled=團隊管理功能需要專業版或更進階的授權
@ -364,42 +364,42 @@ navbar.sections.popular=熱門功能
# SETTINGS #
#############
settings.title=設定
settings.update=更新可用
settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用
settings.update=可用更新
settings.updateAvailable=目前安裝的版本為 {0},已有新版本({1})可供更新
# Update modal and notification strings
update.urgentUpdateAvailable=🚨 Update Available
update.updateAvailable=Update Available
update.modalTitle=Update Available
update.current=Current
update.latest=Latest
update.latestStable=Latest Stable
update.priority=Priority
update.recommendedAction=Recommended Action
update.breakingChangesDetected=⚠️ Breaking Changes Detected
update.breakingChangesMessage=This update contains breaking changes. Please review the migration guides below.
update.migrationGuides=Migration Guides:
update.viewGuide=View Guide
update.loadingDetailedInfo=Loading detailed version information...
update.close=Close
update.viewAllReleases=View All Releases
update.downloadLatest=Download Latest
update.availableUpdates=Available Updates:
update.unableToLoadDetails=Unable to load detailed version information.
update.version=Version
update.urgentUpdateAvailable=🚨 有緊急更新
update.updateAvailable=有可用更新
update.modalTitle=有可用更新
update.current=目前版本
update.latest=最新版本
update.latestStable=最新穩定版本
update.priority=優先等級
update.recommendedAction=建議操作
update.breakingChangesDetected=⚠️ 偵測到重大變更
update.breakingChangesMessage=此更新包含可能無法向下相容的重大變更,請先參閱下方的遷移指南。
update.migrationGuides=遷移指南:
update.viewGuide=檢視指南
update.loadingDetailedInfo=正在載入版本詳細資訊...
update.close=關閉
update.viewAllReleases=檢視所有版本
update.downloadLatest=下載最新版
update.availableUpdates=可用更新:
update.unableToLoadDetails=無法載入版本詳細資訊。
update.version=版本
# Update priority levels
update.priority.urgent=URGENT
update.priority.normal=NORMAL
update.priority.minor=MINOR
update.priority.low=LOW
update.priority.urgent=緊急更新
update.priority.normal=一般更新
update.priority.minor=次要更新
update.priority.low=低優先度更新
# Breaking changes text
update.breakingChanges=Breaking Changes:
update.breakingChangesDefault=This version contains breaking changes
update.migrationGuide=Migration Guide
update.breakingChanges=重大變更:
update.breakingChangesDefault=此版本包含可能無法向下相容的重大變更
update.migrationGuide=遷移指南
settings.appVersion=應用程式版本:
settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載):
settings.downloadOption.title=選擇下載選項(適用於單檔無壓縮下載):
settings.downloadOption.1=在同一視窗中開啟
settings.downloadOption.2=在新視窗中開啟
settings.downloadOption.3=下載檔案
@ -806,7 +806,7 @@ home.extractPage.desc=從 PDF 中提取選定的頁面
extractPage.tags=提取
home.PdfToSinglePage.title=PDF 轉單一大頁面
home.PdfToSinglePage.title=PDF 轉單一大頁面
home.PdfToSinglePage.desc=將所有 PDF 頁面合併為一個大的單一頁面
PdfToSinglePage.tags=單一頁面
@ -893,7 +893,7 @@ login.rememberme=記住我
login.invalid=使用者名稱或密碼無效。
login.locked=您的帳號已被鎖定。
login.signinTitle=請登入
login.ssoSignIn=透過 SSO 單一登入
login.ssoSignIn=透過 SSO 登入
login.oAuth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
login.oAuth2AdminBlockedUser=目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。
login.oauth2RequestNotFound=找不到驗證請求
@ -1109,14 +1109,14 @@ crop.submit=送出
#autoSplitPDF
autoSplitPDF.title=自動分割 PDF
autoSplitPDF.header=自動分割 PDF
autoSplitPDF.description=列印,插入,掃描,上傳,讓 Stirling PDF 處理其餘的工作。不需要手動工作排序。
autoSplitPDF.selectText.1=從下面列印一些分隔紙張(黑白即可)。
autoSplitPDF.selectText.2=透過在它們之間插入分隔紙張一次掃描所有文件。
autoSplitPDF.selectText.3=上傳單一大的掃描 PDF 檔案,讓 Stirling PDF 處理其餘的工作
autoSplitPDF.selectText.4=自動偵測並移除分隔頁面,確保最終文件整潔
autoSplitPDF.formPrompt=送出包含 Stirling-PDF 頁面分隔器的 PDF
autoSplitPDF.description=列印、插入、掃描、上傳,剩下的就交給 Stirling PDF 自動處理,無需手動排序。
autoSplitPDF.selectText.1=從下方列印分隔頁(黑白列印即可)。
autoSplitPDF.selectText.2=將分隔頁夾在文件之間,一次掃描全部文件。
autoSplitPDF.selectText.3=上傳完整的單一掃描 PDF 檔,剩下的交給 Stirling PDF 自動處理
autoSplitPDF.selectText.4=系統會自動偵測並移除分隔頁,確保輸出的文件整齊乾淨
autoSplitPDF.formPrompt=送出包含 Stirling PDF 分隔頁的 PDF 檔案
autoSplitPDF.duplexMode=雙面模式(正反面掃描)
autoSplitPDF.dividerDownload2=下載 '自動分割器分隔器(帶說明).pdf'
autoSplitPDF.dividerDownload2=下載《自動分割用分隔頁(含使用說明).pdf》
autoSplitPDF.submit=送出
@ -1429,7 +1429,7 @@ pdfToImage.title=PDF 轉圖片
pdfToImage.header=PDF 轉圖片
pdfToImage.selectText=影像格式
pdfToImage.singleOrMultiple=頁面到影像的結果類型
pdfToImage.single=單一大影像結合所有頁面
pdfToImage.single=單一大影像結合所有頁面
pdfToImage.multi=多個影像,每頁一個影像
pdfToImage.colorType=顏色類型
pdfToImage.color=顏色
@ -1893,12 +1893,12 @@ editTableOfContents.replaceExisting=取代現有書籤 (取消勾選以附加到
editTableOfContents.editorTitle=書籤編輯器
editTableOfContents.editorDesc=在下方新增和排列書籤。點選 + 新增子書籤。
editTableOfContents.addBookmark=新增書籤
editTableOfContents.importBookmarksDefault=Import
editTableOfContents.importBookmarksFromJsonFile=Upload JSON file
editTableOfContents.importBookmarksFromClipboard=Paste from clipboard
editTableOfContents.exportBookmarksDefault=Export
editTableOfContents.exportBookmarksAsJson=Download as JSON
editTableOfContents.exportBookmarksAsText=Copy as text
editTableOfContents.importBookmarksDefault=匯入
editTableOfContents.importBookmarksFromJsonFile=上傳 JSON 檔案
editTableOfContents.importBookmarksFromClipboard=從剪貼簿貼上
editTableOfContents.exportBookmarksDefault=匯出
editTableOfContents.exportBookmarksAsJson=下載為 JSON
editTableOfContents.exportBookmarksAsText=複製為文字
editTableOfContents.desc.1=此工具可讓您在 PDF 文件中新增或編輯目錄 (書籤)。
editTableOfContents.desc.2=您可以透過將子書籤新增至父書籤來建立階層式結構。
editTableOfContents.desc.3=每個書籤都需要標題和目標頁碼。

View File

@ -504,7 +504,7 @@
{
"moduleName": "com.zaxxer:HikariCP",
"moduleUrl": "https://github.com/brettwooldridge/HikariCP",
"moduleVersion": "6.3.1",
"moduleVersion": "6.3.2",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
@ -559,56 +559,56 @@
{
"moduleName": "io.jsonwebtoken:jjwt-api",
"moduleUrl": "https://github.com/jwtk/jjwt",
"moduleVersion": "0.12.6",
"moduleLicense": "Apache License, Version 2.0",
"moduleVersion": "0.12.7",
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "io.jsonwebtoken:jjwt-impl",
"moduleUrl": "https://github.com/jwtk/jjwt",
"moduleVersion": "0.12.6",
"moduleLicense": "Apache License, Version 2.0",
"moduleVersion": "0.12.7",
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "io.jsonwebtoken:jjwt-jackson",
"moduleUrl": "https://github.com/jwtk/jjwt",
"moduleVersion": "0.12.6",
"moduleLicense": "Apache License, Version 2.0",
"moduleVersion": "0.12.7",
"moduleLicense": "Apache-2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "io.micrometer:micrometer-commons",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.2",
"moduleVersion": "1.15.3",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.micrometer:micrometer-core",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.2",
"moduleVersion": "1.15.3",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.micrometer:micrometer-jakarta9",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.2",
"moduleVersion": "1.15.3",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.micrometer:micrometer-observation",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.2",
"moduleVersion": "1.15.3",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "io.micrometer:micrometer-registry-prometheus",
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
"moduleVersion": "1.15.2",
"moduleVersion": "1.15.3",
"moduleLicense": "The Apache Software License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
@ -657,21 +657,21 @@
{
"moduleName": "io.swagger.core.v3:swagger-annotations-jakarta",
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-annotations",
"moduleVersion": "2.2.35",
"moduleVersion": "2.2.36",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "io.swagger.core.v3:swagger-core-jakarta",
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-core",
"moduleVersion": "2.2.35",
"moduleVersion": "2.2.36",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "io.swagger.core.v3:swagger-models-jakarta",
"moduleUrl": "https://github.com/swagger-api/swagger-core/modules/swagger-models",
"moduleVersion": "2.2.35",
"moduleVersion": "2.2.36",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
@ -803,7 +803,7 @@
},
{
"moduleName": "net.bytebuddy:byte-buddy",
"moduleVersion": "1.17.6",
"moduleVersion": "1.17.7",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
@ -946,7 +946,7 @@
{
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
"moduleUrl": "https://tomcat.apache.org/",
"moduleVersion": "10.1.43",
"moduleVersion": "10.1.44",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
},
@ -1041,196 +1041,196 @@
{
"moduleName": "org.eclipse.angus:angus-mail",
"moduleUrl": "https://www.eclipse.org",
"moduleVersion": "2.0.3",
"moduleVersion": "2.0.4",
"moduleLicense": "GPL2 w/ CPE",
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
},
{
"moduleName": "org.eclipse.angus:jakarta.mail",
"moduleUrl": "https://www.eclipse.org",
"moduleVersion": "2.0.3",
"moduleVersion": "2.0.4",
"moduleLicense": "GPL2 w/ CPE",
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
},
{
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-annotations",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-plus",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlet",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-servlets",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.ee10:jetty-ee10-webapp",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-client",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-common",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-core-server",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-api",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty.websocket:jetty-websocket-jetty-common",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-alpn-client",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-client",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-ee",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-http",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-io",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-plus",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-security",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-server",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-session",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-util",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
{
"moduleName": "org.eclipse.jetty:jetty-xml",
"moduleUrl": "https://jetty.org/",
"moduleVersion": "12.0.23",
"moduleVersion": "12.0.25",
"moduleLicense": "Eclipse Public License - Version 2.0",
"moduleLicenseUrl": "https://www.eclipse.org/legal/epl-2.0/"
},
@ -1272,15 +1272,16 @@
{
"moduleName": "org.hibernate.orm:hibernate-core",
"moduleUrl": "https://www.hibernate.org/orm/6.6",
"moduleVersion": "6.6.22.Final",
"moduleVersion": "6.6.26.Final",
"moduleLicense": "GNU Library General Public License v2.1 or later",
"moduleLicenseUrl": "https://www.opensource.org/licenses/LGPL-2.1"
},
{
"moduleName": "org.hibernate.validator:hibernate-validator",
"moduleVersion": "8.0.2.Final",
"moduleUrl": "https://hibernate.org/validator",
"moduleVersion": "8.0.3.Final",
"moduleLicense": "Apache License 2.0",
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "org.jboss.logging:jboss-logging",
@ -1470,320 +1471,320 @@
},
{
"moduleName": "org.springdoc:springdoc-openapi-starter-common",
"moduleVersion": "2.8.9",
"moduleVersion": "2.8.11",
"moduleLicense": "The Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-api",
"moduleVersion": "2.8.9",
"moduleVersion": "2.8.11",
"moduleLicense": "The Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "org.springdoc:springdoc-openapi-starter-webmvc-ui",
"moduleVersion": "2.8.9",
"moduleVersion": "2.8.11",
"moduleLicense": "The Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
},
{
"moduleName": "org.springframework.boot:spring-boot",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-actuator-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-autoconfigure",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-actuator",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-aop",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-cache",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-data-jpa",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-jdbc",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-jetty",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-json",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-logging",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-mail",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-security",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-thymeleaf",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-validation",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.boot:spring-boot-starter-web",
"moduleUrl": "https://spring.io/projects/spring-boot",
"moduleVersion": "3.5.4",
"moduleVersion": "3.5.5",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.data:spring-data-commons",
"moduleUrl": "https://spring.io/projects/spring-data",
"moduleVersion": "3.5.2",
"moduleVersion": "3.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.data:spring-data-jpa",
"moduleUrl": "https://projects.spring.io/spring-data-jpa",
"moduleVersion": "3.5.2",
"moduleVersion": "3.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-config",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-core",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-crypto",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-oauth2-client",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-oauth2-core",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-oauth2-jose",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-saml2-service-provider",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.security:spring-security-web",
"moduleUrl": "https://spring.io/projects/spring-security",
"moduleVersion": "6.5.2",
"moduleVersion": "6.5.3",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework.session:spring-session-core",
"moduleUrl": "https://spring.io/projects/spring-session",
"moduleVersion": "3.5.1",
"moduleVersion": "3.5.2",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-aop",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-aspects",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-beans",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-context",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-context-support",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-core",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-expression",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-jcl",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-jdbc",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-orm",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-tx",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-web",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
{
"moduleName": "org.springframework:spring-webmvc",
"moduleUrl": "https://github.com/spring-projects/spring-framework",
"moduleVersion": "6.2.9",
"moduleVersion": "6.2.10",
"moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
},
@ -1821,7 +1822,7 @@
{
"moduleName": "org.webjars:swagger-ui",
"moduleUrl": "https://www.webjars.org",
"moduleVersion": "5.21.0",
"moduleVersion": "5.27.1",
"moduleLicense": "Apache-2.0"
},
{

View File

@ -482,6 +482,11 @@
}
progressBar.css('width', '100%');
progressBar.attr('aria-valuenow', Array.from(files).length);
setTimeout(() => {
progressBar.closest('.progressBarContainer').hide();
progressBar.css('width', '0%');
progressBar.attr('aria-valuenow', 0);
}, 1000);
}
function updateProgressBar(progressBar, files) {

View File

@ -57,11 +57,9 @@ class RedactControllerTest {
private static final Logger log = LoggerFactory.getLogger(RedactControllerTest.class);
@Mock
private CustomPDFDocumentFactory pdfDocumentFactory;
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
@InjectMocks
private RedactController redactController;
@InjectMocks private RedactController redactController;
private MockMultipartFile mockPdfFile;
private PDDocument mockDocument;
@ -72,9 +70,15 @@ class RedactControllerTest {
private PDPage realPage;
// Helpers
private void testAutoRedaction(String searchText, boolean useRegex, boolean wholeWordSearch,
String redactColor, float padding, boolean convertToImage,
boolean expectSuccess) throws Exception {
private void testAutoRedaction(
String searchText,
boolean useRegex,
boolean wholeWordSearch,
String redactColor,
float padding,
boolean convertToImage,
boolean expectSuccess)
throws Exception {
RedactPdfRequest request = createRedactPdfRequest();
request.setListOfText(searchText);
request.setUseRegex(useRegex);
@ -103,7 +107,8 @@ class RedactControllerTest {
}
}
private void testManualRedaction(List<RedactionArea> redactionAreas, boolean convertToImage) throws Exception {
private void testManualRedaction(List<RedactionArea> redactionAreas, boolean convertToImage)
throws Exception {
ManualRedactPdfRequest request = createManualRedactPdfRequest();
request.setRedactions(redactionAreas);
request.setConvertPDFToImage(convertToImage);
@ -123,18 +128,16 @@ class RedactControllerTest {
@BeforeEach
void setUp() throws IOException {
mockPdfFile = new MockMultipartFile(
"fileInput",
"test.pdf",
"application/pdf",
createSimplePdfContent()
);
mockPdfFile =
new MockMultipartFile(
"fileInput", "test.pdf", "application/pdf", createSimplePdfContent());
// Mock PDF document and related objects
mockDocument = mock(PDDocument.class);
mockPages = mock(PDPageTree.class);
mockPage = mock(PDPage.class);
org.apache.pdfbox.pdmodel.PDDocumentCatalog mockCatalog = mock(org.apache.pdfbox.pdmodel.PDDocumentCatalog.class);
org.apache.pdfbox.pdmodel.PDDocumentCatalog mockCatalog =
mock(org.apache.pdfbox.pdmodel.PDDocumentCatalog.class);
// Setup document structure properly
when(pdfDocumentFactory.load(any(MockMultipartFile.class))).thenReturn(mockDocument);
@ -153,12 +156,14 @@ class RedactControllerTest {
when(mockPage.getMediaBox()).thenReturn(pageRect);
when(mockPage.getBBox()).thenReturn(pageRect);
InputStream mockInputStream = new ByteArrayInputStream("BT /F1 12 Tf 100 200 Td (test content) Tj ET".getBytes());
InputStream mockInputStream =
new ByteArrayInputStream("BT /F1 12 Tf 100 200 Td (test content) Tj ET".getBytes());
when(mockPage.getContents()).thenReturn(mockInputStream);
when(mockPage.hasContents()).thenReturn(true);
org.apache.pdfbox.cos.COSDocument mockCOSDocument = mock(org.apache.pdfbox.cos.COSDocument.class);
org.apache.pdfbox.cos.COSDocument mockCOSDocument =
mock(org.apache.pdfbox.cos.COSDocument.class);
org.apache.pdfbox.cos.COSStream mockCOSStream = mock(org.apache.pdfbox.cos.COSStream.class);
when(mockDocument.getDocument()).thenReturn(mockCOSDocument);
when(mockCOSDocument.createCOSStream()).thenReturn(mockCOSStream);
@ -167,11 +172,14 @@ class RedactControllerTest {
when(mockCOSStream.createOutputStream()).thenReturn(mockOutputStream);
when(mockCOSStream.createOutputStream(any())).thenReturn(mockOutputStream);
doAnswer(invocation -> {
ByteArrayOutputStream baos = invocation.getArgument(0);
baos.write("Mock PDF Content".getBytes());
return null;
}).when(mockDocument).save(any(ByteArrayOutputStream.class));
doAnswer(
invocation -> {
ByteArrayOutputStream baos = invocation.getArgument(0);
baos.write("Mock PDF Content".getBytes());
return null;
})
.when(mockDocument)
.save(any(ByteArrayOutputStream.class));
doNothing().when(mockDocument).close();
// Initialize a real document for unit tests
@ -185,7 +193,8 @@ class RedactControllerTest {
// Set up basic page resources
PDResources resources = new PDResources();
resources.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
resources.put(
COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.setResources(resources);
}
@ -222,7 +231,14 @@ class RedactControllerTest {
@Test
@DisplayName("Should redact multiple search terms")
void redactMultipleSearchTerms() throws Exception {
testAutoRedaction("confidential\nsecret\nprivate\nclassified", false, true, "#FF0000", 2.0f, false, true);
testAutoRedaction(
"confidential\nsecret\nprivate\nclassified",
false,
true,
"#FF0000",
2.0f,
false,
true);
}
@Test
@ -250,8 +266,12 @@ class RedactControllerTest {
when(page.getBBox()).thenReturn(pageRect);
when(page.hasContents()).thenReturn(true);
InputStream mockInputStream = new ByteArrayInputStream(
("BT /F1 12 Tf 100 200 Td (page " + i + " content with confidential info) Tj ET").getBytes());
InputStream mockInputStream =
new ByteArrayInputStream(
("BT /F1 12 Tf 100 200 Td (page "
+ i
+ " content with confidential info) Tj ET")
.getBytes());
when(page.getContents()).thenReturn(mockInputStream);
pageList.add(page);
@ -285,7 +305,8 @@ class RedactControllerTest {
when(mockPages.get(0)).thenReturn(mockPage);
org.apache.pdfbox.pdmodel.PDDocumentInformation mockInfo = mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class);
org.apache.pdfbox.pdmodel.PDDocumentInformation mockInfo =
mock(org.apache.pdfbox.pdmodel.PDDocumentInformation.class);
when(mockDocument.getDocumentInformation()).thenReturn(mockInfo);
ResponseEntity<byte[]> response = redactController.redactPdf(request);
@ -311,23 +332,27 @@ class RedactControllerTest {
@Test
@DisplayName("Should handle email pattern redaction")
void handleEmailPatternRedaction() throws Exception {
testAutoRedaction("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, false, "#0000FF", 1.5f, false, true);
testAutoRedaction(
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
true, false, "#0000FF", 1.5f, false, true);
}
@Test
@DisplayName("Should handle phone number patterns")
void handlePhoneNumberPatterns() throws Exception {
testAutoRedaction("\\(\\d{3}\\)\\s*\\d{3}-\\d{4}", true, false, "#FF0000", 1.0f, false, true);
testAutoRedaction(
"\\(\\d{3}\\)\\s*\\d{3}-\\d{4}", true, false, "#FF0000", 1.0f, false, true);
}
@ParameterizedTest
@ValueSource(strings = {
"\\d{3}-\\d{2}-\\d{4}", // SSN pattern
"\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}", // Credit card pattern
"\\b[A-Z]{2,}\\b", // Uppercase words
"\\$\\d+\\.\\d{2}", // Currency pattern
"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b" // IP address pattern
})
@ValueSource(
strings = {
"\\d{3}-\\d{2}-\\d{4}", // SSN pattern
"\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}", // Credit card pattern
"\\b[A-Z]{2,}\\b", // Uppercase words
"\\$\\d+\\.\\d{2}", // Currency pattern
"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b" // IP address pattern
})
@DisplayName("Should handle various regex patterns")
void handleVariousRegexPatterns(String regexPattern) throws Exception {
testAutoRedaction(regexPattern, true, false, "#000000", 1.0f, false, true);
@ -519,8 +544,10 @@ class RedactControllerTest {
when(page.getBBox()).thenReturn(pageRect);
when(page.hasContents()).thenReturn(true);
InputStream mockInputStream = new ByteArrayInputStream(
("BT /F1 12 Tf 100 200 Td (page " + i + " content) Tj ET").getBytes());
InputStream mockInputStream =
new ByteArrayInputStream(
("BT /F1 12 Tf 100 200 Td (page " + i + " content) Tj ET")
.getBytes());
when(page.getContents()).thenReturn(mockInputStream);
pageList.add(page);
@ -588,36 +615,38 @@ class RedactControllerTest {
request.setFileInput(null);
request.setListOfText("test");
assertDoesNotThrow(() -> {
try {
redactController.redactPdf(request);
} catch (Exception e) {
assertNotNull(e);
}
});
assertDoesNotThrow(
() -> {
try {
redactController.redactPdf(request);
} catch (Exception e) {
assertNotNull(e);
}
});
}
@Test
@DisplayName("Should handle malformed PDF gracefully")
void handleMalformedPdfGracefully() throws Exception {
MockMultipartFile malformedFile = new MockMultipartFile(
"fileInput",
"malformed.pdf",
"application/pdf",
"Not a real PDF content".getBytes()
);
MockMultipartFile malformedFile =
new MockMultipartFile(
"fileInput",
"malformed.pdf",
"application/pdf",
"Not a real PDF content".getBytes());
RedactPdfRequest request = new RedactPdfRequest();
request.setFileInput(malformedFile);
request.setListOfText("test");
assertDoesNotThrow(() -> {
try {
redactController.redactPdf(request);
} catch (Exception e) {
assertNotNull(e);
}
});
assertDoesNotThrow(
() -> {
try {
redactController.redactPdf(request);
} catch (Exception e) {
assertNotNull(e);
}
});
}
@Test
@ -723,14 +752,24 @@ class RedactControllerTest {
}
@ParameterizedTest
@ValueSource(strings = {"#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000", "FF0000", "00FF00", "0000FF"})
@ValueSource(
strings = {
"#FF0000", "#00FF00", "#0000FF", "#FFFFFF", "#000000", "FF0000", "00FF00",
"0000FF"
})
@DisplayName("Should handle various valid color formats")
void handleVariousValidColorFormats(String colorInput) throws Exception {
Color result = redactController.decodeOrDefault(colorInput);
assertNotNull(result);
assertTrue(result.getRed() >= 0 && result.getRed() <= 255, "Red component should be in valid range");
assertTrue(result.getGreen() >= 0 && result.getGreen() <= 255, "Green component should be in valid range");
assertTrue(result.getBlue() >= 0 && result.getBlue() <= 255, "Blue component should be in valid range");
assertTrue(
result.getRed() >= 0 && result.getRed() <= 255,
"Red component should be in valid range");
assertTrue(
result.getGreen() >= 0 && result.getGreen() <= 255,
"Green component should be in valid range");
assertTrue(
result.getBlue() >= 0 && result.getBlue() <= 255,
"Blue component should be in valid range");
}
@Test
@ -755,16 +794,18 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("confidential");
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
assertNotNull(tokens);
assertFalse(tokens.isEmpty());
String reconstructedText = extractTextFromTokens(tokens);
assertFalse(reconstructedText.contains("confidential"),
"Target text should be replaced with placeholder");
assertTrue(reconstructedText.contains("document"),
"Non-target text should remain");
assertFalse(
reconstructedText.contains("confidential"),
"Target text should be replaced with placeholder");
assertTrue(reconstructedText.contains("document"), "Non-target text should remain");
}
@Test
@ -774,7 +815,9 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("secret");
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
assertNotNull(tokens);
@ -785,7 +828,9 @@ class RedactControllerTest {
if (array.getObject(i) instanceof COSString cosString) {
String text = cosString.getString();
if (text.contains("secret")) {
fail("Target text 'secret' should have been redacted from TJ array");
fail(
"Target text 'secret' should have been redacted from TJ"
+ " array");
}
foundModifiedTJArray = true;
}
@ -803,21 +848,33 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("redact");
List<Object> originalTokens = getOriginalTokens();
List<Object> filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> filteredTokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
long originalNonTextCount = originalTokens.stream()
.filter(token -> token instanceof Operator op && !redactController.isTextShowingOperator(op.getName()))
.count();
long originalNonTextCount =
originalTokens.stream()
.filter(
token ->
token instanceof Operator op
&& !redactController.isTextShowingOperator(
op.getName()))
.count();
long filteredNonTextCount = filteredTokens.stream()
.filter(token -> token instanceof Operator op && !redactController.isTextShowingOperator(op.getName()))
.count();
long filteredNonTextCount =
filteredTokens.stream()
.filter(
token ->
token instanceof Operator op
&& !redactController.isTextShowingOperator(
op.getName()))
.count();
assertTrue(filteredNonTextCount > 0,
"Non-text operators should be preserved");
assertTrue(filteredNonTextCount > 0, "Non-text operators should be preserved");
assertTrue(filteredNonTextCount >= originalNonTextCount / 2,
"A reasonable number of non-text operators should be preserved");
assertTrue(
filteredNonTextCount >= originalNonTextCount / 2,
"A reasonable number of non-text operators should be preserved");
}
@Test
@ -827,7 +884,9 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("\\d{3}-\\d{2}-\\d{4}"); // SSN pattern
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, true, false);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, true, false);
String reconstructedText = extractTextFromTokens(tokens);
assertFalse(reconstructedText.contains("111-22-3333"), "SSN should be redacted");
@ -841,7 +900,9 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("test");
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, true);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, true);
String reconstructedText = extractTextFromTokens(tokens);
assertTrue(reconstructedText.contains("testing"), "Partial matches should remain");
@ -856,11 +917,14 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("sensitive");
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
String reconstructedText = extractTextFromTokens(tokens);
assertFalse(reconstructedText.contains("sensitive"),
"Text should be redacted regardless of operator type");
assertFalse(
reconstructedText.contains("sensitive"),
"Text should be redacted regardless of operator type");
}
@Test
@ -884,7 +948,10 @@ class RedactControllerTest {
void shouldHandleEmptyTokenList() throws Exception {
List<Object> emptyTokens = Collections.emptyList();
assertDoesNotThrow(() -> redactController.writeFilteredContentStream(realDocument, realPage, emptyTokens));
assertDoesNotThrow(
() ->
redactController.writeFilteredContentStream(
realDocument, realPage, emptyTokens));
assertNotNull(realPage.getContents(), "Page should still have content stream");
}
@ -906,20 +973,27 @@ class RedactControllerTest {
@DisplayName("Placeholder creation should maintain text width")
void shouldCreateWidthMatchingPlaceholder() throws Exception {
String originalText = "confidential";
String placeholder = redactController.createPlaceholder(originalText);
String placeholder =
redactController.createPlaceholderWithFont(
originalText, new PDType1Font(Standard14Fonts.FontName.HELVETICA));
assertEquals(originalText.length(), placeholder.length(),
"Placeholder should maintain character count for width preservation");
assertEquals(
originalText.length(),
placeholder.length(),
"Placeholder should maintain character count for width preservation");
}
@Test
@DisplayName("Placeholder should handle special characters")
void shouldHandleSpecialCharactersInPlaceholder() throws Exception {
String originalText = "café naïve";
String placeholder = redactController.createPlaceholder(originalText);
String placeholder =
redactController.createPlaceholderWithFont(
originalText, new PDType1Font(Standard14Fonts.FontName.HELVETICA));
assertEquals(originalText.length(), placeholder.length());
assertFalse(placeholder.contains("café"), "Placeholder should not contain original text");
assertFalse(
placeholder.contains("café"), "Placeholder should not contain original text");
}
@Test
@ -929,7 +1003,9 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("secret");
List<Object> filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> filteredTokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
redactController.writeFilteredContentStream(realDocument, realPage, filteredTokens);
assertNotNull(realPage.getContents());
@ -946,15 +1022,21 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("confidential");
List<Object> filteredTokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> filteredTokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
long filteredPositioning = filteredTokens.stream()
.filter(token -> token instanceof Operator op &&
(op.getName().equals("Td") || op.getName().equals("TD") || op.getName().equals("Tm")))
.count();
long filteredPositioning =
filteredTokens.stream()
.filter(
token ->
token instanceof Operator op
&& ("Td".equals(op.getName())
|| "TD".equals(op.getName())
|| "Tm".equals(op.getName())))
.count();
assertTrue(filteredPositioning > 0,
"Positioning operators should be preserved");
assertTrue(filteredPositioning > 0, "Positioning operators should be preserved");
}
@Test
@ -966,16 +1048,21 @@ class RedactControllerTest {
}
realDocument.addPage(realPage);
realPage.setResources(new PDResources());
realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.getResources()
.put(
COSName.getPDFName("F1"),
new PDType1Font(Standard14Fonts.FontName.HELVETICA));
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
try (PDPageContentStream contentStream =
new PDPageContentStream(realDocument, realPage)) {
contentStream.setLineWidth(2);
contentStream.moveTo(100, 100);
contentStream.lineTo(200, 200);
contentStream.stroke();
contentStream.beginText();
contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12);
contentStream.setFont(
realPage.getResources().getFont(COSName.getPDFName("F1")), 12);
contentStream.newLineAtOffset(50, 750);
contentStream.showText("This is a complex document with ");
contentStream.setTextRise(5);
@ -990,19 +1077,27 @@ class RedactControllerTest {
Set<String> targetWords = Set.of("confidential");
List<Object> tokens = redactController.createTokensWithoutTargetText(realPage, targetWords, false, false);
List<Object> tokens =
redactController.createTokensWithoutTargetText(
realDocument, realPage, targetWords, false, false);
assertNotNull(tokens);
assertFalse(tokens.isEmpty());
String reconstructedText = extractTextFromTokens(tokens);
assertFalse(reconstructedText.contains("confidential"), "Target text should be redacted");
assertFalse(
reconstructedText.contains("confidential"), "Target text should be redacted");
boolean hasGraphicsOperators = tokens.stream()
.anyMatch(token -> token instanceof Operator op &&
(op.getName().equals("re") || op.getName().equals("f") ||
op.getName().equals("m") || op.getName().equals("l") ||
op.getName().equals("S")));
boolean hasGraphicsOperators =
tokens.stream()
.anyMatch(
token ->
token instanceof Operator op
&& ("re".equals(op.getName())
|| "f".equals(op.getName())
|| "m".equals(op.getName())
|| "l".equals(op.getName())
|| "S".equals(op.getName())));
assertTrue(hasGraphicsOperators, "Graphics operators should be preserved");
}
@ -1019,10 +1114,12 @@ class RedactControllerTest {
// Create resources
PDResources resources = new PDResources();
resources.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
resources.put(
COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.setResources(resources);
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
try (PDPageContentStream contentStream =
new PDPageContentStream(realDocument, realPage)) {
contentStream.beginText();
contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 12);
contentStream.newLineAtOffset(50, 750);
@ -1180,7 +1277,8 @@ class RedactControllerTest {
}
realDocument.addPage(realPage);
realPage.setResources(new PDResources());
realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.getResources()
.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
contentStream.beginText();
@ -1198,7 +1296,8 @@ class RedactControllerTest {
}
realDocument.addPage(realPage);
realPage.setResources(new PDResources());
realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.getResources()
.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
contentStream.beginText();
@ -1221,7 +1320,8 @@ class RedactControllerTest {
}
realDocument.addPage(realPage);
realPage.setResources(new PDResources());
realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.getResources()
.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
contentStream.setLineWidth(2);
@ -1248,7 +1348,8 @@ class RedactControllerTest {
}
realDocument.addPage(realPage);
realPage.setResources(new PDResources());
realPage.getResources().put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
realPage.getResources()
.put(COSName.getPDFName("F1"), new PDType1Font(Standard14Fonts.FontName.HELVETICA));
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, realPage)) {
contentStream.beginText();
@ -1266,28 +1367,29 @@ class RedactControllerTest {
// Helper for token creation
private List<Object> createSampleTokenList() {
return List.of(
Operator.getOperator("BT"),
COSName.getPDFName("F1"),
new COSFloat(12),
Operator.getOperator("Tf"),
new COSString("Sample text"),
Operator.getOperator("Tj"),
Operator.getOperator("ET")
);
Operator.getOperator("BT"),
COSName.getPDFName("F1"),
new COSFloat(12),
Operator.getOperator("Tf"),
new COSString("Sample text"),
Operator.getOperator("Tj"),
Operator.getOperator("ET"));
}
private List<Object> getOriginalTokens() throws Exception {
// Create a new page to avoid side effects from other tests
PDPage pageForTokenExtraction = new PDPage(PDRectangle.A4);
pageForTokenExtraction.setResources(realPage.getResources());
try (PDPageContentStream contentStream = new PDPageContentStream(realDocument, pageForTokenExtraction)) {
contentStream.beginText();
contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12);
contentStream.newLineAtOffset(50, 750);
contentStream.showText("Original content");
contentStream.endText();
try (PDPageContentStream contentStream =
new PDPageContentStream(realDocument, pageForTokenExtraction)) {
contentStream.beginText();
contentStream.setFont(realPage.getResources().getFont(COSName.getPDFName("F1")), 12);
contentStream.newLineAtOffset(50, 750);
contentStream.showText("Original content");
contentStream.endText();
}
return redactController.createTokensWithoutTargetText(pageForTokenExtraction, Collections.emptySet(), false, false);
return redactController.createTokensWithoutTargetText(
realDocument, pageForTokenExtraction, Collections.emptySet(), false, false);
}
private String extractTextFromTokens(List<Object> tokens) {

View File

@ -1,5 +1,11 @@
package stirling.software.SPDF.pdf;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.List;
@ -10,11 +16,6 @@ import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@ -34,33 +35,44 @@ class TextFinderTest {
private PDPage page;
// Helpers
private void testTextFinding(String pageContent, String searchTerm, boolean useRegex, boolean wholeWord,
String[] expectedTexts, int expectedCount) throws IOException {
private void testTextFinding(
String pageContent,
String searchTerm,
boolean useRegex,
boolean wholeWord,
String[] expectedTexts,
int expectedCount)
throws IOException {
addTextToPage(pageContent);
TextFinder textFinder = new TextFinder(searchTerm, useRegex, wholeWord);
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertEquals(expectedCount, foundTexts.size(),
String.format("Expected %d matches for search term '%s'", expectedCount, searchTerm));
assertEquals(
expectedCount,
foundTexts.size(),
String.format(
"Expected %d matches for search term '%s'", expectedCount, searchTerm));
if (expectedTexts != null) {
for (String expectedText : expectedTexts) {
assertTrue(foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)),
String.format("Expected to find text: '%s'", expectedText));
assertTrue(
foundTexts.stream().anyMatch(text -> text.getText().equals(expectedText)),
String.format("Expected to find text: '%s'", expectedText));
}
}
// Verify basic properties of found texts
foundTexts.forEach(text -> {
assertNotNull(text.getText());
assertTrue(text.getX1() >= 0);
assertTrue(text.getY1() >= 0);
assertTrue(text.getX2() >= text.getX1());
assertTrue(text.getY2() >= text.getY1());
assertEquals(0, text.getPageIndex()); // Single page test
});
foundTexts.forEach(
text -> {
assertNotNull(text.getText());
assertTrue(text.getX1() >= 0);
assertTrue(text.getY1() >= 0);
assertTrue(text.getX2() >= text.getX1());
assertTrue(text.getY2() >= text.getY1());
assertEquals(0, text.getPageIndex()); // Single page test
});
}
@BeforeEach
@ -84,25 +96,37 @@ class TextFinderTest {
@Test
@DisplayName("Should find simple text correctly")
void findSimpleText() throws IOException {
testTextFinding("This is a confidential document with secret information.",
"confidential", false, false,
new String[]{"confidential"}, 1);
testTextFinding(
"This is a confidential document with secret information.",
"confidential",
false,
false,
new String[] {"confidential"},
1);
}
@Test
@DisplayName("Should perform case-insensitive search")
void performCaseInsensitiveSearch() throws IOException {
testTextFinding("This document contains CONFIDENTIAL information.",
"confidential", false, false,
new String[]{"CONFIDENTIAL"}, 1);
testTextFinding(
"This document contains CONFIDENTIAL information.",
"confidential",
false,
false,
new String[] {"CONFIDENTIAL"},
1);
}
@Test
@DisplayName("Should find multiple occurrences of same term")
void findMultipleOccurrences() throws IOException {
testTextFinding("The secret code is secret123. Keep this secret safe!",
"secret", false, false,
new String[]{"secret", "secret", "secret"}, 3);
testTextFinding(
"The secret code is secret123. Keep this secret safe!",
"secret",
false,
false,
new String[] {"secret", "secret", "secret"},
3);
}
@Test
@ -131,33 +155,49 @@ class TextFinderTest {
@Test
@DisplayName("Should find only whole words when enabled")
void findOnlyWholeWords() throws IOException {
testTextFinding("This is a test testing document with tested results.",
"test", false, true,
new String[]{"test"}, 1);
testTextFinding(
"This is a test testing document with tested results.",
"test",
false,
true,
new String[] {"test"},
1);
}
@Test
@DisplayName("Should find partial matches when whole word search disabled")
void findPartialMatches() throws IOException {
testTextFinding("This is a test testing document with tested results.",
"test", false, false,
new String[]{"test", "test", "test"}, 3);
testTextFinding(
"This is a test testing document with tested results.",
"test",
false,
false,
new String[] {"test", "test", "test"},
3);
}
@Test
@DisplayName("Should handle punctuation boundaries correctly")
void handlePunctuationBoundaries() throws IOException {
testTextFinding("Hello, world! Testing: test-case (test).",
"test", false, true,
new String[]{"test"}, 2); // Both standalone "test" and "test" in "test-case"
testTextFinding(
"Hello, world! Testing: test-case (test).",
"test",
false,
true,
new String[] {"test"},
2); // Both standalone "test" and "test" in "test-case"
}
@Test
@DisplayName("Should handle word boundaries with special characters")
void handleSpecialCharacterBoundaries() throws IOException {
testTextFinding("Email: test@example.com and test.txt file",
"test", false, true,
new String[]{"test"}, 2); // Both in email and filename should match
testTextFinding(
"Email: test@example.com and test.txt file",
"test",
false,
true,
new String[] {"test"},
2); // Both in email and filename should match
}
}
@ -168,46 +208,64 @@ class TextFinderTest {
@Test
@DisplayName("Should find text matching regex pattern")
void findTextMatchingRegex() throws IOException {
testTextFinding("Contact John at 123-45-6789 or Jane at 987-65-4321 for details.",
"\\d{3}-\\d{2}-\\d{4}", true, false,
new String[]{"123-45-6789", "987-65-4321"}, 2);
testTextFinding(
"Contact John at 123-45-6789 or Jane at 987-65-4321 for details.",
"\\d{3}-\\d{2}-\\d{4}",
true,
false,
new String[] {"123-45-6789", "987-65-4321"},
2);
}
@Test
@DisplayName("Should find email addresses with regex")
void findEmailAddresses() throws IOException {
testTextFinding("Email: test@example.com and admin@test.org",
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, false,
new String[]{"test@example.com", "admin@test.org"}, 2);
testTextFinding(
"Email: test@example.com and admin@test.org",
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
true,
false,
new String[] {"test@example.com", "admin@test.org"},
2);
}
@Test
@DisplayName("Should combine regex with whole word search")
void combineRegexWithWholeWord() throws IOException {
testTextFinding("Email: test@example.com and admin@test.org",
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", true, true,
new String[]{"test@example.com", "admin@test.org"}, 2);
testTextFinding(
"Email: test@example.com and admin@test.org",
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
true,
true,
new String[] {"test@example.com", "admin@test.org"},
2);
}
@Test
@DisplayName("Should find currency patterns")
void findCurrencyPatterns() throws IOException {
testTextFinding("Price: $100.50 and €75.25",
"\\$\\d+\\.\\d{2}", true, false,
new String[]{"$100.50"}, 1);
testTextFinding(
"Price: $100.50 and €75.25",
"\\$\\d+\\.\\d{2}",
true,
false,
new String[] {"$100.50"},
1);
}
@ParameterizedTest
@ValueSource(strings = {
"\\d{4}-\\d{2}-\\d{2}", // Date pattern
"\\b[A-Z]{2,}\\b", // Uppercase words
"\\w+@\\w+\\.\\w+", // Simple email pattern
"\\$\\d+", // Simple currency
"\\b\\d{3,4}\\b" // 3-4 digit numbers
})
@ValueSource(
strings = {
"\\d{4}-\\d{2}-\\d{2}", // Date pattern
"\\b[A-Z]{2,}\\b", // Uppercase words
"\\w+@\\w+\\.\\w+", // Simple email pattern
"\\$\\d+", // Simple currency
"\\b\\d{3,4}\\b" // 3-4 digit numbers
})
@DisplayName("Should handle various regex patterns")
void handleVariousRegexPatterns(String regexPattern) throws IOException {
String testContent = "Date: 2023-12-25, Email: test@domain.com, Price: $250, Code: ABC123, Number: 1234";
String testContent =
"Date: 2023-12-25, Email: test@domain.com, Price: $250, Code: ABC123, Number: 1234";
addTextToPage(testContent);
TextFinder textFinder = new TextFinder(regexPattern, true, false);
@ -215,7 +273,9 @@ class TextFinderTest {
List<PDFText> foundTexts = textFinder.getFoundTexts();
// Each pattern should find at least one match in our test content
assertFalse(foundTexts.isEmpty(), String.format("Pattern '%s' should find at least one match", regexPattern));
assertFalse(
foundTexts.isEmpty(),
String.format("Pattern '%s' should find at least one match", regexPattern));
}
@Test
@ -230,9 +290,10 @@ class TextFinderTest {
assertNotNull(foundTexts);
} catch (java.util.regex.PatternSyntaxException e) {
assertNotNull(e.getMessage());
assertTrue(e.getMessage().contains("Unclosed character class") ||
e.getMessage().contains("syntax"),
"Exception should indicate regex syntax error");
assertTrue(
e.getMessage().contains("Unclosed character class")
|| e.getMessage().contains("syntax"),
"Exception should indicate regex syntax error");
} catch (RuntimeException | IOException e) {
assertNotNull(e.getMessage());
}
@ -246,33 +307,38 @@ class TextFinderTest {
@Test
@DisplayName("Should handle international characters")
void handleInternationalCharacters() throws IOException {
testTextFinding("Hello café naïve résumé",
"café", false, false,
new String[]{"café"}, 1);
testTextFinding(
"Hello café naïve résumé", "café", false, false, new String[] {"café"}, 1);
}
@Test
@DisplayName("Should find text with accented characters")
void findAccentedCharacters() throws IOException {
testTextFinding("Café, naïve, résumé, piñata",
"café", false, false,
new String[]{"Café"}, 1); // Case insensitive
testTextFinding(
"Café, naïve, résumé, piñata",
"café",
false,
false,
new String[] {"Café"},
1); // Case insensitive
}
@Test
@DisplayName("Should handle special symbols")
void handleSpecialSymbols() throws IOException {
testTextFinding("Symbols: © ® ™ ± × ÷ § ¶",
"©", false, false,
new String[]{"©"}, 1);
testTextFinding("Symbols: © ® ™ ± × ÷ § ¶", "©", false, false, new String[] {"©"}, 1);
}
@Test
@DisplayName("Should find currency symbols")
void findCurrencySymbols() throws IOException {
testTextFinding("Prices: $100 €75 £50 ¥1000",
"[€£¥]", true, false,
new String[]{"", "£", "¥"}, 3);
testTextFinding(
"Prices: $100 €75 £50 ¥1000",
"[€£¥]",
true,
false,
new String[] {"", "£", "¥"},
3);
}
}
@ -330,7 +396,7 @@ class TextFinderTest {
String longTerm = "a".repeat(1000);
String content = "Short text with " + longTerm + " embedded.";
testTextFinding(content, longTerm, false, false, new String[]{longTerm}, 1);
testTextFinding(content, longTerm, false, false, new String[] {longTerm}, 1);
}
@Test
@ -350,8 +416,9 @@ class TextFinderTest {
long endTime = System.currentTimeMillis();
assertEquals(10, foundTexts.size());
assertTrue(endTime - startTime < 3000,
"Multi-page search should complete within 3 seconds");
assertTrue(
endTime - startTime < 3000,
"Multi-page search should complete within 3 seconds");
}
}
@ -402,12 +469,13 @@ class TextFinderTest {
String complexRegex = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z\\d]{6}";
assertDoesNotThrow(() -> {
TextFinder textFinder = new TextFinder(complexRegex, true, false);
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertNotNull(foundTexts);
});
assertDoesNotThrow(
() -> {
TextFinder textFinder = new TextFinder(complexRegex, true, false);
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertNotNull(foundTexts);
});
}
@ParameterizedTest
@ -464,10 +532,11 @@ class TextFinderTest {
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertFalse(foundTexts.isEmpty());
foundTexts.forEach(text -> {
assertNotNull(text.getText());
assertTrue(text.getX1() >= 0 && text.getY1() >= 0);
});
foundTexts.forEach(
text -> {
assertNotNull(text.getText());
assertTrue(text.getX1() >= 0 && text.getY1() >= 0);
});
}
}
@ -485,8 +554,10 @@ class TextFinderTest {
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertEquals(1, foundTexts.size(),
"Should find exactly one standalone '1', not the ones embedded in other numbers/codes");
assertEquals(
1,
foundTexts.size(),
"Should find exactly one standalone '1', not the ones embedded in other numbers/codes");
assertEquals("1", foundTexts.get(0).getText());
}
@ -500,14 +571,16 @@ class TextFinderTest {
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertTrue(foundTexts.size() >= 3,
"Should find multiple instances of '1' including standalone, in '1234', and in 'A1B'");
assertTrue(
foundTexts.size() >= 3,
"Should find multiple instances of '1' including standalone, in '1234', and in 'A1B'");
}
@Test
@DisplayName("Should find single characters in various contexts")
void findSingleCharacters() throws IOException {
String content = "Grade: A. Section B has item A-1. The letter A appears multiple times.";
String content =
"Grade: A. Section B has item A-1. The letter A appears multiple times.";
addTextToPage(content);
TextFinder textFinder = new TextFinder("A", false, true);
@ -522,24 +595,29 @@ class TextFinderTest {
}
@Test
@DisplayName("Should handle digits at word boundaries correctly")
@DisplayName("Digits as strict standalone tokens (exclude decimals and suffixes)")
void findDigitsAtWordBoundaries() throws IOException {
String content = "Numbers: 1, 2, 3. Code: 123. Version: 1.0. Item1 and Item2.";
String content =
"Numbers: 1, 2, 3. Code: 123. Version: 1.0. Item1 and Item2. Price: 2,50€";
addTextToPage(content);
TextFinder textFinder1 = new TextFinder("1", false, true);
textFinder1.getText(document);
List<PDFText> foundTexts1 = textFinder1.getFoundTexts();
assertEquals(1, foundTexts1.size(),
"Should find only the standalone '1' at the beginning");
assertEquals(
1,
foundTexts1.size(),
"Should find only the standalone '1'; do not count the '1' in '1.0' or in 'Item1'.");
TextFinder textFinder2 = new TextFinder("2", false, true);
textFinder2.getText(document);
List<PDFText> foundTexts2 = textFinder2.getFoundTexts();
assertEquals(1, foundTexts2.size(),
"Should find only the standalone '2' in the number list");
assertEquals(
1,
foundTexts2.size(),
"Should find only the standalone '2' in the number list");
}
@Test
@ -566,8 +644,10 @@ class TextFinderTest {
textFinder.getText(document);
List<PDFText> foundTexts = textFinder.getFoundTexts();
assertEquals(2, foundTexts.size(),
"Should find both '1' instances despite spacing variations");
assertEquals(
2,
foundTexts.size(),
"Should find both '1' instances despite spacing variations");
}
}

View File

@ -49,7 +49,7 @@ dependencies {
api 'org.springframework.boot:spring-boot-starter-mail'
api 'org.springframework.boot:spring-boot-starter-cache'
api 'com.github.ben-manes.caffeine:caffeine'
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.35'
api 'io.swagger.core.v3:swagger-core-jakarta:2.2.36'
implementation 'com.bucket4j:bucket4j_jdk17-core:8.14.0'
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17

View File

@ -11,7 +11,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
@ -59,19 +58,16 @@ public class AccountWebController {
private final SessionPersistentRegistry sessionPersistentRegistry;
// Assuming you have a repository for user operations
private final UserRepository userRepository;
private final boolean runningEE;
private final TeamRepository teamRepository;
public AccountWebController(
ApplicationProperties applicationProperties,
SessionPersistentRegistry sessionPersistentRegistry,
UserRepository userRepository,
TeamRepository teamRepository,
@Qualifier("runningEE") boolean runningEE) {
TeamRepository teamRepository) {
this.applicationProperties = applicationProperties;
this.sessionPersistentRegistry = sessionPersistentRegistry;
this.userRepository = userRepository;
this.runningEE = runningEE;
this.teamRepository = teamRepository;
}
@ -207,11 +203,9 @@ public class AccountWebController {
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
@EnterpriseEndpoint
@GetMapping("/usage")
public String showUsage() {
if (!runningEE) {
return "error";
}
return "usage";
}
@ -243,7 +237,7 @@ public class AccountWebController {
// Also check if user is part of the Internal team
if (user.getTeam() != null
&& user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
&& TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) {
shouldRemove = true;
}
@ -362,11 +356,9 @@ public class AccountWebController {
teamRepository.findAll().stream()
.filter(
team ->
!team.getName()
.equals(
stirling.software.proprietary.security
.service.TeamService
.INTERNAL_TEAM_NAME))
!stirling.software.proprietary.security.service.TeamService
.INTERNAL_TEAM_NAME
.equals(team.getName()))
.toList();
model.addAttribute("teams", allTeams);

View File

@ -134,21 +134,21 @@ public class DatabaseConfig {
ApplicationProperties.Driver driver =
ApplicationProperties.Driver.valueOf(driverName.toUpperCase());
switch (driver) {
return switch (driver) {
case H2 -> {
log.debug("H2 driver selected");
return DatabaseDriver.H2.getDriverClassName();
yield DatabaseDriver.H2.getDriverClassName();
}
case POSTGRESQL -> {
log.debug("Postgres driver selected");
return DatabaseDriver.POSTGRESQL.getDriverClassName();
yield DatabaseDriver.POSTGRESQL.getDriverClassName();
}
default -> {
log.warn("{} driver selected", driverName);
throw new UnsupportedProviderException(
driverName + " is not currently supported");
}
}
};
} catch (IllegalArgumentException e) {
log.warn("Unknown driver: {}", driverName);
throw new UnsupportedProviderException(driverName + " is not currently supported");

View File

@ -7,10 +7,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.eclipse.jetty.http.HttpStatus;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
@ -145,7 +145,7 @@ public class DatabaseController {
.body(resource);
} catch (IOException e) {
log.error("Error downloading file: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.SEE_OTHER_303)
return ResponseEntity.status(HttpStatus.SEE_OTHER)
.location(URI.create("/database?error=downloadFailed"))
.build();
}

View File

@ -0,0 +1,101 @@
package stirling.software.proprietary.security.controller.api.enterprise;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Conditional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.FileInfo;
import stirling.software.proprietary.security.config.EnterpriseEndpoint;
import stirling.software.proprietary.security.database.H2SQLCondition;
import stirling.software.proprietary.security.service.DatabaseService;
@Slf4j
@Controller
@RequestMapping("/api/v1/database")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@EnterpriseEndpoint
@Conditional(H2SQLCondition.class)
@Tag(name = "Database", description = "Database APIs for backup, import, and management")
@RequiredArgsConstructor
public class DatabaseControllerEnterprise {
private final DatabaseService databaseService;
@Operation(
summary = "Delete the last database backup file",
description =
"Only Enterprise - Deletes the last database backup file from the server.")
@DeleteMapping("/deleteLast")
public ResponseEntity<?> deleteLastFile() {
log.info("Deleting last database backup file...");
List<Pair<FileInfo, Boolean>> results = databaseService.deleteLastBackup();
return getDeleteAllResults(results);
}
@Operation(
summary = "Delete all database backup files",
description = "Only Enterprise - Deletes all database backup files from the server.")
@DeleteMapping("/deleteAll")
public ResponseEntity<?> deleteAllFiles() {
log.info("Deleting all database backup files...");
List<Pair<FileInfo, Boolean>> results = databaseService.deleteAllBackups();
return getDeleteAllResults(results);
}
private ResponseEntity<?> getDeleteAllResults(List<Pair<FileInfo, Boolean>> results) {
if (results.isEmpty()) {
log.info("No backup files found to delete.");
return ResponseEntity.ok(new DeleteAllResult(List.of(), List.of(), "noContent"));
}
List<String> deleted =
results.stream()
.filter(p -> Boolean.TRUE.equals(p.getRight()))
.map(p -> p.getLeft().getFileName())
.toList();
List<String> failed =
results.stream()
.filter(p -> !Boolean.TRUE.equals(p.getRight()))
.map(p -> p.getLeft().getFileName())
.toList();
log.info("Deleted backup files: {}", deleted);
if (!failed.isEmpty()) {
log.warn("Some backup files could not be deleted: {}", failed);
return ResponseEntity.status(HttpStatus.MULTI_STATUS) // 207
.body(new DeleteAllResult(deleted, failed, "partialFailure"));
}
DeleteAllResult result = new DeleteAllResult(deleted, failed, "ok");
log.debug(
"DeleteAllResult: deleted={}, failed={}, status={}",
result.deleted,
result.failed,
result.status);
return ResponseEntity.ok(result); // 200
}
private static final class DeleteAllResult {
public final List<String> deleted;
public final List<String> failed;
public final String status;
public DeleteAllResult(List<String> deleted, List<String> failed, String status) {
this.deleted = deleted;
this.failed = failed;
this.status = status;
}
}
}

View File

@ -5,6 +5,7 @@ import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.Connection;
import java.sql.PreparedStatement;
@ -21,6 +22,7 @@ import java.util.stream.Collectors;
import javax.sql.DataSource;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.jdbc.datasource.init.CannotReadScriptException;
import org.springframework.jdbc.datasource.init.ScriptException;
import org.springframework.stereotype.Service;
@ -45,10 +47,39 @@ public class DatabaseService implements DatabaseServiceInterface {
public DatabaseService(
ApplicationProperties.Datasource datasourceProps, DataSource dataSource) {
this.BACKUP_DIR =
Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
this.BACKUP_DIR = Paths.get(InstallationPathConfig.getBackupPath()).normalize();
this.datasourceProps = datasourceProps;
this.dataSource = dataSource;
moveBackupFiles();
}
/** Move all backup files from db/backup to backup/db */
@Deprecated(since = "2.0.0", forRemoval = true)
private void moveBackupFiles() {
Path sourceDir =
Paths.get(InstallationPathConfig.getConfigPath(), "db", "backup").normalize();
if (!Files.exists(sourceDir)) {
log.info("Source directory does not exist: {}", sourceDir);
return;
}
try {
Files.createDirectories(BACKUP_DIR);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
for (Path entry : stream) {
if (entry.getFileName().toString().startsWith(BACKUP_PREFIX)
&& entry.getFileName().toString().endsWith(SQL_SUFFIX)) {
Files.move(
entry,
BACKUP_DIR.resolve(entry.getFileName()),
StandardCopyOption.REPLACE_EXISTING);
}
}
}
} catch (IOException e) {
log.error("Error moving backup files: {}", e.getMessage(), e);
}
}
/**
@ -198,6 +229,46 @@ public class DatabaseService implements DatabaseServiceInterface {
}
}
@Override
public List<Pair<FileInfo, Boolean>> deleteAllBackups() {
List<FileInfo> backupList = this.getBackupList();
List<Pair<FileInfo, Boolean>> deletedFiles = new ArrayList<>();
for (FileInfo backup : backupList) {
try {
Files.deleteIfExists(Paths.get(backup.getFilePath()));
deletedFiles.add(Pair.of(backup, true));
} catch (IOException e) {
log.error("Error deleting backup file: {}", backup.getFileName(), e);
deletedFiles.add(Pair.of(backup, false));
}
}
return deletedFiles;
}
@Override
public List<Pair<FileInfo, Boolean>> deleteLastBackup() {
List<FileInfo> backupList = this.getBackupList();
List<Pair<FileInfo, Boolean>> deletedFiles = new ArrayList<>();
if (!backupList.isEmpty()) {
FileInfo lastBackup = backupList.get(backupList.size() - 1);
try {
Files.deleteIfExists(Paths.get(lastBackup.getFilePath()));
deletedFiles.add(Pair.of(lastBackup, true));
} catch (IOException e) {
log.error("Error deleting last backup file: {}", lastBackup.getFileName(), e);
deletedFiles.add(Pair.of(lastBackup, false));
}
}
return deletedFiles;
}
/**
* Deletes the oldest backup file from the specified list.
*
* @param filteredBackupList the list of backup files
*/
private static void deleteOldestBackup(List<FileInfo> filteredBackupList) {
try {
filteredBackupList.sort(
@ -237,6 +308,11 @@ public class DatabaseService implements DatabaseServiceInterface {
return version;
}
/*
* Checks if the current datasource is H2.
*
* @return true if the datasource is H2, false otherwise
*/
private boolean isH2Database() {
boolean isTypeH2 =
datasourceProps.getType().equalsIgnoreCase(ApplicationProperties.Driver.H2.name());
@ -301,6 +377,11 @@ public class DatabaseService implements DatabaseServiceInterface {
return filePath;
}
/**
* Executes a database script.
*
* @param scriptPath the path to the script file
*/
private void executeDatabaseScript(Path scriptPath) {
if (isH2Database()) {
String query = "RUNSCRIPT from ?;";

View File

@ -3,6 +3,8 @@ package stirling.software.proprietary.security.service;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
import stirling.software.common.model.FileInfo;
import stirling.software.common.model.exception.UnsupportedProviderException;
@ -14,4 +16,8 @@ public interface DatabaseServiceInterface {
boolean hasBackup();
List<FileInfo> getBackupList();
List<Pair<FileInfo, Boolean>> deleteAllBackups();
List<Pair<FileInfo, Boolean>> deleteLastBackup();
}

View File

@ -1,9 +1,11 @@
package stirling.software.proprietary.security.service;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@ -52,6 +54,34 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
this.verifyingKeyCache = cacheManager.getCache("verifyingKeys");
}
/** Move all key files from db/keys to backup/keys */
@Deprecated(since = "2.0.0", forRemoval = true)
private void moveKeysToBackup() {
Path sourceDir =
Paths.get(InstallationPathConfig.getConfigPath(), "db", "keys").normalize();
if (!Files.exists(sourceDir)) {
log.info("Source directory does not exist: {}", sourceDir);
return;
}
Path targetDir = Paths.get(InstallationPathConfig.getPrivateKeyPath()).normalize();
try {
Files.createDirectories(targetDir);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
for (Path entry : stream) {
Files.move(
entry,
targetDir.resolve(entry.getFileName()),
StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (IOException e) {
log.error("Error moving key files to backup: {}", e.getMessage(), e);
}
}
@PostConstruct
public void initializeKeystore() {
if (!isKeystoreEnabled()) {
@ -59,6 +89,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
}
try {
moveKeysToBackup();
ensurePrivateKeyDirectoryExists();
loadKeyPair();
} catch (Exception e) {

View File

@ -2,6 +2,7 @@ package stirling.software.proprietary.service;
import java.util.Map;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.security.core.Authentication;
@ -29,8 +30,7 @@ public class AuditService {
public AuditService(
AuditEventRepository repository,
AuditConfigurationProperties auditConfig,
@org.springframework.beans.factory.annotation.Qualifier("runningEE")
boolean runningEE) {
@Qualifier("runningEE") boolean runningEE) {
this.repository = repository;
this.auditConfig = auditConfig;
this.runningEE = runningEE;

View File

@ -2,7 +2,7 @@ plugins {
id "java"
id "jacoco"
id "io.spring.dependency-management" version "1.1.7"
id "org.springframework.boot" version "3.5.4"
id "org.springframework.boot" version "3.5.5"
id "org.springdoc.openapi-gradle-plugin" version "1.9.0"
id "io.swagger.swaggerhub" version "1.3.2"
id "edu.sc.seis.launch4j" version "4.0.0"
@ -21,12 +21,12 @@ import java.nio.file.Files
import java.time.Year
ext {
springBootVersion = "3.5.4"
springBootVersion = "3.5.5"
pdfboxVersion = "3.0.5"
imageioVersion = "3.12.0"
lombokVersion = "1.18.38"
bouncycastleVersion = "1.81"
springSecuritySamlVersion = "6.5.2"
springSecuritySamlVersion = "6.5.3"
openSamlVersion = "4.3.2"
commonmarkVersion = "0.25.1"
googleJavaFormatVersion = "1.28.0"
@ -74,8 +74,8 @@ allprojects {
}
tasks.register('writeVersion', WriteProperties) {
outputFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties')
println "Writing version.properties to ${outputFile.path}"
destinationFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties')
println "Writing version.properties to ${destinationFile.get().asFile.path}"
comment = "${new Date()}"
property 'version', project.provider { project.version.toString() }
}
@ -222,11 +222,14 @@ tasks.withType(JavaCompile).configureEach {
dependsOn "spotlessApply"
}
def allProjects = ((subprojects as Set<Project>) + project) as Set<Project>
licenseReport {
projects = [project]
projects = allProjects
renderers = [new JsonReportRenderer()]
allowedLicensesFile = project.layout.projectDirectory.file("app/allowed-licenses.json").asFile
outputDir = project.layout.buildDirectory.dir("reports/dependency-license").get().asFile.path
configurations = [ "productionRuntimeClasspath", "runtimeClasspath" ]
}
// Configure the forked spring boot run task to properly delegate to the stirling-pdf module