Merge remote-tracking branch 'origin/master' into feature/add-option-to-disable-captcha
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Duplicati.Library.Backend.Strings
|
||||
public static string OSSAccessKeySecretDescriptionLong { get { return LC.L(@"Access Key Secret is the key used by the user to encrypt signature strings and by OSS to verify these signature strings."); } }
|
||||
public static string OSSAccessKeySecretDescriptionShort { get { return LC.L(@"Access Key Secret"); } }
|
||||
public static string OSSBucketNameDescriptionLong { get { return LC.L(@"A storage space is a container used to store objects (Object), and all objects must belong to a specific storage space."); } }
|
||||
public static string OSSBucketNameDescriptionShort { get { return LC.L(@"Bucket Name"); } }
|
||||
public static string OSSBucketNameDescriptionShort { get { return LC.L(@"Bucket name"); } }
|
||||
public static string OSSRegionDescriptionLong { get { return LC.L(@"Region indicates the physical location of the OSS data center."); } }
|
||||
public static string OSSRegionDescriptionShort { get { return LC.L(@"Region"); } }
|
||||
public static string OSSEndpointDescriptionLong { get { return LC.L(@"Endpoint refers to the domain name through which OSS provides external services."); } }
|
||||
|
||||
@@ -26,20 +26,20 @@ namespace Duplicati.Library.Backend.Strings
|
||||
{
|
||||
public static string Description { get { return LC.L(@"This backend can read and write data to the Storj DCS."); } }
|
||||
public static string DisplayName { get { return LC.L(@"Storj DCS (Decentralized Cloud Storage)"); } }
|
||||
public static string TestConnectionFailed { get { return LC.L(@"The connection-test failed."); } }
|
||||
public static string StorjAuthMethodDescriptionLong { get { return LC.L(@"The authentication method describes which way to use to connect to the network - either via API key or via an access grant."); } }
|
||||
public static string StorjAuthMethodDescriptionShort { get { return LC.L(@"The authentication method"); } }
|
||||
public static string StorjSatelliteDescriptionLong { get { return LC.L(@"The satellite that keeps track of all metadata. Use a Storj DCS server for high-performance SLA-backed connectivity or use a community server. Or even host your own."); } }
|
||||
public static string StorjSatelliteDescriptionShort { get { return LC.L(@"The satellite"); } }
|
||||
public static string StorjAPIKeyDescriptionLong { get { return LC.L(@"The API key grants access to a specific project on your chosen satellite. Head over to the dashboard of your satellite to create one if you do not already have an API key."); } }
|
||||
public static string StorjAPIKeyDescriptionShort { get { return LC.L(@"The API key"); } }
|
||||
public static string StorjSecretDescriptionLong { get { return LC.L(@"The encryption passphrase is used to encrypt your data before sending it to the Storj network. This passphrase can be the only secret to provide - for Storj you do not necessary need any additional encryption (from Duplicati) in place."); } }
|
||||
public static string StorjSecretDescriptionShort { get { return LC.L(@"The encryption passphrase"); } }
|
||||
public static string StorjSharedAccessDescriptionLong { get { return LC.L(@"An access grant contains all information in one encrypted string. You may use it instead of a satellite, API key and secret."); } }
|
||||
public static string StorjSharedAccessDescriptionShort { get { return LC.L(@"The access grant"); } }
|
||||
public static string StorjBucketDescriptionLong { get { return LC.L(@"The bucket where the backup will reside in."); } }
|
||||
public static string StorjBucketDescriptionShort { get { return LC.L(@"The bucket"); } }
|
||||
public static string StorjFolderDescriptionLong { get { return LC.L(@"The folder within the bucket where the backup will reside in."); } }
|
||||
public static string StorjFolderDescriptionShort { get { return LC.L(@"The folder"); } }
|
||||
public static string TestConnectionFailed { get { return LC.L(@"Connection-test failed."); } }
|
||||
public static string StorjAuthMethodDescriptionLong { get { return LC.L(@"Specify the authentication method which describes which way to use to connect to the network - either via API key or via an access grant."); } }
|
||||
public static string StorjAuthMethodDescriptionShort { get { return LC.L(@"Authentication method"); } }
|
||||
public static string StorjSatelliteDescriptionLong { get { return LC.L(@"Specify the satellite that keeps track of all metadata. Use a Storj DCS server for high-performance SLA-backed connectivity or use a community server. Or even host your own."); } }
|
||||
public static string StorjSatelliteDescriptionShort { get { return LC.L(@"Satellite"); } }
|
||||
public static string StorjAPIKeyDescriptionLong { get { return LC.L(@"Supply the API key which grants access to a specific project on your chosen satellite. Head over to the dashboard of your satellite to create one if you do not already have an API key."); } }
|
||||
public static string StorjAPIKeyDescriptionShort { get { return LC.L(@"API key"); } }
|
||||
public static string StorjSecretDescriptionLong { get { return LC.L(@"Supply the encryption passphrase used to encrypt your data before sending it to the Storj network. This passphrase can be the only secret to provide - for Storj you do not necessary need any additional encryption (from Duplicati) in place."); } }
|
||||
public static string StorjSecretDescriptionShort { get { return LC.L(@"Encryption passphrase"); } }
|
||||
public static string StorjSharedAccessDescriptionLong { get { return LC.L(@"Supply the access grant which contains all information in one encrypted string. You may use it instead of a satellite, API key and secret."); } }
|
||||
public static string StorjSharedAccessDescriptionShort { get { return LC.L(@"Access grant"); } }
|
||||
public static string StorjBucketDescriptionLong { get { return LC.L(@"Specify the bucket for storing the backup."); } }
|
||||
public static string StorjBucketDescriptionShort { get { return LC.L(@"Bucket"); } }
|
||||
public static string StorjFolderDescriptionLong { get { return LC.L(@"Specify the folder in the bucket for storing the backup."); } }
|
||||
public static string StorjFolderDescriptionShort { get { return LC.L(@"Folder"); } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Duplicati.Library.Backend.Strings
|
||||
public static string COSAPISecretIdDescriptionShort { get { return LC.L(@"Secret ID"); } }
|
||||
public static string COSAPISecretKeyDescriptionLong { get { return LC.L(@"Cloud API Secret Key."); } }
|
||||
public static string COSAPISecretKeyDescriptionShort { get { return LC.L(@"Secret Key"); } }
|
||||
public static string COSBucketDescriptionLong { get { return LC.L(@"Bucket, format: BucketName-APPID"); } }
|
||||
public static string COSBucketDescriptionShort { get { return LC.L(@"Bucket"); } }
|
||||
public static string COSBucketDescriptionLong { get { return LC.L(@"Bucket name, format: BucketName-APPID"); } }
|
||||
public static string COSBucketDescriptionShort { get { return LC.L(@"Bucket name"); } }
|
||||
public static string COSLocationDescriptionLong { get { return LC.L(@"Region is the distribution area of the Tencent cloud hosting machine room. The object storage COS data is stored in the storage buckets of these regions. https://intl.cloud.tencent.com/document/product/436/6224."); } }
|
||||
public static string COSLocationDescriptionShort { get { return LC.L(@"Specifies COS location constraints"); } }
|
||||
public static string COSStorageClassDescriptionLong { get { return LC.L(@"Storage class of the object; check enumerated values at https://intl.cloud.tencent.com/document/product/436/30925."); } }
|
||||
|
||||
@@ -40,6 +40,12 @@ namespace Duplicati.Server
|
||||
|
||||
private static readonly List<string> ParameterFileOptionStrings = new List<string> { "parameters-file", "parameterfile" };
|
||||
|
||||
#if DEBUG
|
||||
private const bool DEBUG_MODE = true;
|
||||
#else
|
||||
private const bool DEBUG_MODE = false;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The log tag for messages from this class
|
||||
/// </summary>
|
||||
@@ -583,13 +589,16 @@ namespace Duplicati.Server
|
||||
|
||||
if (string.IsNullOrEmpty(serverDataFolder))
|
||||
{
|
||||
#if DEBUG
|
||||
//debug mode uses a lock file located in the app folder
|
||||
DataFolder = StartupPath;
|
||||
#else
|
||||
bool portableMode = commandlineOptions.ContainsKey("portable-mode") ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) : false;
|
||||
bool portableMode = commandlineOptions.ContainsKey("portable-mode")
|
||||
? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true)
|
||||
: (DEBUG_MODE ? true : false); // Default to portable mode in debug mode
|
||||
|
||||
if (portableMode)
|
||||
if (DEBUG_MODE && portableMode)
|
||||
{
|
||||
//debug mode uses a lock file located in the app folder
|
||||
DataFolder = StartupPath;
|
||||
}
|
||||
else if (portableMode)
|
||||
{
|
||||
//Portable mode uses a data folder in the application home dir
|
||||
DataFolder = System.IO.Path.Combine(StartupPath, "data");
|
||||
@@ -644,7 +653,6 @@ namespace Duplicati.Server
|
||||
|
||||
DataFolder = serverDataFolder;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
DataFolder = Util.AppendDirSeparator(Environment.ExpandEnvironmentVariables(serverDataFolder).Trim('"'));
|
||||
|
||||
@@ -729,6 +729,7 @@ body {
|
||||
|
||||
.stop {
|
||||
background: url('../img/progress-stop.png');
|
||||
background-size: 100%;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
|
||||
@@ -743,7 +744,8 @@ body {
|
||||
|
||||
.resume {
|
||||
background: url('../img/progress-resume.png');
|
||||
|
||||
background-size: 100%;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
background-image: url('../img/progress-resume_2x.png');
|
||||
}
|
||||
@@ -797,6 +799,7 @@ body {
|
||||
|
||||
> .pause {
|
||||
background: url('../img/pause.png');
|
||||
background-size: 100%;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
background-image: url('../img/pause_2x.png');
|
||||
@@ -809,6 +812,7 @@ body {
|
||||
|
||||
> .pause.active {
|
||||
background: url('../img/resume.png');
|
||||
background-size: 100%;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
background-image: url('../img/resume_2x.png');
|
||||
@@ -821,7 +825,8 @@ body {
|
||||
|
||||
> .throttle {
|
||||
background: url('../img/throttle.png');
|
||||
|
||||
background-size: 100%;
|
||||
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
background-image: url('../img/throttle_2x.png');
|
||||
}
|
||||
|
||||
@@ -997,7 +997,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
EditUriBackendConfig.validaters['openstack'] = function (scope, continuation) {
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('Username')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Path', gettextCatalog.getString('Bucket Name'));
|
||||
EditUriBackendConfig.require_field(scope, 'Path', gettextCatalog.getString('Bucket name'));
|
||||
|
||||
if (res && (scope['openstack_server'] || '').trim().length == 0 && (scope['openstack_server_custom'] || '').trim().length == 0)
|
||||
res = EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('You must select or fill in the AuthURI'));
|
||||
@@ -1037,7 +1037,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
EditUriBackendConfig.validaters['s3'] = function (scope, continuation) {
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket Name')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket name')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('AWS Access ID')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('AWS Access Key'));
|
||||
|
||||
@@ -1083,7 +1083,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
EditUriBackendConfig.validaters['b2'] = function (scope, continuation) {
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket Name')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket name')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('B2 Cloud Storage Account ID')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('B2 Cloud Storage Application Key'));
|
||||
|
||||
@@ -1097,7 +1097,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
value: bucketname[ix].charCodeAt(),
|
||||
pos: ix,
|
||||
character: bucketname[ix],
|
||||
fieldname: gettextCatalog.getString('Bucket Name')
|
||||
fieldname: gettextCatalog.getString('Bucket name')
|
||||
}));
|
||||
res = false;
|
||||
}
|
||||
@@ -1160,13 +1160,13 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
|
||||
if(res && scope['storj_auth_method'] == 'Access grant'){
|
||||
res = EditUriBackendConfig.require_field(scope, 'storj_shared_access', gettextCatalog.getString('storj_shared_access')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'storj_bucket', gettextCatalog.getString('Bucket'));
|
||||
EditUriBackendConfig.require_field(scope, 'storj_bucket', gettextCatalog.getString('Bucket name'));
|
||||
}
|
||||
|
||||
if(res && scope['storj_auth_method'] == 'API key'){
|
||||
res = EditUriBackendConfig.require_field(scope, 'storj_api_key', gettextCatalog.getString('API key')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'storj_secret', gettextCatalog.getString('Encryption passphrase')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'storj_bucket', gettextCatalog.getString('Bucket'));
|
||||
EditUriBackendConfig.require_field(scope, 'storj_bucket', gettextCatalog.getString('Bucket name'));
|
||||
}
|
||||
|
||||
if(res && scope['storj_auth_method'] == 'API key' && !scope['storj_satellite']){
|
||||
@@ -1223,7 +1223,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
var res =
|
||||
EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('IDrive e2 Access Key ID')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('IDrive e2 Access Key Secret')) &&
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket Name'));
|
||||
EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket name'));
|
||||
|
||||
if (res) {
|
||||
var re = new RegExp('[^A-Za-z0-9-]');
|
||||
@@ -1235,7 +1235,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo,
|
||||
value: bucketname[ix].charCodeAt(),
|
||||
pos: ix,
|
||||
character: bucketname[ix],
|
||||
fieldname: gettextCatalog.getString('Bucket Name')
|
||||
fieldname: gettextCatalog.getString('Bucket name')
|
||||
}));
|
||||
res = false;
|
||||
}
|
||||
|
||||
@@ -2815,6 +2815,7 @@ body .container .header .state .buttons button {
|
||||
}
|
||||
body .container .header .state .buttons .stop {
|
||||
background: url('../img/progress-stop.png');
|
||||
background-size: 100%;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
@@ -2830,6 +2831,7 @@ body .container .header .state .buttons .stop {
|
||||
}
|
||||
body .container .header .state .buttons .resume {
|
||||
background: url('../img/progress-resume.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .state .buttons .resume {
|
||||
@@ -2877,6 +2879,7 @@ body .container .header .action-icons-small > button {
|
||||
body .container .header .action-icons > .pause,
|
||||
body .container .header .action-icons-small > .pause {
|
||||
background: url('../img/pause.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .pause,
|
||||
@@ -2893,6 +2896,7 @@ body .container .header .action-icons-small > .pause {
|
||||
body .container .header .action-icons > .pause.active,
|
||||
body .container .header .action-icons-small > .pause.active {
|
||||
background: url('../img/resume.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .pause.active,
|
||||
@@ -2909,6 +2913,7 @@ body .container .header .action-icons-small > .pause.active {
|
||||
body .container .header .action-icons > .throttle,
|
||||
body .container .header .action-icons-small > .throttle {
|
||||
background: url('../img/throttle.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .throttle,
|
||||
|
||||
@@ -2815,6 +2815,7 @@ body .container .header .state .buttons button {
|
||||
}
|
||||
body .container .header .state .buttons .stop {
|
||||
background: url('../img/progress-stop.png');
|
||||
background-size: 100%;
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
@@ -2830,6 +2831,7 @@ body .container .header .state .buttons .stop {
|
||||
}
|
||||
body .container .header .state .buttons .resume {
|
||||
background: url('../img/progress-resume.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .state .buttons .resume {
|
||||
@@ -2877,6 +2879,7 @@ body .container .header .action-icons-small > button {
|
||||
body .container .header .action-icons > .pause,
|
||||
body .container .header .action-icons-small > .pause {
|
||||
background: url('../img/pause.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .pause,
|
||||
@@ -2893,6 +2896,7 @@ body .container .header .action-icons-small > .pause {
|
||||
body .container .header .action-icons > .pause.active,
|
||||
body .container .header .action-icons-small > .pause.active {
|
||||
background: url('../img/resume.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .pause.active,
|
||||
@@ -2909,6 +2913,7 @@ body .container .header .action-icons-small > .pause.active {
|
||||
body .container .header .action-icons > .throttle,
|
||||
body .container .header .action-icons-small > .throttle {
|
||||
background: url('../img/throttle.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: 1.25), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 1.25dppx) {
|
||||
body .container .header .action-icons > .throttle,
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
</div>
|
||||
|
||||
<div class="input text">
|
||||
<label for="oss_bucket_name" translate>OSS Bucket Name</label>
|
||||
<input type="text" name="oss_bucket_name" id="oss_bucket_name" ng-model="$parent.oss_bucket_name" placeholder="{{'OSS Bucket Name' | translate}}"/>
|
||||
<label for="oss_bucket_name" translate>OSS Bucket name</label>
|
||||
<input type="text" name="oss_bucket_name" id="oss_bucket_name" ng-model="$parent.oss_bucket_name" placeholder="{{'OSS Bucket name' | translate}}"/>
|
||||
</div>
|
||||
|
||||
<div class="input text">
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
<input type="text" name="storj_shared_access" id="storj_shared_access" ng-model="$parent.storj_shared_access" placeholder="{{'Access grant' | translate}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="storj_bucket" translate>Bucket</label>
|
||||
<input type="text" name="storj_bucket" id="storj_bucket" ng-model="$parent.storj_bucket" placeholder="{{'Bucket for storing the backup' | translate}}" />
|
||||
<label for="storj_bucket" translate>Bucket name</label>
|
||||
<input type="text" name="storj_bucket" id="storj_bucket" ng-model="$parent.storj_bucket" placeholder="{{'Bucket name' | translate}}" />
|
||||
</div>
|
||||
<div class="input text">
|
||||
<label for="storj_folder" translate>Folder path</label>
|
||||
<input type="text" name="storj_folder" id="storj_folder" ng-model="$parent.storj_folder" placeholder="{{'Folder within the bucket for storing the backup' | translate}}" />
|
||||
<input type="text" name="storj_folder" id="storj_folder" ng-model="$parent.storj_folder" placeholder="{{'Folder in the bucket' | translate}}" />
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,15 @@ public class CaptchaService : ICaptchaProvider
|
||||
m_disableVisualCaptcha = settings.GetSettings().DisableVisualCaptcha;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of possible system fonts, ordered by preference
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, int> FontNamePreference = new string[] {
|
||||
"Arial", "Verdana", "FreeSans", "Tahoma", "Helvetica", "Times New Roman", "Courier New", "Andale Mono"
|
||||
}
|
||||
.Select((x, i) => new { Key = x, Value = i })
|
||||
.ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private class CaptchaEntry
|
||||
{
|
||||
public readonly string Answer;
|
||||
@@ -115,12 +124,10 @@ public class CaptchaService : ICaptchaProvider
|
||||
/// <summary>
|
||||
/// Approximate the size in pixels of text drawn at the given fontsize
|
||||
/// </summary>
|
||||
private static int ApproxTextWidth(string text, string fontFamily, float fontSize)
|
||||
{
|
||||
var font = SystemFonts.CreateFont(fontFamily, fontSize);
|
||||
var textSize = TextMeasurer.MeasureSize(text, new TextOptions(font) { KerningMode = KerningMode.Standard });
|
||||
return (int)textSize.Width;
|
||||
}
|
||||
/// <param name="text">The text to measure</param>
|
||||
/// <param name="font">The font to use</param>
|
||||
private static int ApproxTextWidth(string text, Font font)
|
||||
=> (int)TextMeasurer.MeasureSize(text, new TextOptions(font) { KerningMode = KerningMode.Standard }).Width;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random answer.
|
||||
@@ -149,8 +156,18 @@ public class CaptchaService : ICaptchaProvider
|
||||
/// <param name="fontsize">The size of the font used to create the captcha, in pixels.</param>
|
||||
public static Image<Rgba32> CreateCaptcha(string answer, Size size = default(Size), float fontsize = 40)
|
||||
{
|
||||
var fontfamily = "Arial";
|
||||
var text_width = ApproxTextWidth(answer, fontfamily, fontsize);
|
||||
var fontFamily = SystemFonts.Collection.Families.OrderBy(x =>
|
||||
{
|
||||
if (FontNamePreference.TryGetValue(x.Name, out var val))
|
||||
return val;
|
||||
return int.MaxValue;
|
||||
}).FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fontFamily.Name))
|
||||
throw new Exception("No usable font found");
|
||||
|
||||
var font = fontFamily.CreateFont(fontsize);
|
||||
var text_width = ApproxTextWidth(answer, font);
|
||||
if (size.Width == 0 || size.Height == 0)
|
||||
size = new Size((int)(text_width * 1.2), (int)(fontsize * 1.2));
|
||||
|
||||
@@ -160,7 +177,6 @@ public class CaptchaService : ICaptchaProvider
|
||||
var stray_y = size.Height / 4;
|
||||
var ans_stray_x = (int)fontsize / 3;
|
||||
var ans_stray_y = size.Height / 6;
|
||||
var font = SystemFonts.CreateFont(fontfamily, fontsize);
|
||||
|
||||
image.Mutate(ctx =>
|
||||
{
|
||||
@@ -169,7 +185,7 @@ public class CaptchaService : ICaptchaProvider
|
||||
// Apply a background string to make it hard to do OCR
|
||||
foreach (var color in new[] { Color.Yellow, Color.LightGreen, Color.GreenYellow })
|
||||
{
|
||||
var backgroundFont = SystemFonts.CreateFont(fontfamily, fontsize);
|
||||
var backgroundFont = font;
|
||||
ctx.DrawText(CreateRandomAnswer(minlength: answer.Length, maxlength: answer.Length), backgroundFont, color, new PointF(rnd.Next(-stray_x, stray_x), rnd.Next(-stray_y, stray_y)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user