mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-25 10:09:20 +03:00
Merge branch 'master' into plugin-hooks
# Conflicts: # dev/View/User/Login.js
This commit is contained in:
commit
2ab42b1f03
547 changed files with 29045 additions and 52993 deletions
33
README.md
33
README.md
|
|
@ -116,23 +116,23 @@ RainLoop 1.15 vs SnappyMail
|
|||
|
||||
|js/* |RainLoop |Snappy |
|
||||
|--------------- |--------: |--------: |
|
||||
|admin.js |2.158.025 | 100.608 |
|
||||
|app.js |4.215.733 | 495.934 |
|
||||
|boot.js | 672.433 | 4.726 |
|
||||
|libs.js | 647.679 | 227.974 |
|
||||
|admin.js |2.158.025 | 94.549 |
|
||||
|app.js |4.215.733 | 458.762 |
|
||||
|boot.js | 672.433 | 4.554 |
|
||||
|libs.js | 647.679 | 218.511 |
|
||||
|polyfills.js | 325.908 | 0 |
|
||||
|serviceworker.js | 0 | 285 |
|
||||
|TOTAL |8.019.778 | 829.355 |
|
||||
|TOTAL |8.019.778 | 776.661 |
|
||||
|
||||
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
||||
|admin.min.js | 255.514 | 50.607 | 73.899 | 14.944 | 60.674 | 13.364 |
|
||||
|app.min.js | 516.000 | 240.553 |140.430 | 70.291 |110.657 | 59.309 |
|
||||
|admin.min.js | 255.514 | 49.871 | 73.899 | 14.771 | 60.674 | 13.213 |
|
||||
|app.min.js | 516.000 | 235.474 |140.430 | 69.223 |110.657 | 58.398 |
|
||||
|boot.min.js | 66.456 | 2.442 | 22.553 | 1.371 | 20.043 | 1.178 |
|
||||
|libs.min.js | 574.626 | 115.877 |177.280 | 42.809 |151.855 | 38.099 |
|
||||
|libs.min.js | 574.626 | 106.574 |177.280 | 38.600 |151.855 | 34.532 |
|
||||
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|
||||
|TOTAL |1.445.204 | 409.479 |425.477 |129.415 |353.301 |111.950 |
|
||||
|TOTAL (no admin) |1.189.690 | 358.872 |351.061 |114.471 |292.627 | 98.586 |
|
||||
|TOTAL |1.445.204 | 394.361 |425.477 |123.965 |353.301 |107.321 |
|
||||
|TOTAL (no admin) |1.189.690 | 344.490 |351.061 |109.194 |292.627 | 94.108 |
|
||||
|
||||
For a user its around 66% smaller and faster than traditional RainLoop.
|
||||
|
||||
|
|
@ -166,12 +166,12 @@ For a user its around 66% smaller and faster than traditional RainLoop.
|
|||
|
||||
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
||||
|------------ |-------: |-------: |------: |------: |--------: |
|
||||
|app.css | 340.334 | 107.279 | 46,959 | 18.816 | 16.160 |
|
||||
|app.min.css | 274.791 | 88.402 | 39.618 | 16.831 | 14.827 |
|
||||
|app.css | 340.334 | 106.864 | 46,959 | 18.751 | 16.110 |
|
||||
|app.min.css | 274.791 | 88.077 | 39.618 | 16.788 | 14.787 |
|
||||
|boot.css | | 2.066 | | 913 | 742 |
|
||||
|boot.min.css | | 1.696 | | 818 | 664 |
|
||||
|admin.css | | 46.393 | | 9.237 | 8.073 |
|
||||
|admin.min.css | | 37.306 | | 8.191 | 7.282 |
|
||||
|admin.css | | 46.337 | | 9.221 | 8.058 |
|
||||
|admin.min.css | | 37.251 | | 8.178 | 7.269 |
|
||||
|
||||
|
||||
### Squire vs CKEditor
|
||||
|
|
@ -190,8 +190,3 @@ Still TODO:
|
|||
CKEditor including the 7 asset requests (css,language,plugins,icons) is 633.46 KB / 180.47 KB (gzip).
|
||||
|
||||
To use the old CKEditor, you must install the plugin.
|
||||
|
||||
### PHP73 branch
|
||||
|
||||
There's a RainLoop 1.14.0 branch with only the PHP 7.3 changes at
|
||||
https://github.com/the-djmaze/snappymail/tree/php73
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ import { AbstractApp } from 'App/Abstract';
|
|||
import { ComposePopupView } from 'View/Popup/Compose';
|
||||
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
||||
import { AskPopupView } from 'View/Popup/Ask';
|
||||
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
|
||||
|
||||
// Every 5 minutes
|
||||
const refreshFolders = 300000;
|
||||
|
|
@ -187,16 +186,18 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
MessageUserStore.listLoading(false);
|
||||
},
|
||||
FolderUserStore.currentFolderFullNameRaw(),
|
||||
iOffset,
|
||||
SettingsUserStore.messagesPerPage(),
|
||||
MessageUserStore.listSearch(),
|
||||
MessageUserStore.listThreadUid()
|
||||
{
|
||||
Folder: FolderUserStore.currentFolderFullNameRaw(),
|
||||
Offset: iOffset,
|
||||
Limit: SettingsUserStore.messagesPerPage(),
|
||||
Search: MessageUserStore.listSearch(),
|
||||
ThreadUid: MessageUserStore.listThreadUid()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
recacheInboxMessageList() {
|
||||
Remote.messageList(()=>{}, getFolderInboxName(), 0, SettingsUserStore.messagesPerPage(), '', '', true);
|
||||
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -910,18 +911,8 @@ class AppUser extends AbstractApp {
|
|||
|
||||
rl.setWindowTitle();
|
||||
if (SettingsGet('Auth')) {
|
||||
if (
|
||||
Settings.capa(Capa.TwoFactor) &&
|
||||
Settings.capa(Capa.TwoFactorForce) &&
|
||||
SettingsGet('RequireTwoFactor')
|
||||
) {
|
||||
this.bootend();
|
||||
showScreenPopup(TwoFactorConfigurationPopupView, [true]);
|
||||
} else {
|
||||
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
|
||||
|
||||
// require.ensure([], function() { // require code splitting
|
||||
|
||||
this.foldersReload(value => {
|
||||
try {
|
||||
this.bootend();
|
||||
|
|
@ -1038,8 +1029,6 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
});
|
||||
|
||||
// }); // require code splitting
|
||||
}
|
||||
} else {
|
||||
this.bootend();
|
||||
startScreens([LoginUserScreen]);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
* @enum {string}
|
||||
*/
|
||||
export const Capa = {
|
||||
TwoFactor: 'TWO_FACTOR',
|
||||
TwoFactorForce: 'TWO_FACTOR_FORCE',
|
||||
OpenPGP: 'OPEN_PGP',
|
||||
Prefetch: 'PREFETCH',
|
||||
Composer: 'COMPOSER',
|
||||
|
|
@ -86,9 +84,6 @@ export const Notification = {
|
|||
DomainNotAllowed: 109,
|
||||
AccountNotAllowed: 110,
|
||||
|
||||
AccountTwoFactorAuthRequired: 120,
|
||||
AccountTwoFactorAuthError: 121,
|
||||
|
||||
ContactsSyncError: 140,
|
||||
|
||||
CantGetMessageList: 201,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,22 @@ export const FolderType = {
|
|||
User: 99
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
export const FolderSortMode = {
|
||||
DateDesc: '', // default 'REVERSE DATE'
|
||||
DateAsc: 'DATE',
|
||||
FromDesc: 'REVERSE FROM',
|
||||
FromAsc: 'FROM',
|
||||
SizeDesc: 'REVERSE SIZE',
|
||||
SizeAsc: 'SIZE',
|
||||
SubjectDesc: 'REVERSE SUBJECT',
|
||||
SubjectAsc: 'SUBJECT'
|
||||
// ToDesc: 'REVERSE TO',
|
||||
// ToAsc: 'TO',
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -13,16 +13,15 @@ const
|
|||
exts = {
|
||||
eml: 'message/rfc822',
|
||||
mime: 'message/rfc822',
|
||||
rtx: 'text/richtext',
|
||||
vcard: 'text/vcard',
|
||||
vcf: 'text/vcard',
|
||||
htm: 'text/html',
|
||||
html: 'text/html',
|
||||
csv: 'text/csv',
|
||||
ics: 'text/calendar',
|
||||
ifb: 'text/calendar',
|
||||
xml: 'text/xml',
|
||||
json: app+'json',
|
||||
asc: app+'pgp-signature',
|
||||
p10: app+'pkcs10',
|
||||
p7c: app+'pkcs7-mime',
|
||||
p7m: app+'pkcs7-mime',
|
||||
|
|
@ -34,8 +33,7 @@ const
|
|||
pl: 'text/perl',
|
||||
css: 'text/css',
|
||||
asp: 'text/asp',
|
||||
php: app+'x-httpd-php',
|
||||
phtml: app+'x-httpd-php',
|
||||
php: app+'x-php',
|
||||
|
||||
// images
|
||||
jpg: 'image/jpeg',
|
||||
|
|
@ -106,18 +104,16 @@ const
|
|||
export const FileType = {
|
||||
Unknown: 'unknown',
|
||||
Text: 'text',
|
||||
Html: 'html',
|
||||
Code: 'code',
|
||||
Eml: 'eml',
|
||||
WordText: 'word-text',
|
||||
Word: 'word',
|
||||
Pdf: 'pdf',
|
||||
Image: 'image',
|
||||
Audio: 'audio',
|
||||
Video: 'video',
|
||||
Sheet: 'sheet',
|
||||
Spreadsheet: 'spreadsheet',
|
||||
Presentation: 'presentation',
|
||||
Certificate: 'certificate',
|
||||
CertificateBin: 'certificate-bin',
|
||||
Archive: 'archive'
|
||||
};
|
||||
|
||||
|
|
@ -138,7 +134,7 @@ export const FileInfo = {
|
|||
return app + 'ms-tnef';
|
||||
}
|
||||
let ext = fileName.split('.').pop();
|
||||
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf|asc)$/.test(ext))
|
||||
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf)$/.test(ext))
|
||||
return 'text/plain';
|
||||
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
|
||||
return 'video/mpeg';
|
||||
|
|
@ -186,15 +182,12 @@ export const FileInfo = {
|
|||
case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext:
|
||||
result = FileType.Video;
|
||||
break;
|
||||
case ['php', 'js', 'css'].includes(ext):
|
||||
case ['php', 'js', 'css', 'xml', 'html'].includes(ext) || 'text/html' == mimeType:
|
||||
result = FileType.Code;
|
||||
break;
|
||||
case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
|
||||
result = FileType.Eml;
|
||||
break;
|
||||
case 'text/html' == mimeType || 'html' == ext:
|
||||
result = FileType.Html;
|
||||
break;
|
||||
case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext:
|
||||
result = FileType.Text;
|
||||
break;
|
||||
|
|
@ -204,19 +197,17 @@ export const FileInfo = {
|
|||
case 'pdf' == type || 'pdf' == ext:
|
||||
result = FileType.Pdf;
|
||||
break;
|
||||
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) ||
|
||||
['asc', 'pem', 'ppk'].includes(ext):
|
||||
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType)
|
||||
|| ['asc', 'pem', 'ppk'].includes(ext)
|
||||
|| [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
|
||||
result = FileType.Certificate;
|
||||
break;
|
||||
case [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
|
||||
result = FileType.CertificateBin;
|
||||
break;
|
||||
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|
||||
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
|
||||
result = FileType.WordText;
|
||||
result = FileType.Word;
|
||||
break;
|
||||
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
|
||||
result = FileType.Sheet;
|
||||
result = FileType.Spreadsheet;
|
||||
break;
|
||||
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
|
||||
result = FileType.Presentation;
|
||||
|
|
@ -232,45 +223,23 @@ export const FileInfo = {
|
|||
* @returns {string}
|
||||
*/
|
||||
getTypeIconClass: fileType => {
|
||||
let result = ['icon-file', ''];
|
||||
let result = 'icon-file';
|
||||
switch (fileType) {
|
||||
case FileType.Text:
|
||||
case FileType.Eml:
|
||||
case FileType.WordText:
|
||||
result[0] += '-text';
|
||||
break;
|
||||
case FileType.Html:
|
||||
case FileType.Code:
|
||||
result[0] += '-code';
|
||||
break;
|
||||
case FileType.Image:
|
||||
result[0] += '-image';
|
||||
break;
|
||||
case FileType.Audio:
|
||||
result[0] += '-music';
|
||||
break;
|
||||
case FileType.Video:
|
||||
result[0] += '-movie';
|
||||
break;
|
||||
case FileType.Archive:
|
||||
result[0] += '-zip';
|
||||
break;
|
||||
case FileType.Certificate:
|
||||
case FileType.CertificateBin:
|
||||
result[0] += '-certificate';
|
||||
break;
|
||||
case FileType.Sheet:
|
||||
result[0] += '-excel';
|
||||
break;
|
||||
case FileType.Presentation:
|
||||
result[0] += '-chart-graph';
|
||||
break;
|
||||
case FileType.Pdf:
|
||||
result = ['icon-none', 'pdf'];
|
||||
break;
|
||||
// no default
|
||||
case FileType.Word:
|
||||
return result + '-text';
|
||||
case FileType.Code:
|
||||
case FileType.Image:
|
||||
case FileType.Audio:
|
||||
case FileType.Video:
|
||||
case FileType.Archive:
|
||||
case FileType.Certificate:
|
||||
case FileType.Spreadsheet:
|
||||
case FileType.Presentation:
|
||||
return result + '-' + fileType;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
|
@ -283,7 +252,7 @@ export const FileInfo = {
|
|||
getCombinedIconClass: data => {
|
||||
if (isNonEmptyArray(data)) {
|
||||
let icons = data
|
||||
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1])[0] : '')
|
||||
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1]) : '')
|
||||
.validUnique();
|
||||
|
||||
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
|
||||
|
|
@ -297,7 +266,7 @@ export const FileInfo = {
|
|||
friendlySize: bytes => {
|
||||
bytes = parseInt(bytes, 10) || 0;
|
||||
let i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + sizes[i];
|
||||
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -179,12 +179,13 @@ export function mailBox(folder, page = 1, search = '', threadUid = '') {
|
|||
}
|
||||
|
||||
if (1 < page) {
|
||||
result = result.replace(/[/]+$/, '') + '/p' + page;
|
||||
result = result.replace(/\/+$/, '') + '/p' + page;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
result = result.replace(/[/]+$/, '') + '/' + encodeURI(search);
|
||||
result = result.replace(/\/+$/, '') + '/' + encodeURI(search);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
|
|||
import { langLink } from 'Common/Links';
|
||||
import { doc, createElement } from 'Common/Globals';
|
||||
|
||||
let I18N_DATA = window.rainloopI18N || {};
|
||||
let I18N_DATA = window.snappymailI18N || {};
|
||||
|
||||
export const trigger = ko.observable(false);
|
||||
|
||||
|
|
@ -14,14 +14,16 @@ export const trigger = ko.observable(false);
|
|||
* @returns {string}
|
||||
*/
|
||||
export function i18n(key, valueList, defaulValue) {
|
||||
let result = I18N_DATA[key] || defaulValue || key;
|
||||
|
||||
let path = key.split('/');
|
||||
if (!I18N_DATA[path[0]] || !path[1]) {
|
||||
return defaulValue || key;
|
||||
}
|
||||
let result = I18N_DATA[path[0]][path[1]] || defaulValue || key;
|
||||
if (valueList) {
|
||||
Object.entries(valueList).forEach(([key, value]) => {
|
||||
result = result.replace('%' + key + '%', value);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ function getNotificationMessage(code) {
|
|||
let key = getKeyByValue(Notification, code);
|
||||
if (key) {
|
||||
key = i18nKey(key).replace('_NOTIFICATION', '_ERROR');
|
||||
return I18N_DATA['NOTIFICATIONS/' + key];
|
||||
return I18N_DATA.NOTIFICATIONS[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,13 +127,13 @@ export function reload(admin, language) {
|
|||
const script = createElement('script');
|
||||
script.onload = () => {
|
||||
// reload the data
|
||||
if (window.rainloopI18N) {
|
||||
I18N_DATA = window.rainloopI18N || {};
|
||||
if (window.snappymailI18N) {
|
||||
I18N_DATA = window.snappymailI18N;
|
||||
i18nToNodes(doc);
|
||||
dispatchEvent(new CustomEvent('reload-time'));
|
||||
trigger(!trigger());
|
||||
}
|
||||
window.rainloopI18N = null;
|
||||
window.snappymailI18N = null;
|
||||
script.remove();
|
||||
resolve();
|
||||
};
|
||||
|
|
@ -150,7 +152,7 @@ export function reload(admin, language) {
|
|||
*/
|
||||
export function convertLangName(language, isEng = false) {
|
||||
return i18n(
|
||||
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/LANG_' + language.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'),
|
||||
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
|
||||
null,
|
||||
language
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent {
|
|||
this.inline = !!params.inline;
|
||||
|
||||
this.labeled = undefined !== params.label;
|
||||
this.labelAnimated = !!params.labelAnimated;
|
||||
}
|
||||
|
||||
click() {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
|||
|
||||
import { SMAudio } from 'Common/Audio';
|
||||
|
||||
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
|
||||
|
||||
export class AttachmentModel extends AbstractModel {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
@ -92,27 +90,21 @@ export class AttachmentModel extends AbstractModel {
|
|||
* @returns {boolean}
|
||||
*/
|
||||
isText() {
|
||||
return (
|
||||
FileType.Text === this.fileType ||
|
||||
FileType.Eml === this.fileType ||
|
||||
FileType.Certificate === this.fileType ||
|
||||
FileType.Html === this.fileType ||
|
||||
FileType.Code === this.fileType
|
||||
);
|
||||
return FileType.Text === this.fileType || FileType.Eml === this.fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPdf() {
|
||||
return FileType.Pdf === this.fileType;
|
||||
pdfPreview() {
|
||||
return null != navigator.mimeTypes['application/pdf'] && FileType.Pdf === this.fileType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasPreview() {
|
||||
return this.isImage() || (this.isPdf() && bAllowPdfPreview) || this.isText();
|
||||
return this.isImage() || this.pdfPreview() || this.isText();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -155,13 +147,6 @@ export class AttachmentModel extends AbstractModel {
|
|||
return link ? 'background:url(' + link + ')' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
linkPreviewAsPlain() {
|
||||
return serverRequestRaw('ViewAsPlain', this.download);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
|
@ -169,11 +154,11 @@ export class AttachmentModel extends AbstractModel {
|
|||
let result = '';
|
||||
switch (true) {
|
||||
case this.isImage():
|
||||
case this.isPdf() && bAllowPdfPreview:
|
||||
case this.pdfPreview():
|
||||
result = this.linkPreview();
|
||||
break;
|
||||
case this.isText():
|
||||
result = this.linkPreviewAsPlain();
|
||||
result = serverRequestRaw('ViewAsPlain', this.download);
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
|
|
@ -181,18 +166,6 @@ export class AttachmentModel extends AbstractModel {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
generateTransferDownloadUrl() {
|
||||
let link = this.linkDownload();
|
||||
if ('http' !== link.substr(0, 4)) {
|
||||
link = location.protocol + '//' + location.host + location.pathname + link;
|
||||
}
|
||||
|
||||
return this.mimeType + ':' + this.fileName + ':' + link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AttachmentModel} attachment
|
||||
* @param {*} event
|
||||
|
|
@ -201,7 +174,11 @@ export class AttachmentModel extends AbstractModel {
|
|||
eventDragStart(attachment, event) {
|
||||
const localEvent = event.originalEvent || event;
|
||||
if (attachment && localEvent && localEvent.dataTransfer && localEvent.dataTransfer.setData) {
|
||||
localEvent.dataTransfer.setData('DownloadURL', this.generateTransferDownloadUrl());
|
||||
let link = this.linkDownload();
|
||||
if ('http' !== link.substr(0, 4)) {
|
||||
link = location.protocol + '//' + location.host + location.pathname + link;
|
||||
}
|
||||
localEvent.dataTransfer.setData('DownloadURL', this.mimeType + ':' + this.fileName + ':' + link);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -211,13 +188,6 @@ export class AttachmentModel extends AbstractModel {
|
|||
* @returns {string}
|
||||
*/
|
||||
iconClass() {
|
||||
return FileInfo.getTypeIconClass(this.fileType)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
iconText() {
|
||||
return FileInfo.getTypeIconClass(this.fileType)[1];
|
||||
return FileInfo.getTypeIconClass(this.fileType);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,13 +96,6 @@ export class ComposeAttachmentModel extends AbstractModel {
|
|||
* @returns {string}
|
||||
*/
|
||||
iconClass() {
|
||||
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
iconText() {
|
||||
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[1];
|
||||
return FileInfo.getIconClass(this.fileExt(), this.mimeType());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import * as Local from 'Storage/Client';
|
|||
|
||||
import { AppUserStore } from 'Stores/User/App';
|
||||
import { FolderUserStore } from 'Stores/User/Folder';
|
||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||
|
||||
import ko from 'ko';
|
||||
|
||||
|
|
@ -113,6 +114,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported));
|
||||
|
||||
FolderUserStore.folderListOptimized(!!this.Optimized);
|
||||
FolderUserStore.sortSupported(!!this.IsSortSupported);
|
||||
|
||||
let update = false;
|
||||
|
||||
|
|
@ -123,8 +125,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
SettingsGet('DraftFolder') +
|
||||
SettingsGet('SpamFolder') +
|
||||
SettingsGet('TrashFolder') +
|
||||
SettingsGet('ArchiveFolder') +
|
||||
SettingsGet('NullFolder'))
|
||||
SettingsGet('ArchiveFolder'))
|
||||
) {
|
||||
Settings.set('SentFolder', this.SystemFolders[ServerFolderType.SENT] || null);
|
||||
Settings.set('DraftFolder', this.SystemFolders[ServerFolderType.DRAFTS] || null);
|
||||
|
|
@ -147,8 +148,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
DraftFolder: FolderUserStore.draftFolder(),
|
||||
SpamFolder: FolderUserStore.spamFolder(),
|
||||
TrashFolder: FolderUserStore.trashFolder(),
|
||||
ArchiveFolder: FolderUserStore.archiveFolder(),
|
||||
NullFolder: 'NullFolder'
|
||||
ArchiveFolder: FolderUserStore.archiveFolder()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -301,7 +301,10 @@ export class FolderModel extends AbstractModel {
|
|||
|
||||
canBeDeleted: () => !folder.isSystemFolder() && !folder.subFolders.length,
|
||||
|
||||
canBeSubscribed: () => !folder.isSystemFolder() && folder.selectable && Settings.app('useImapSubscribe'),
|
||||
canBeSubscribed: () => !folder.isSystemFolder()
|
||||
&& SettingsUserStore.hideUnsubscribed()
|
||||
&& folder.selectable
|
||||
&& Settings.app('useImapSubscribe'),
|
||||
|
||||
canBeSelected: () => !folder.isSystemFolder() && folder.selectable,
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ export class MessageModel extends AbstractModel {
|
|||
this.addObservables({
|
||||
subject: '',
|
||||
size: 0,
|
||||
spamScore: 0,
|
||||
spamResult: '',
|
||||
isSpam: false,
|
||||
dateTimeStampInUTC: 0,
|
||||
priority: MessagePriority.Normal,
|
||||
|
||||
|
|
@ -116,6 +119,9 @@ export class MessageModel extends AbstractModel {
|
|||
this._reset();
|
||||
this.subject('');
|
||||
this.size(0);
|
||||
this.spamScore(0);
|
||||
this.spamResult('');
|
||||
this.isSpam(false);
|
||||
this.dateTimeStampInUTC(0);
|
||||
this.priority(MessagePriority.Normal);
|
||||
|
||||
|
|
@ -211,10 +217,10 @@ export class MessageModel extends AbstractModel {
|
|||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink = false
|
||||
* @param {boolean=} wrapWithLink
|
||||
* @returns {string}
|
||||
*/
|
||||
fromToLine(friendlyView, wrapWithLink = false) {
|
||||
fromToLine(friendlyView, wrapWithLink) {
|
||||
return this.from.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
|
|
@ -232,37 +238,37 @@ export class MessageModel extends AbstractModel {
|
|||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink = false
|
||||
* @param {boolean=} wrapWithLink
|
||||
* @returns {string}
|
||||
*/
|
||||
toToLine(friendlyView, wrapWithLink = false) {
|
||||
toToLine(friendlyView, wrapWithLink) {
|
||||
return this.to.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink = false
|
||||
* @param {boolean=} wrapWithLink
|
||||
* @returns {string}
|
||||
*/
|
||||
ccToLine(friendlyView, wrapWithLink = false) {
|
||||
ccToLine(friendlyView, wrapWithLink) {
|
||||
return this.cc.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink = false
|
||||
* @param {boolean=} wrapWithLink
|
||||
* @returns {string}
|
||||
*/
|
||||
bccToLine(friendlyView, wrapWithLink = false) {
|
||||
bccToLine(friendlyView, wrapWithLink) {
|
||||
return this.bcc.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} friendlyView
|
||||
* @param {boolean=} wrapWithLink = false
|
||||
* @param {boolean=} wrapWithLink
|
||||
* @returns {string}
|
||||
*/
|
||||
replyToToLine(friendlyView, wrapWithLink = false) {
|
||||
replyToToLine(friendlyView, wrapWithLink) {
|
||||
return this.replyTo.toString(friendlyView, wrapWithLink);
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +324,7 @@ export class MessageModel extends AbstractModel {
|
|||
* @param {boolean=} last = false
|
||||
* @returns {Array}
|
||||
*/
|
||||
replyEmails(excludeEmails, last = false) {
|
||||
replyEmails(excludeEmails, last) {
|
||||
const result = [],
|
||||
unic = undefined === excludeEmails ? {} : excludeEmails;
|
||||
|
||||
|
|
@ -339,7 +345,7 @@ export class MessageModel extends AbstractModel {
|
|||
* @param {boolean=} last = false
|
||||
* @returns {Array.<Array>}
|
||||
*/
|
||||
replyAllEmails(excludeEmails, last = false) {
|
||||
replyAllEmails(excludeEmails, last) {
|
||||
let data = [];
|
||||
const toResult = [],
|
||||
ccResult = [],
|
||||
|
|
@ -364,7 +370,7 @@ export class MessageModel extends AbstractModel {
|
|||
/**
|
||||
* @param {boolean=} print = false
|
||||
*/
|
||||
viewPopupMessage(print = false) {
|
||||
viewPopupMessage(print) {
|
||||
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
||||
ccLine = this.ccToLine(false),
|
||||
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
||||
|
|
@ -414,6 +420,9 @@ export class MessageModel extends AbstractModel {
|
|||
this.subject(message.subject());
|
||||
|
||||
this.size(message.size());
|
||||
this.spamScore(message.spamScore());
|
||||
this.spamResult(message.spamResult());
|
||||
this.isSpam(message.isSpam());
|
||||
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
|
||||
this.priority(message.priority());
|
||||
|
||||
|
|
|
|||
|
|
@ -55,76 +55,17 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
|||
* @param {string} sPassword
|
||||
* @param {boolean} bSignMe
|
||||
* @param {string=} sLanguage
|
||||
* @param {string=} sAdditionalCode
|
||||
* @param {boolean=} bAdditionalCodeSignMe
|
||||
*/
|
||||
login(fCallback, sEmail, sPassword, bSignMe, sLanguage, sAdditionalCode, bAdditionalCodeSignMe) {
|
||||
login(fCallback, sEmail, sPassword, bSignMe, sLanguage) {
|
||||
this.defaultRequest(fCallback, 'Login', {
|
||||
Email: sEmail,
|
||||
Login: '',
|
||||
Password: sPassword,
|
||||
Language: sLanguage || '',
|
||||
AdditionalCode: sAdditionalCode || '',
|
||||
AdditionalCodeSignMe: bAdditionalCodeSignMe ? 1 : 0,
|
||||
SignMe: bSignMe ? 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
getTwoFactor(fCallback) {
|
||||
this.defaultRequest(fCallback, 'GetTwoFactorInfo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
createTwoFactor(fCallback) {
|
||||
this.defaultRequest(fCallback, 'CreateTwoFactorSecret');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
clearTwoFactor(fCallback) {
|
||||
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
showTwoFactorSecret(fCallback) {
|
||||
this.defaultRequest(fCallback, 'ShowTwoFactorSecret');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {string} sCode
|
||||
*/
|
||||
testTwoFactor(fCallback, sCode) {
|
||||
this.defaultRequest(fCallback, 'TestTwoFactorInfo', {
|
||||
Code: sCode
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {boolean} bEnable
|
||||
*/
|
||||
enableTwoFactor(fCallback, bEnable) {
|
||||
this.defaultRequest(fCallback, 'EnableTwoFactor', {
|
||||
Enable: bEnable ? 1 : 0
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
clearTwoFactorInfo(fCallback) {
|
||||
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
*/
|
||||
|
|
@ -297,54 +238,44 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
|||
|
||||
/**
|
||||
* @param {Function} fCallback
|
||||
* @param {string} sFolderFullNameRaw
|
||||
* @param {number=} iOffset = 0
|
||||
* @param {number=} iLimit = 20
|
||||
* @param {string=} sSearch = ''
|
||||
* @param {string=} sThreadUid = ''
|
||||
* @param {object} params
|
||||
* @param {boolean=} bSilent = false
|
||||
*/
|
||||
messageList(fCallback, sFolderFullNameRaw, iOffset = 0, iLimit = 20, sSearch = '', sThreadUid = '', bSilent = false) {
|
||||
sFolderFullNameRaw = pString(sFolderFullNameRaw);
|
||||
|
||||
const folderHash = getFolderHash(sFolderFullNameRaw),
|
||||
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads(),
|
||||
messageList(fCallback, params, bSilent = false) {
|
||||
const
|
||||
sFolderFullNameRaw = pString(params.Folder),
|
||||
folderHash = getFolderHash(sFolderFullNameRaw),
|
||||
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
|
||||
inboxUidNext = getFolderInboxName() === sFolderFullNameRaw ? getFolderUidNext(sFolderFullNameRaw) : '';
|
||||
|
||||
let params = {}, sGetAdd = '';
|
||||
params.Folder = sFolderFullNameRaw;
|
||||
params.ThreadUid = useThreads ? params.ThreadUid : '';
|
||||
params = Object.assign({
|
||||
Folder: '',
|
||||
Offset: 0,
|
||||
Limit: SettingsUserStore.messagesPerPage(),
|
||||
Search: '',
|
||||
UidNext: inboxUidNext,
|
||||
UseThreads: useThreads,
|
||||
ThreadUid: '',
|
||||
Sort: FolderUserStore.sortMode()
|
||||
}, params);
|
||||
|
||||
if (folderHash && (!sSearch || !sSearch.includes('is:'))) {
|
||||
let sGetAdd = '';
|
||||
|
||||
if (folderHash && (!params.Search || !params.Search.includes('is:'))) {
|
||||
sGetAdd = 'MessageList/' +
|
||||
SUB_QUERY_PREFIX +
|
||||
'/' +
|
||||
urlsafeArray([
|
||||
sFolderFullNameRaw,
|
||||
iOffset,
|
||||
iLimit,
|
||||
sSearch,
|
||||
SettingsGet('ProjectHash'),
|
||||
folderHash,
|
||||
inboxUidNext,
|
||||
useThreads ? 1 : 0,
|
||||
useThreads ? sThreadUid : ''
|
||||
]);
|
||||
} else {
|
||||
params = {
|
||||
Folder: sFolderFullNameRaw,
|
||||
Offset: iOffset,
|
||||
Limit: iLimit,
|
||||
Search: sSearch,
|
||||
UidNext: inboxUidNext,
|
||||
UseThreads: useThreads ? 1 : 0,
|
||||
ThreadUid: useThreads ? sThreadUid : ''
|
||||
};
|
||||
urlsafeArray([SettingsGet('ProjectHash'),folderHash].concat(Object.values(params)));
|
||||
params = {};
|
||||
}
|
||||
|
||||
this.defaultRequest(
|
||||
fCallback,
|
||||
'MessageList',
|
||||
params,
|
||||
sSearch ? 300000 : 30000,
|
||||
30000,
|
||||
sGetAdd,
|
||||
bSilent ? [] : ['MessageList']
|
||||
);
|
||||
|
|
@ -562,7 +493,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
|||
* @param {Object} oData
|
||||
*/
|
||||
sendMessage(fCallback, oData) {
|
||||
this.defaultRequest(fCallback, 'SendMessage', oData, 300000);
|
||||
this.defaultRequest(fCallback, 'SendMessage', oData, 30000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export class MailBoxUserScreen extends AbstractScreen {
|
|||
|
||||
FolderUserStore.currentFolder(folder);
|
||||
|
||||
MessageUserStore.listPage(page);
|
||||
MessageUserStore.listPage(1 > page ? 1 : page);
|
||||
MessageUserStore.listSearch(search);
|
||||
MessageUserStore.listThreadUid(threadUid);
|
||||
|
||||
|
|
@ -119,23 +119,15 @@ export class MailBoxUserScreen extends AbstractScreen {
|
|||
*/
|
||||
routes() {
|
||||
const
|
||||
fNormS = (request, vals) => {
|
||||
if (request) {
|
||||
vals[0] = decodeURI(pString(vals[0]));
|
||||
vals[1] = pInt(vals[1]);
|
||||
} else {
|
||||
vals[0] = getFolderInboxName();
|
||||
vals[1] = 1;
|
||||
}
|
||||
return [vals[0], 1 > vals[1] ? 1 : vals[1], decodeURI(pString(vals[2]))];
|
||||
},
|
||||
fNormD = (request, vals) =>
|
||||
[decodeURI(request ? pString(vals[0]) : getFolderInboxName()), 1, decodeURI(pString(vals[1]))];
|
||||
folder = (request, vals) => request ? decodeURI(pString(vals[0])) : getFolderInboxName(),
|
||||
fNormS = (request, vals) => [folder(request, vals), request ? pInt(vals[1]) : 1, decodeURI(pString(vals[2]))];
|
||||
|
||||
return [
|
||||
[/^([^/]*)$/, { 'normalize_': fNormS }],
|
||||
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { 'normalize_': fNormD }],
|
||||
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { 'normalize_': fNormS }]
|
||||
[/^([^/]*)$/, { normalize_: fNormS }],
|
||||
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { normalize_: (request, vals) =>
|
||||
[folder(request, vals), 1, decodeURI(pString(vals[1]))]
|
||||
}],
|
||||
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { normalize_: fNormS }]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
|
|||
settingsAddViewModel(FiltersUserSettings, 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
|
||||
}
|
||||
|
||||
if (Settings.capa(Capa.AutoLogout) || Settings.capa(Capa.TwoFactor)) {
|
||||
if (Settings.capa(Capa.AutoLogout)) {
|
||||
settingsAddViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export class ContactsAdminSettings {
|
|||
addObservablesTo(this, {
|
||||
enableContacts: !!SettingsGet('ContactsEnable'),
|
||||
contactsSync: !!SettingsGet('ContactsSync'),
|
||||
contactsType: '',
|
||||
contactsType: SettingsGet('ContactsPdoType'),
|
||||
|
||||
pdoDsn: SettingsGet('ContactsPdoDsn'),
|
||||
pdoUser: SettingsGet('ContactsPdoUser'),
|
||||
|
|
@ -69,8 +69,6 @@ export class ContactsAdminSettings {
|
|||
})
|
||||
.extend({ notify: 'always' });
|
||||
|
||||
this.contactsType(SettingsGet('ContactsPdoType'));
|
||||
|
||||
addSubscribablesTo(this, {
|
||||
enableContacts: value =>
|
||||
Remote.saveAdminConfig(null, {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,11 @@ export class GeneralAdminSettings {
|
|||
|
||||
this.weakPassword = rl.app.weakPassword;
|
||||
|
||||
// fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
|
||||
/** https://github.com/RainLoop/rainloop-webmail/issues/1924
|
||||
if (this.weakPassword) {
|
||||
fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
|
||||
}
|
||||
*/
|
||||
|
||||
this.mainAttachmentLimit = ko
|
||||
.observable(pInt(SettingsGet('AttachmentLimit')) / (1024 * 1024))
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ export class SecurityAdminSettings {
|
|||
verifySslCertificate: !!SettingsGet('VerifySslCertificate'),
|
||||
allowSelfSigned: !!SettingsGet('AllowSelfSigned'),
|
||||
|
||||
isTwoFactorDropperShown: false,
|
||||
twoFactorDropperUser: '',
|
||||
twoFactorDropperUserFocused: false,
|
||||
|
||||
adminLogin: SettingsGet('AdminLogin'),
|
||||
adminLoginError: false,
|
||||
adminPassword: '',
|
||||
|
|
@ -30,9 +26,7 @@ export class SecurityAdminSettings {
|
|||
adminPasswordUpdateError: false,
|
||||
adminPasswordUpdateSuccess: false,
|
||||
|
||||
capaOpenPGP: Settings.capa(Capa.OpenPGP),
|
||||
capaTwoFactorAuth: Settings.capa(Capa.TwoFactor),
|
||||
capaTwoFactorAuthForce: Settings.capa(Capa.TwoFactorForce)
|
||||
capaOpenPGP: Settings.capa(Capa.OpenPGP)
|
||||
});
|
||||
|
||||
addSubscribablesTo(this, {
|
||||
|
|
@ -60,18 +54,6 @@ export class SecurityAdminSettings {
|
|||
CapaOpenPGP: value ? 1 : 0
|
||||
}),
|
||||
|
||||
capaTwoFactorAuth: value => {
|
||||
value || this.capaTwoFactorAuthForce(false);
|
||||
Remote.saveAdminConfig(null, {
|
||||
CapaTwoFactorAuth: value ? 1 : 0
|
||||
});
|
||||
},
|
||||
|
||||
capaTwoFactorAuthForce: value =>
|
||||
Remote.saveAdminConfig(null, {
|
||||
CapaTwoFactorAuthForce: value ? 1 : 0
|
||||
}),
|
||||
|
||||
useLocalProxyForExternalImages: value =>
|
||||
Remote.saveAdminConfig(null, {
|
||||
UseLocalProxyForExternalImages: value ? 1 : 0
|
||||
|
|
@ -120,15 +102,6 @@ export class SecurityAdminSettings {
|
|||
return true;
|
||||
}
|
||||
|
||||
showTwoFactorDropper() {
|
||||
this.twoFactorDropperUser('');
|
||||
this.isTwoFactorDropperShown(true);
|
||||
|
||||
setTimeout(() => {
|
||||
this.twoFactorDropperUserFocused(true);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
onNewAdminPasswordResponse(iError, data) {
|
||||
if (iError) {
|
||||
this.adminPasswordUpdateError(true);
|
||||
|
|
@ -147,9 +120,5 @@ export class SecurityAdminSettings {
|
|||
this.adminPassword('');
|
||||
this.adminPasswordNew('');
|
||||
this.adminPasswordNew2('');
|
||||
|
||||
this.isTwoFactorDropperShown(false);
|
||||
this.twoFactorDropperUser('');
|
||||
this.twoFactorDropperUserFocused(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import ko from 'ko';
|
|||
|
||||
import { Notification } from 'Common/Enums';
|
||||
import { ClientSideKeyName } from 'Common/EnumsUser';
|
||||
import { Settings } from 'Common/Globals';
|
||||
import { getNotification } from 'Common/Translator';
|
||||
|
||||
import { removeFolderFromCacheList } from 'Common/Cache';
|
||||
|
|
@ -9,6 +10,7 @@ import { removeFolderFromCacheList } from 'Common/Cache';
|
|||
import * as Local from 'Storage/Client';
|
||||
|
||||
import { FolderUserStore } from 'Stores/User/Folder';
|
||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
|
|
@ -23,6 +25,7 @@ export class FoldersUserSettings {
|
|||
this.folderList = FolderUserStore.folderList;
|
||||
this.folderListOptimized = FolderUserStore.folderListOptimized;
|
||||
this.folderListError = FolderUserStore.folderListError;
|
||||
this.hideUnsubscribed = SettingsUserStore.hideUnsubscribed;
|
||||
|
||||
this.loading = ko.computed(() => {
|
||||
const loading = FolderUserStore.foldersLoading(),
|
||||
|
|
@ -36,6 +39,9 @@ export class FoldersUserSettings {
|
|||
this.folderForDeletion = ko.observable(null).deleteAccessHelper();
|
||||
|
||||
this.folderForEdit = ko.observable(null).extend({ toggleSubscribeProperty: [this, 'edited'] });
|
||||
|
||||
this.useImapSubscribe = Settings.app('useImapSubscribe');
|
||||
this.hideUnsubscribed.subscribe(value => Remote.saveSetting('HideUnsubscribed', value ? 1 : 0));
|
||||
}
|
||||
|
||||
folderEditOnEnter(folder) {
|
||||
|
|
|
|||
|
|
@ -5,18 +5,13 @@ import { Capa, SaveSettingsStep } from 'Common/Enums';
|
|||
import { Settings } from 'Common/Globals';
|
||||
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
|
||||
|
||||
import { showScreenPopup } from 'Knoin/Knoin';
|
||||
|
||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
|
||||
|
||||
export class SecurityUserSettings {
|
||||
constructor() {
|
||||
this.capaAutoLogout = Settings.capa(Capa.AutoLogout);
|
||||
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
|
||||
|
||||
this.autoLogout = SettingsUserStore.autoLogout;
|
||||
this.autoLogoutTrigger = ko.observable(SaveSettingsStep.Idle);
|
||||
|
|
@ -43,8 +38,4 @@ export class SecurityUserSettings {
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
configureTwoFactor() {
|
||||
showScreenPopup(TwoFactorConfigurationPopupView);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { FolderType } from 'Common/EnumsUser';
|
||||
import { FolderType, FolderSortMode } from 'Common/EnumsUser';
|
||||
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||
import { addObservablesTo, addSubscribablesTo } from 'Common/Utils';
|
||||
import { folderListOptionsBuilder } from 'Common/UtilsUser';
|
||||
|
|
@ -19,6 +19,12 @@ export const FolderUserStore = new class {
|
|||
*/
|
||||
displaySpecSetting: false,
|
||||
|
||||
/**
|
||||
* If the IMAP server supports SORT
|
||||
*/
|
||||
sortSupported: false,
|
||||
// sortMode: '',
|
||||
|
||||
sentFolder: '',
|
||||
draftFolder: '',
|
||||
spamFolder: '',
|
||||
|
|
@ -36,6 +42,8 @@ export const FolderUserStore = new class {
|
|||
foldersInboxUnreadCount: 0
|
||||
});
|
||||
|
||||
this.sortMode = ko.observable('').extend({ limitedList: Object.values(FolderSortMode) });
|
||||
|
||||
this.namespace = '';
|
||||
|
||||
this.folderList = ko.observableArray();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export const SettingsUserStore = new class {
|
|||
allowDraftAutosave: !!SettingsGet('AllowDraftAutosave'),
|
||||
useThreads: !!SettingsGet('UseThreads'),
|
||||
replySameFolder: !!SettingsGet('ReplySameFolder'),
|
||||
hideUnsubscribed: !!SettingsGet('HideUnsubscribed'),
|
||||
|
||||
autoLogout: pInt(SettingsGet('AutoLogout'))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
@import "Filter.less";
|
||||
@import "Template.less";
|
||||
@import "OpenPgpKey.less";
|
||||
@import "TwoFactor.less";
|
||||
@import "Identity.less";
|
||||
@import "AdvancedSearch.less";
|
||||
@import "Attachmnets.less";
|
||||
|
|
|
|||
|
|
@ -29,11 +29,10 @@
|
|||
}
|
||||
|
||||
.checkboxAttachment {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 1px 4px 0 5px;
|
||||
bottom: 6px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
}
|
||||
|
||||
.attachmentSize {
|
||||
|
|
@ -104,16 +103,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.attachmentIconText {
|
||||
display: inline-block;
|
||||
font-size: 28px;
|
||||
width: 60px;
|
||||
height: 56px;
|
||||
color: #aaa;
|
||||
line-height: 56px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
.attachmentIconParent.hasPreview.isImage {
|
||||
.iconMain {
|
||||
|
|
|
|||
|
|
@ -18,14 +18,9 @@
|
|||
}
|
||||
|
||||
.attachmentAreaParent {
|
||||
padding: 10px 10px 6px 10px;
|
||||
background: #ddd;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.attachmentAreaParent {
|
||||
padding: 10px 10px 6px 10px;
|
||||
|
||||
.no-attachments-desc {
|
||||
padding: 50px 10px;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
}
|
||||
|
||||
.controls {
|
||||
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
|
||||
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword {
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ html.rl-no-preview-pane {
|
|||
}
|
||||
}
|
||||
|
||||
#sort-list-dropdown-id {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.messageList {
|
||||
|
||||
.toolbar {
|
||||
|
|
@ -254,9 +259,9 @@ html.rl-no-preview-pane {
|
|||
padding: 0 10px 0 5px;
|
||||
}
|
||||
|
||||
time {
|
||||
time, .sizeParent {
|
||||
margin: 0 5px;
|
||||
color: #999;
|
||||
opacity: 0.6;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -472,7 +477,7 @@ html .messageList .line-loading {
|
|||
line-height: 16px;
|
||||
margin-left: 30px;
|
||||
order: 1;
|
||||
width: calc(100% - 60px);
|
||||
width: calc(100% - 120px);
|
||||
}
|
||||
|
||||
.senderParent {
|
||||
|
|
@ -483,9 +488,13 @@ html .messageList .line-loading {
|
|||
order: 1;
|
||||
}
|
||||
|
||||
.attachmentParent {
|
||||
.sizeParent {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.attachmentParent {
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,15 +136,8 @@
|
|||
}
|
||||
|
||||
.informationFull {
|
||||
margin-top: 10px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 5px;
|
||||
padding: 8px 0;
|
||||
overflow: hidden;
|
||||
margin-top: 30px;
|
||||
|
||||
.size {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
|
@ -155,7 +148,7 @@
|
|||
}
|
||||
|
||||
td {
|
||||
padding: 0 10px;
|
||||
padding: 4px;
|
||||
vertical-align: top;
|
||||
min-width: 43px;
|
||||
}
|
||||
|
|
@ -163,17 +156,15 @@
|
|||
td:first-child {
|
||||
border-right: 1px solid #999;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
.uiLine {
|
||||
}
|
||||
|
||||
.uiLabel {
|
||||
.labelTo {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.uiLabelValue {
|
||||
td + td {
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
|
@ -273,14 +264,10 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
&:not(.selection-mode) {
|
||||
.checkboxAttachment {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.selection-mode {
|
||||
.checkboxAttachment {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.unselectedAttachmentsError {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
}
|
||||
|
||||
.list-table {
|
||||
margin-top: 40px;
|
||||
margin-top: 1em;
|
||||
|
||||
.folder-padding {
|
||||
display: inline-block;
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
.b-two-factor-content {
|
||||
|
||||
width: 750px;
|
||||
|
||||
.modal-body {
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
|
|
@ -64,8 +64,7 @@ class FolderSystemPopupView extends AbstractViewPopup {
|
|||
DraftFolder: FolderUserStore.draftFolder(),
|
||||
SpamFolder: FolderUserStore.spamFolder(),
|
||||
TrashFolder: FolderUserStore.trashFolder(),
|
||||
ArchiveFolder: FolderUserStore.archiveFolder(),
|
||||
NullFolder: 'NullFolder'
|
||||
ArchiveFolder: FolderUserStore.archiveFolder()
|
||||
});
|
||||
}).debounce(1000),
|
||||
fCallback = () => {
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
import { Capa } from 'Common/Enums';
|
||||
import { Settings } from 'Common/Globals';
|
||||
import { pString } from 'Common/Utils';
|
||||
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
|
||||
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
import { showScreenPopup } from 'Knoin/Knoin';
|
||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||
|
||||
import { TwoFactorTestPopupView } from 'View/Popup/TwoFactorTest';
|
||||
|
||||
class TwoFactorConfigurationPopupView extends AbstractViewPopup {
|
||||
constructor() {
|
||||
super('TwoFactorConfiguration');
|
||||
|
||||
this.addObservables({
|
||||
lock: false,
|
||||
|
||||
processing: false,
|
||||
clearing: false,
|
||||
secreting: false,
|
||||
|
||||
viewUser: '',
|
||||
twoFactorStatus: false,
|
||||
|
||||
twoFactorTested: false,
|
||||
|
||||
viewSecret: '',
|
||||
viewBackupCodes: '',
|
||||
viewUrlTitle: '',
|
||||
viewUrl: '',
|
||||
|
||||
viewEnable_: false
|
||||
});
|
||||
|
||||
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
|
||||
|
||||
const fn = iError => iError && this.viewEnable_(false);
|
||||
this.addComputables({
|
||||
viewEnable: {
|
||||
read: this.viewEnable_,
|
||||
write: (value) => {
|
||||
value = !!value;
|
||||
if (value && this.twoFactorTested()) {
|
||||
this.viewEnable_(value);
|
||||
Remote.enableTwoFactor(fn, value);
|
||||
} else {
|
||||
if (!value) {
|
||||
this.viewEnable_(value);
|
||||
}
|
||||
Remote.enableTwoFactor(fn, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
viewTwoFactorEnableTooltip: () => {
|
||||
translatorTrigger();
|
||||
return this.twoFactorTested() || this.viewEnable_()
|
||||
? ''
|
||||
: i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_TEST_BEFORE_DESC');
|
||||
},
|
||||
|
||||
viewTwoFactorStatus: () => {
|
||||
translatorTrigger();
|
||||
return i18n(
|
||||
this.twoFactorStatus()
|
||||
? 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_CONFIGURED_DESC'
|
||||
: 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC'
|
||||
);
|
||||
},
|
||||
|
||||
twoFactorAllowedEnable: () => this.viewEnable() || this.twoFactorTested()
|
||||
});
|
||||
|
||||
this.onResult = this.onResult.bind(this);
|
||||
this.onShowSecretResult = this.onShowSecretResult.bind(this);
|
||||
}
|
||||
|
||||
showSecret() {
|
||||
this.secreting(true);
|
||||
Remote.showTwoFactorSecret(this.onShowSecretResult);
|
||||
}
|
||||
|
||||
hideSecret() {
|
||||
this.viewSecret('');
|
||||
this.viewBackupCodes('');
|
||||
this.viewUrlTitle('');
|
||||
this.viewUrl('');
|
||||
}
|
||||
|
||||
createTwoFactor() {
|
||||
this.processing(true);
|
||||
Remote.createTwoFactor(this.onResult);
|
||||
}
|
||||
|
||||
logout() {
|
||||
rl.app.logout();
|
||||
}
|
||||
|
||||
testTwoFactor() {
|
||||
showScreenPopup(TwoFactorTestPopupView, [this.twoFactorTested]);
|
||||
}
|
||||
|
||||
clearTwoFactor() {
|
||||
this.viewSecret('');
|
||||
this.viewBackupCodes('');
|
||||
this.viewUrlTitle('');
|
||||
this.viewUrl('');
|
||||
|
||||
this.twoFactorTested(false);
|
||||
|
||||
this.clearing(true);
|
||||
Remote.clearTwoFactor(this.onResult);
|
||||
}
|
||||
|
||||
onShow(bLock) {
|
||||
this.lock(!!bLock);
|
||||
|
||||
this.viewSecret('');
|
||||
this.viewBackupCodes('');
|
||||
this.viewUrlTitle('');
|
||||
this.viewUrl('');
|
||||
}
|
||||
|
||||
onHide() {
|
||||
if (this.lock()) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
getQr() {
|
||||
return (
|
||||
'otpauth://totp/' +
|
||||
encodeURIComponent(this.viewUser()) +
|
||||
'?secret=' +
|
||||
encodeURIComponent(this.viewSecret()) +
|
||||
'&issuer=' +
|
||||
encodeURIComponent('')
|
||||
);
|
||||
}
|
||||
|
||||
onResult(iError, oData) {
|
||||
this.processing(false);
|
||||
this.clearing(false);
|
||||
|
||||
if (iError) {
|
||||
this.viewUser('');
|
||||
this.viewEnable_(false);
|
||||
this.twoFactorStatus(false);
|
||||
this.twoFactorTested(false);
|
||||
|
||||
this.viewSecret('');
|
||||
this.viewBackupCodes('');
|
||||
this.viewUrlTitle('');
|
||||
this.viewUrl('');
|
||||
} else {
|
||||
this.viewUser(pString(oData.Result.User));
|
||||
this.viewEnable_(!!oData.Result.Enable);
|
||||
this.twoFactorStatus(!!oData.Result.IsSet);
|
||||
this.twoFactorTested(!!oData.Result.Tested);
|
||||
|
||||
this.viewSecret(pString(oData.Result.Secret));
|
||||
this.viewBackupCodes(pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' '));
|
||||
|
||||
this.viewUrlTitle(pString(oData.Result.UrlTitle));
|
||||
this.viewUrl(qr.toDataURL({ level: 'M', size: 8, value: this.getQr() }));
|
||||
}
|
||||
}
|
||||
|
||||
onShowSecretResult(iError, data) {
|
||||
this.secreting(false);
|
||||
|
||||
if (iError) {
|
||||
this.viewSecret('');
|
||||
this.viewUrlTitle('');
|
||||
this.viewUrl('');
|
||||
} else {
|
||||
this.viewSecret(pString(data.Result.Secret));
|
||||
this.viewUrlTitle(pString(data.Result.UrlTitle));
|
||||
this.viewUrl(qr.toDataURL({ level: 'M', size: 6, value: this.getQr() }));
|
||||
}
|
||||
}
|
||||
|
||||
onBuild() {
|
||||
if (this.capaTwoFactor) {
|
||||
this.processing(true);
|
||||
Remote.getTwoFactor(this.onResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { TwoFactorConfigurationPopupView, TwoFactorConfigurationPopupView as default };
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
import { decorateKoCommands } from 'Knoin/Knoin';
|
||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||
|
||||
class TwoFactorTestPopupView extends AbstractViewPopup {
|
||||
constructor() {
|
||||
super('TwoFactorTest');
|
||||
|
||||
this.addObservables({
|
||||
code: '',
|
||||
codeStatus: null,
|
||||
|
||||
testing: false
|
||||
});
|
||||
|
||||
this.koTestedTrigger = null;
|
||||
|
||||
decorateKoCommands(this, {
|
||||
testCodeCommand: self => self.code() && !self.testing()
|
||||
});
|
||||
}
|
||||
|
||||
testCodeCommand() {
|
||||
this.testing(true);
|
||||
Remote.testTwoFactor(iError => {
|
||||
this.testing(false);
|
||||
this.codeStatus(!iError);
|
||||
|
||||
if (this.koTestedTrigger && this.codeStatus()) {
|
||||
this.koTestedTrigger(true);
|
||||
}
|
||||
}, this.code());
|
||||
}
|
||||
|
||||
clearPopup() {
|
||||
this.code('');
|
||||
this.codeStatus(null);
|
||||
this.testing(false);
|
||||
|
||||
this.koTestedTrigger = null;
|
||||
}
|
||||
|
||||
onShow(koTestedTrigger) {
|
||||
this.clearPopup();
|
||||
|
||||
this.koTestedTrigger = koTestedTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
export { TwoFactorTestPopupView, TwoFactorTestPopupView as default };
|
||||
|
|
@ -43,7 +43,6 @@ class LoginUserView extends AbstractViewCenter {
|
|||
email: SettingsGet('DevEmail'),
|
||||
password: SettingsGet('DevPassword'),
|
||||
signMe: false,
|
||||
additionalCode: '',
|
||||
|
||||
emailError: false,
|
||||
passwordError: false,
|
||||
|
|
@ -56,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
|
|||
|
||||
langRequest: false,
|
||||
|
||||
additionalCodeError: false,
|
||||
additionalCodeSignMe: false,
|
||||
additionalCodeVisibility: false,
|
||||
signMeType: LoginSignMeType.Unused
|
||||
});
|
||||
|
||||
|
|
@ -84,15 +80,10 @@ class LoginUserView extends AbstractViewCenter {
|
|||
this.addSubscribables({
|
||||
email: () => {
|
||||
this.emailError(false);
|
||||
this.additionalCode('');
|
||||
this.additionalCodeVisibility(false);
|
||||
},
|
||||
|
||||
password: () => this.passwordError(false),
|
||||
|
||||
additionalCode: () => this.additionalCodeError(false),
|
||||
additionalCodeVisibility: () => this.additionalCodeError(false),
|
||||
|
||||
submitError: value => value || this.submitErrorAddidional(''),
|
||||
|
||||
signMeType: iValue => this.signMe(LoginSignMeType.DefaultOn === iValue),
|
||||
|
|
@ -121,16 +112,13 @@ class LoginUserView extends AbstractViewCenter {
|
|||
submitCommand(self, event) {
|
||||
let email = this.email().trim(),
|
||||
valid = event.target.form.reportValidity() && email,
|
||||
pass = this.password(),
|
||||
totp = this.additionalCodeVisibility(),
|
||||
code = totp ? this.additionalCode() : '';
|
||||
pass = this.password();
|
||||
|
||||
this.emailError(!email);
|
||||
this.passwordError(!pass);
|
||||
this.additionalCodeError(totp && !code);
|
||||
|
||||
let pluginResultCode = 0, pluginResultMessage = '';
|
||||
Plugins.runHook('user-login-submit', [(iResultCode, sResultMessage) => {
|
||||
runHook('user-login-submit', [(iResultCode, sResultMessage) => {
|
||||
pluginResultCode = iResultCode;
|
||||
pluginResultMessage = sResultMessage;
|
||||
}]);
|
||||
|
|
@ -156,13 +144,6 @@ class LoginUserView extends AbstractViewCenter {
|
|||
}
|
||||
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
|
||||
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
|
||||
} else if (oData.TwoFactorAuth) {
|
||||
this.submitRequest(false);
|
||||
this.additionalCode('');
|
||||
this.additionalCodeVisibility(true);
|
||||
let input = this.querySelector('.inputAdditionalCode');
|
||||
input.required = true;
|
||||
setTimeout(() => input.focus(), 100);
|
||||
} else {
|
||||
rl.route.reload();
|
||||
}
|
||||
|
|
@ -170,9 +151,7 @@ class LoginUserView extends AbstractViewCenter {
|
|||
email,
|
||||
pass,
|
||||
!!this.signMe(),
|
||||
this.bSendLanguage ? this.language() : '',
|
||||
code,
|
||||
!!(totp && this.additionalCodeSignMe())
|
||||
this.bSendLanguage ? this.language() : ''
|
||||
);
|
||||
|
||||
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
|
||||
this.composeInEdit = AppUserStore.composeInEdit;
|
||||
|
||||
this.messageList = MessageUserStore.list;
|
||||
this.folderList = FolderUserStore.folderList;
|
||||
this.folderListSystem = FolderUserStore.folderListSystem;
|
||||
this.foldersChanging = FolderUserStore.foldersChanging;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
constructor() {
|
||||
super('User/MailBox/MessageList', 'MailMessageList');
|
||||
|
||||
this.sLastUid = null;
|
||||
this.bPrefetch = false;
|
||||
this.emptySubjectValue = '';
|
||||
|
||||
|
|
@ -69,43 +68,34 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
this.popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
|
||||
|
||||
this.message = MessageUserStore.message;
|
||||
this.messageList = MessageUserStore.list;
|
||||
this.messageListDisableAutoSelect = MessageUserStore.listDisableAutoSelect;
|
||||
|
||||
this.folderList = FolderUserStore.folderList;
|
||||
this.sortSupported = FolderUserStore.sortSupported;
|
||||
|
||||
this.composeInEdit = AppUserStore.composeInEdit;
|
||||
this.leftPanelDisabled = leftPanelDisabled;
|
||||
|
||||
this.selectorMessageSelected = MessageUserStore.selectorMessageSelected;
|
||||
this.selectorMessageFocused = MessageUserStore.selectorMessageFocused;
|
||||
this.isMessageSelected = MessageUserStore.isMessageSelected;
|
||||
this.messageListSearch = MessageUserStore.listSearch;
|
||||
this.messageListThreadUid = MessageUserStore.listThreadUid;
|
||||
this.messageListError = MessageUserStore.listError;
|
||||
this.folderMenuForMove = FolderUserStore.folderMenuForMove;
|
||||
|
||||
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
|
||||
|
||||
this.mainMessageListSearch = MessageUserStore.mainMessageListSearch;
|
||||
this.messageListEndFolder = MessageUserStore.listEndFolder;
|
||||
this.messageListEndThreadUid = MessageUserStore.listEndThreadUid;
|
||||
|
||||
this.messageListCheckedOrSelected = MessageUserStore.listCheckedOrSelected;
|
||||
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
|
||||
this.messageListCompleteLoadingThrottle = MessageUserStore.listCompleteLoading;
|
||||
this.messageListCompleteLoadingThrottleForAnimation = MessageUserStore.listLoadingAnimation;
|
||||
|
||||
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
|
||||
|
||||
this.userQuota = QuotaUserStore.quota;
|
||||
this.userUsageSize = QuotaUserStore.usage;
|
||||
this.userUsageProc = QuotaUserStore.percentage;
|
||||
|
||||
this.addObservables({
|
||||
moveDropdownTrigger: false,
|
||||
moreDropdownTrigger: false,
|
||||
sortDropdownTrigger: false,
|
||||
|
||||
dragOverArea: null,
|
||||
dragOverBodyArea: null,
|
||||
|
|
@ -137,7 +127,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
},
|
||||
|
||||
inputProxyMessageListSearch: {
|
||||
read: this.mainMessageListSearch,
|
||||
read: MessageUserStore.mainMessageListSearch,
|
||||
write: value => this.sLastSearchValue = value
|
||||
},
|
||||
|
||||
|
|
@ -146,19 +136,19 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
return c && MessageUserStore.list.length > c;
|
||||
},
|
||||
|
||||
hasMessages: () => 0 < this.messageList.length,
|
||||
hasMessages: () => 0 < MessageUserStore.list.length,
|
||||
|
||||
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === this.messageListEndFolder(),
|
||||
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||
|
||||
isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(),
|
||||
|
||||
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === this.messageListEndFolder(),
|
||||
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||
|
||||
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === this.messageListEndFolder(),
|
||||
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||
|
||||
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === this.messageListEndFolder(),
|
||||
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||
|
||||
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === this.messageListEndFolder(),
|
||||
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessageUserStore.listEndFolder(),
|
||||
|
||||
isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(),
|
||||
|
||||
|
|
@ -174,17 +164,28 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessageUserStore.listChecked().length : true,
|
||||
|
||||
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState()
|
||||
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState(),
|
||||
|
||||
sortText: () => {
|
||||
let mode = FolderUserStore.sortMode(),
|
||||
desc = '' === mode || mode.includes('REVERSE');
|
||||
mode = mode.split(/\s+/);
|
||||
if (mode.includes('FROM')) {
|
||||
return '@' + (desc ? '⬆' : '⬇');
|
||||
}
|
||||
if (mode.includes('SUBJECT')) {
|
||||
return '𝐒' + (desc ? '⬆' : '⬇');
|
||||
}
|
||||
return (mode.includes('SIZE') ? '✉' : '📅') + (desc ? '⬇' : '⬆');
|
||||
}
|
||||
});
|
||||
|
||||
this.hasCheckedOrSelectedLines = MessageUserStore.hasCheckedOrSelected,
|
||||
|
||||
this.quotaTooltip = this.quotaTooltip.bind(this);
|
||||
|
||||
this.selector = new Selector(
|
||||
this.messageList,
|
||||
this.selectorMessageSelected,
|
||||
this.selectorMessageFocused,
|
||||
MessageUserStore.list,
|
||||
MessageUserStore.selectorMessageSelected,
|
||||
MessageUserStore.selectorMessageFocused,
|
||||
'.messageListItem .actionHandle',
|
||||
'.messageListItem .checkboxMessage',
|
||||
'.messageListItem.focused'
|
||||
|
|
@ -209,7 +210,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
addEventListener('mailbox.message.show', e => {
|
||||
const sFolder = e.detail.Folder, sUid = e.detail.Uid;
|
||||
|
||||
const message = this.messageList.find(
|
||||
const message = MessageUserStore.list.find(
|
||||
item => item && sFolder === item.folder && sUid === item.uid
|
||||
);
|
||||
|
||||
|
|
@ -246,6 +247,11 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
});
|
||||
}
|
||||
|
||||
changeSort(self, event) {
|
||||
FolderUserStore.sortMode(event.target.closest('li').dataset.sort);
|
||||
this.reloadCommand();
|
||||
}
|
||||
|
||||
clearCommand() {
|
||||
if (Settings.capa(Capa.DangerousActions)) {
|
||||
showScreenPopup(FolderClearPopupView, [FolderUserStore.currentFolder()]);
|
||||
|
|
@ -375,7 +381,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
return false;
|
||||
});
|
||||
|
||||
if (!SettingsUserStore.usePreviewPane() && !this.message()) {
|
||||
if (!SettingsUserStore.usePreviewPane() && !MessageUserStore.message()) {
|
||||
this.selector.iFocusedNextHelper = up ? -1 : 1;
|
||||
} else {
|
||||
this.selector.iSelectNextHelper = up ? -1 : 1;
|
||||
|
|
@ -391,18 +397,18 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
useAutoSelect() {
|
||||
return !this.messageListDisableAutoSelect()
|
||||
&& !/is:unseen/.test(this.mainMessageListSearch())
|
||||
return !MessageUserStore.listDisableAutoSelect()
|
||||
&& !/is:unseen/.test(MessageUserStore.mainMessageListSearch())
|
||||
&& SettingsUserStore.usePreviewPane();
|
||||
}
|
||||
|
||||
searchEnterAction() {
|
||||
this.mainMessageListSearch(this.sLastSearchValue);
|
||||
MessageUserStore.mainMessageListSearch(this.sLastSearchValue);
|
||||
this.inputMessageListSearchFocus(false);
|
||||
}
|
||||
|
||||
cancelSearch() {
|
||||
this.mainMessageListSearch('');
|
||||
MessageUserStore.mainMessageListSearch('');
|
||||
this.inputMessageListSearchFocus(false);
|
||||
}
|
||||
|
||||
|
|
@ -571,7 +577,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
flagMessages(currentMessage) {
|
||||
const checked = this.messageListCheckedOrSelected();
|
||||
const checked = MessageUserStore.listCheckedOrSelected();
|
||||
if (currentMessage) {
|
||||
const checkedUids = checked.map(message => message.uid);
|
||||
if (checkedUids.includes(currentMessage.uid)) {
|
||||
|
|
@ -591,7 +597,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
flagMessagesFast(bFlag) {
|
||||
const checked = this.messageListCheckedOrSelected();
|
||||
const checked = MessageUserStore.listCheckedOrSelected();
|
||||
if (checked.length) {
|
||||
if (undefined === bFlag) {
|
||||
const flagged = checked.filter(message => message.isFlagged());
|
||||
|
|
@ -611,7 +617,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
seenMessagesFast(seen) {
|
||||
const checked = this.messageListCheckedOrSelected();
|
||||
const checked = MessageUserStore.listCheckedOrSelected();
|
||||
if (checked.length) {
|
||||
if (undefined === seen) {
|
||||
const unseen = checked.filter(message => message.isUnseen());
|
||||
|
|
@ -656,7 +662,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
!this.messageListSearchDesc() &&
|
||||
!this.messageListError() &&
|
||||
!this.messageListEndThreadUid() &&
|
||||
this.messageList.length &&
|
||||
MessageUserStore.list.length &&
|
||||
(this.isSpamFolder() || this.isTrashFolder())
|
||||
);
|
||||
}
|
||||
|
|
@ -700,7 +706,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
initShortcuts() {
|
||||
shortcuts.add('enter,open', '', Scope.MessageList, () => {
|
||||
if (this.message() && this.useAutoSelect()) {
|
||||
if (MessageUserStore.message() && this.useAutoSelect()) {
|
||||
dispatchEvent(new CustomEvent('mailbox.message-view.toggle-full-screen'));
|
||||
return false;
|
||||
}
|
||||
|
|
@ -756,9 +762,9 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
shortcuts.add('t', '', [Scope.MessageList], () => {
|
||||
let message = this.selectorMessageSelected();
|
||||
let message = MessageUserStore.selectorMessageSelected();
|
||||
if (!message) {
|
||||
message = this.selectorMessageFocused();
|
||||
message = MessageUserStore.selectorMessageFocused();
|
||||
}
|
||||
|
||||
if (message && 0 < message.threadsLen()) {
|
||||
|
|
@ -831,7 +837,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
return false;
|
||||
});
|
||||
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
|
||||
this.message() && AppUserStore.focusedState(Scope.MessageView);
|
||||
MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView);
|
||||
return false;
|
||||
});
|
||||
|
||||
|
|
@ -841,7 +847,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
prefetchNextTick() {
|
||||
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisible) {
|
||||
const message = this.messageList.find(
|
||||
const message = MessageUserStore.list.find(
|
||||
item => item && !hasRequestedMessage(item.folder, item.uid)
|
||||
);
|
||||
if (message) {
|
||||
|
|
@ -866,15 +872,15 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
advancedSearchClick() {
|
||||
Settings.capa(Capa.SearchAdv)
|
||||
&& showScreenPopup(AdvancedSearchPopupView, [this.mainMessageListSearch()]);
|
||||
&& showScreenPopup(AdvancedSearchPopupView, [MessageUserStore.mainMessageListSearch()]);
|
||||
}
|
||||
|
||||
quotaTooltip() {
|
||||
return i18n('MESSAGE_LIST/QUOTA_SIZE', {
|
||||
SIZE: FileInfo.friendlySize(this.userUsageSize()),
|
||||
PROC: this.userUsageProc(),
|
||||
LIMIT: FileInfo.friendlySize(this.userQuota())
|
||||
});
|
||||
SIZE: FileInfo.friendlySize(QuotaUserStore.usage()),
|
||||
PROC: QuotaUserStore.percentage(),
|
||||
LIMIT: FileInfo.friendlySize(QuotaUserStore.quota())
|
||||
}).replace(/<[^>]+>/g, '');
|
||||
}
|
||||
|
||||
initUploaderForAppend() {
|
||||
|
|
|
|||
|
|
@ -71,8 +71,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
moreDropdownTrigger: false
|
||||
});
|
||||
|
||||
this.pswp = null;
|
||||
|
||||
this.moveAction = moveAction;
|
||||
|
||||
this.allowComposer = Settings.capa(Capa.Composer);
|
||||
|
|
@ -84,7 +82,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
this.message = MessageUserStore.message;
|
||||
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
|
||||
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
|
||||
this.messageLoadingThrottle = MessageUserStore.messageLoading;
|
||||
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
|
||||
this.useThreads = SettingsUserStore.useThreads;
|
||||
|
|
@ -136,6 +133,8 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
viewReplyTo: '',
|
||||
viewTimeStamp: 0,
|
||||
viewSize: '',
|
||||
viewSpamScore: 0,
|
||||
viewSpamStatus: '',
|
||||
viewLineAsCss: '',
|
||||
viewViewLink: '',
|
||||
viewUnsubscribeLink: '',
|
||||
|
|
@ -220,13 +219,15 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
this.viewFromShort(message.fromToLine(true, true));
|
||||
this.viewFromDkimData(message.fromDkimData());
|
||||
this.viewToShort(message.toToLine(true, true));
|
||||
this.viewFrom(message.fromToLine(false));
|
||||
this.viewTo(message.toToLine(false));
|
||||
this.viewCc(message.ccToLine(false));
|
||||
this.viewBcc(message.bccToLine(false));
|
||||
this.viewReplyTo(message.replyToToLine(false));
|
||||
this.viewFrom(message.fromToLine());
|
||||
this.viewTo(message.toToLine());
|
||||
this.viewCc(message.ccToLine());
|
||||
this.viewBcc(message.bccToLine());
|
||||
this.viewReplyTo(message.replyToToLine());
|
||||
this.viewTimeStamp(message.dateTimeStampInUTC());
|
||||
this.viewSize(message.friendlySize());
|
||||
this.viewSpamScore(message.spamScore());
|
||||
this.viewSpamStatus(i18n(message.isSpam() ? 'GLOBAL/SPAM' : 'GLOBAL/NOT_SPAM') + ': ' + message.spamResult());
|
||||
this.viewLineAsCss(message.lineAsCss());
|
||||
this.viewViewLink(message.viewLink());
|
||||
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
|
||||
|
|
@ -717,7 +718,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
* @returns {string}
|
||||
*/
|
||||
printableCheckedMessageCount() {
|
||||
const cnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
|
||||
const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length;
|
||||
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"title": "SnappyMail",
|
||||
"description": "Simple, modern & fast web-based email client",
|
||||
"private": true,
|
||||
"version": "2.4.2",
|
||||
"version": "2.5.0-rc.4",
|
||||
"homepage": "https://snappymail.eu",
|
||||
"author": {
|
||||
"name": "DJ Maze",
|
||||
|
|
@ -42,11 +42,6 @@
|
|||
],
|
||||
"readmeFilename": "README.md",
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.8.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.8.3",
|
||||
"@babel/plugin-proposal-decorators": "7.8.3",
|
||||
"@babel/preset-env": "7.8.7",
|
||||
"@babel/runtime-corejs3": "^7.12.13",
|
||||
"babel-eslint": "10.1.0",
|
||||
"core-js": "^3.8.3",
|
||||
"eslint": "6.8.0",
|
||||
|
|
@ -75,7 +70,6 @@
|
|||
"dependencies": {
|
||||
"gulp-rollup-2": "^1.2.1",
|
||||
"rollup": "^2.38.0",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-external-globals": "^0.6.1",
|
||||
"rollup-plugin-html": "^0.2.1",
|
||||
"rollup-plugin-includepaths": "^0.2.4",
|
||||
|
|
|
|||
345
plugins/README.md
Normal file
345
plugins/README.md
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
|
||||
class Plugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
|
||||
# Hooks
|
||||
|
||||
$Plugin->addHook('hook.name', 'functionName');
|
||||
|
||||
## Login
|
||||
|
||||
### login.credentials.step-1
|
||||
params:
|
||||
string &$sEmail
|
||||
|
||||
### login.credentials.step-2
|
||||
params:
|
||||
string &$sEmail
|
||||
string &$sPassword
|
||||
|
||||
### login.credentials
|
||||
params:
|
||||
string &$sEmail
|
||||
string &$sLogin
|
||||
string &$sPassword
|
||||
|
||||
### login.success
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
## IMAP
|
||||
|
||||
### imap.credentials
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
array &$aCredentials
|
||||
|
||||
### imap.before-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\MailClient $oMailClient
|
||||
array $aCredentials
|
||||
|
||||
### imap.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\MailClient $oMailClient
|
||||
array $aCredentials
|
||||
|
||||
### imap.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\MailClient $oMailClient
|
||||
array $aCredentials
|
||||
|
||||
### imap.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\MailClient $oMailClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
|
||||
## Sieve
|
||||
|
||||
### sieve.credentials
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
array &$aCredentials
|
||||
|
||||
### sieve.before-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array $aCredentials
|
||||
|
||||
### sieve.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array $aCredentials
|
||||
|
||||
### sieve.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
|
||||
### sieve.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Sieve\ManageSieveClient $oSieveClient
|
||||
array $aCredentials
|
||||
|
||||
## SMTP
|
||||
|
||||
### smtp.credentials
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
array &$aCredentials
|
||||
|
||||
### smtp.before-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array $aCredentials
|
||||
|
||||
### smtp.after-connect
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array $aCredentials
|
||||
|
||||
### smtp.before-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
array $aCredentials
|
||||
|
||||
### smtp.after-login
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Smtp\SmtpClient $oSmtpClient
|
||||
bool $bSuccess
|
||||
array $aCredentials
|
||||
|
||||
## Folders
|
||||
|
||||
### filter.folders-post
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\FolderCollection $oFolderCollection
|
||||
|
||||
### filter.folders-complete
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mail\FolderCollection $oFolderCollection
|
||||
|
||||
### filter.folders-system-types
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
array &$aList
|
||||
|
||||
### filter.system-folders-names
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
array &$aCache
|
||||
|
||||
## Others
|
||||
|
||||
### filter.account
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
### filter.action-params
|
||||
params:
|
||||
string $sMethodName
|
||||
array &$aCurrentActionParams
|
||||
|
||||
### filter.app-data
|
||||
params:
|
||||
bool $bAdmin
|
||||
array &$aAppData
|
||||
|
||||
### filter.application-config
|
||||
params:
|
||||
\RainLoop\Config\Application $oConfig
|
||||
|
||||
### filter.build-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
|
||||
### filter.build-read-receipt-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
### filter.domain
|
||||
params:
|
||||
\RainLoop\Model\Domain &$oDomain
|
||||
|
||||
### filter.fabrica
|
||||
params:
|
||||
string $sName
|
||||
mixed &$mResult
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
### filter.http-paths
|
||||
params:
|
||||
array &$aPaths
|
||||
|
||||
### filter.http-query
|
||||
params:
|
||||
string &$sQuery
|
||||
|
||||
### filter.json-response
|
||||
params:
|
||||
string $sAction
|
||||
array &$aResponseItem
|
||||
|
||||
### filter.message-html'
|
||||
### filter.message-plain
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mime\Message $oMessage
|
||||
string &$sTextConverted
|
||||
|
||||
### filter.message-rcpt
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mime\EmailCollection $oRcpt
|
||||
|
||||
### filter.read-receipt-message-plain
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mime\Message $oMessage
|
||||
string &$sText
|
||||
|
||||
### filter.result-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
|
||||
### filter.save-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
|
||||
### filter.send-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
|
||||
### filter.send-message-stream
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
resource &$rMessageStream
|
||||
int &$iMessageStreamSize
|
||||
|
||||
### filter.send-read-receipt-message
|
||||
params:
|
||||
\MailSo\Mime\Message $oMessage
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
### filter.smtp-from
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mime\Message $oMessage
|
||||
string &$sFrom
|
||||
|
||||
### filter.smtp-hidden-rcpt
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
\MailSo\Mime\Message $oMessage
|
||||
array &$aHiddenRcpt
|
||||
|
||||
### filter.smtp-message-stream
|
||||
params:
|
||||
\RainLoop\Model\Account $oAccount
|
||||
resource &$rMessageStream
|
||||
int &$iMessageStreamSize
|
||||
|
||||
### filter.upload-response
|
||||
params:
|
||||
array &$aResponseItem
|
||||
|
||||
### json.action-post-call
|
||||
params:
|
||||
string $sAction
|
||||
array &$aResponseItem
|
||||
|
||||
### json.action-post-call
|
||||
params:
|
||||
string $sAction
|
||||
array &$aResponseItem
|
||||
|
||||
### json.action-pre-call
|
||||
params:
|
||||
string $sAction
|
||||
|
||||
### json.action-pre-call
|
||||
params:
|
||||
string $sAction
|
||||
|
||||
### json.suggestions-input-parameters
|
||||
params:
|
||||
string &$sQuery
|
||||
int &$iLimit
|
||||
\RainLoop\Model\Account $oAccount
|
||||
|
||||
### json.suggestions-post
|
||||
params:
|
||||
array &$aResult
|
||||
string $sQuery
|
||||
\RainLoop\Model\Account $oAccount
|
||||
int $iLimit
|
||||
|
||||
### json.suggestions-pre
|
||||
params:
|
||||
array &$aResult
|
||||
string $sQuery
|
||||
\RainLoop\Model\Account $oAccount
|
||||
int $iLimit
|
||||
|
||||
### main.default-response
|
||||
params:
|
||||
string $sActionName
|
||||
array &$aResponseItem
|
||||
|
||||
### main.default-response
|
||||
params:
|
||||
string $sActionName
|
||||
array &$aResponseItem
|
||||
|
||||
### main.default-response
|
||||
params:
|
||||
string $sActionName
|
||||
array &$aResponseItem
|
||||
|
||||
### main.default-response-data
|
||||
params:
|
||||
string $sActionName
|
||||
mixed &$mResult
|
||||
|
||||
### main.default-response-data
|
||||
params:
|
||||
string $sActionName
|
||||
mixed &$mResult
|
||||
|
||||
### main.default-response-data
|
||||
params:
|
||||
string $sActionName
|
||||
mixed &$mResult
|
||||
|
||||
### main.default-response-error-data
|
||||
params:
|
||||
string $sActionName
|
||||
int &$iErrorCode
|
||||
string &$sErrorMessage
|
||||
|
||||
### main.fabrica
|
||||
params:
|
||||
string $sName
|
||||
mixed &$mResult
|
||||
|
||||
### service.app-delay-start-begin
|
||||
no params
|
||||
|
||||
### service.app-delay-start-end
|
||||
no params
|
||||
|
|
@ -22,8 +22,8 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
||||
$this->addHook('filter.imap-credentials', 'FilterImapCredentials');
|
||||
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
|
||||
$this->addHook('imap.credentials', 'FilterImapCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.login-credentials', 'FilterLoginCredentials');
|
||||
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class ChangeSmtpEhloMessagePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
||||
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
213
plugins/ckeditor/ckeditor.less
Normal file
213
plugins/ckeditor/ckeditor.less
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*.cke_button__sourcedialog_label {
|
||||
display: none !important;
|
||||
}*/
|
||||
|
||||
.cke_chrome {
|
||||
border: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
.cke_toolgroup {
|
||||
padding-right: 0 !important;
|
||||
background: #FBFBFB !important;
|
||||
}
|
||||
|
||||
.cke_toolgroup, .cke_combo_button {
|
||||
border: 1px solid #A6A6A6 !important;
|
||||
}
|
||||
|
||||
.cke_top {
|
||||
padding: 2px;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #b6b6b6 !important;
|
||||
background: #F0F0F0 !important;
|
||||
}
|
||||
|
||||
.cke_combo_on a.cke_combo_button,
|
||||
.cke_combo_off a.cke_combo_button:hover,
|
||||
.cke_combo_off a.cke_combo_button:focus,
|
||||
.cke_combo_off a.cke_combo_button:active {
|
||||
padding: 1px !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.cke_toolgroup a.cke_button:last-child:after,
|
||||
.cke_toolgroup a.cke_button.cke_button_disabled:hover:last-child:after {
|
||||
border: none !important;
|
||||
height: 0 !important;
|
||||
width: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.cke_button_on {
|
||||
background: #ddd !important;
|
||||
}
|
||||
|
||||
.cke_combo {
|
||||
margin-top: 1px !important;
|
||||
}
|
||||
|
||||
.cke_combo__fontsize {
|
||||
margin-left: 3px !important;
|
||||
.cke_combo_text {
|
||||
width: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cke_combo_text {
|
||||
line-height: 24px !important;
|
||||
padding-left: 8px !important;
|
||||
}
|
||||
|
||||
.cke_combo_button {
|
||||
background: #FBFBFB !important;
|
||||
}
|
||||
|
||||
.cke_source {
|
||||
font-family: var(--fontMono) !important;
|
||||
padding: 10px !important;
|
||||
padding-right: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.cke_plain {
|
||||
|
||||
background-color: #fff;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
|
||||
padding: 10px;
|
||||
padding-right: 0;
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--fontMono), monospace;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
color: #333;
|
||||
resize: none;
|
||||
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cke_plain:focus, .cke_source:focus {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.b-compose .cke_chrome {
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.cke_wysiwyg_div {
|
||||
|
||||
padding: 10px !important;
|
||||
font-family: var(--fontSans);
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
color: #333;
|
||||
|
||||
ul {
|
||||
padding-left: 40px;
|
||||
li {
|
||||
list-style-type: disc !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding-left: 40px;
|
||||
li {
|
||||
list-style-type: decimal !important;
|
||||
}
|
||||
}
|
||||
|
||||
pre, code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-family: var(--fontMono);
|
||||
display: block;
|
||||
word-break: normal;
|
||||
word-wrap: break-word;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
code {
|
||||
display: inline;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border: 0;
|
||||
border-left: solid 2px #444;
|
||||
margin: 5px 0 5px 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
margin: 0 0 10px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
a {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
|
||||
&:visited {
|
||||
color: #609;
|
||||
}
|
||||
&:active {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cke_dialog {
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cke_dialog_ui_labeled_content {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.cke_dialog_ui_input_select, .cke_dialog_ui_input_text, .cke_dialog_ui_input_textarea {
|
||||
|
||||
box-shadow: none;
|
||||
border-radius: 2px;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
}
|
||||
|
||||
.cke_dialog_ui_input_select, .cke_dialog_ui_input_text {
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,9 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'CKEditor',
|
||||
VERSION = '2.0',
|
||||
RELEASE = '2021-03-25',
|
||||
REQUIRED = '2.4.2',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-01',
|
||||
REQUIRED = '2.5.0',
|
||||
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';
|
||||
|
||||
public function Init() : void
|
||||
|
|
@ -32,6 +32,7 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
}
|
||||
|
||||
if (\is_file("{$path}/ckeditor.js")) {
|
||||
$this->addCss('ckeditor.less');
|
||||
$this->addJs('ckeditor.js');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.login-credentials', 'FilterLoginСredentials');
|
||||
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -19,7 +19,7 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
*
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||
{
|
||||
// Your custom php logic
|
||||
// You may change login credentials
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.login-credentials', 'FilterLoginСredentials');
|
||||
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
*
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function FilterLoginСredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||
public function FilterLoginCredentials(&$sEmail, &$sLogin, &$sPassword)
|
||||
{
|
||||
$sMapping = \trim($this->Config()->Get('plugin', 'mapping', ''));
|
||||
if (!empty($sMapping))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.smtp-credentials', 'FilterSmtpCredentials');
|
||||
$this->addHook('smtp.credentials', 'FilterSmtpCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class ProxyauthLoginExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('event.login-post-login-provide', 'EventLoginPostLoginProvide');
|
||||
$this->addHook('login.success', 'EventLoginPostLoginProvide');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class WhiteListPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->addHook('filter.login-credentials', 'FilterLoginCredentials');
|
||||
$this->addHook('login.credentials', 'FilterLoginCredentials');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -30,16 +30,6 @@ if (\class_exists('RainLoop\Api'))
|
|||
{
|
||||
\MailSo\Base\Loader::Init();
|
||||
|
||||
if (!\function_exists('yaml_parse')) {
|
||||
function yaml_parse(string $input) {
|
||||
require_once RAINLOOP_APP_LIBRARIES_PATH.'spyc/Spyc.php';
|
||||
return \Spyc::YAMLLoadString(\str_replace(array(': >-', ': |-', ': |+'), array(': >', ': |', ': |'), $input));
|
||||
}
|
||||
function yaml_parse_file(string $filename) {
|
||||
return yaml_parse(\file_get_contents($filename));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API']))
|
||||
{
|
||||
if (!\defined('APP_API_STARTED'))
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Draw;
|
||||
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Interface for the drawer
|
||||
*/
|
||||
interface DrawerInterface
|
||||
{
|
||||
/**
|
||||
* Draws an arc on a starting at a given x, y coordinates under a given
|
||||
* start and end angles
|
||||
*
|
||||
* @param PointInterface $center
|
||||
* @param BoxInterface $size
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @param ColorInterface $color
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Same as arc, but also connects end points with a straight line
|
||||
*
|
||||
* @param PointInterface $center
|
||||
* @param BoxInterface $size
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @param ColorInterface $color
|
||||
* @param Boolean $fill
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Draws and ellipse with center at the given x, y coordinates, and given
|
||||
* width and height
|
||||
*
|
||||
* @param PointInterface $center
|
||||
* @param BoxInterface $size
|
||||
* @param ColorInterface $color
|
||||
* @param Boolean $fill
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Draws a line from start(x, y) to end(x, y) coordinates
|
||||
*
|
||||
* @param PointInterface $start
|
||||
* @param PointInterface $end
|
||||
* @param ColorInterface $outline
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $outline, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Same as arc, but connects end points and the center
|
||||
*
|
||||
* @param PointInterface $center
|
||||
* @param BoxInterface $size
|
||||
* @param integer $start
|
||||
* @param integer $end
|
||||
* @param ColorInterface $color
|
||||
* @param Boolean $fill
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Places a one pixel point at specific coordinates and fills it with
|
||||
* specified color
|
||||
*
|
||||
* @param PointInterface $position
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function dot(PointInterface $position, ColorInterface $color);
|
||||
|
||||
/**
|
||||
* Draws a polygon using array of x, y coordinates. Must contain at least
|
||||
* three coordinates
|
||||
*
|
||||
* @param array $coordinates
|
||||
* @param ColorInterface $color
|
||||
* @param Boolean $fill
|
||||
* @param integer $thickness
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1);
|
||||
|
||||
/**
|
||||
* Annotates image with specified text at a given position starting on the
|
||||
* top left of the final text box
|
||||
*
|
||||
* The rotation is done CW
|
||||
*
|
||||
* @param string $string
|
||||
* @param AbstractFont $font
|
||||
* @param PointInterface $position
|
||||
* @param integer $angle
|
||||
* @param integer $width
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* @return DrawerInterface
|
||||
*/
|
||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null);
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Effects;
|
||||
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Interface for the effects
|
||||
*/
|
||||
interface EffectsInterface
|
||||
{
|
||||
/**
|
||||
* Apply gamma correction
|
||||
*
|
||||
* @param float $correction
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function gamma($correction);
|
||||
|
||||
/**
|
||||
* Invert the colors of the image
|
||||
*
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function negative();
|
||||
|
||||
/**
|
||||
* Grayscale the image
|
||||
*
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function grayscale();
|
||||
|
||||
/**
|
||||
* Colorize the image
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function colorize(ColorInterface $color);
|
||||
|
||||
/**
|
||||
* Sharpens the image
|
||||
*
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function sharpen();
|
||||
|
||||
/**
|
||||
* Blur the image
|
||||
*
|
||||
* @param float|int $sigma
|
||||
*
|
||||
* @return EffectsInterface
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function blur($sigma);
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Exception;
|
||||
|
||||
/**
|
||||
* Imagine-specific exception
|
||||
*/
|
||||
interface Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Exception;
|
||||
|
||||
/**
|
||||
* Imagine-specific invalid argument exception
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Exception;
|
||||
|
||||
/**
|
||||
* Should be used when a driver does not support an operation.
|
||||
*/
|
||||
class NotSupportedException extends RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Exception;
|
||||
|
||||
/**
|
||||
* Imagine-specific out of bounds exception
|
||||
*/
|
||||
class OutOfBoundsException extends \OutOfBoundsException implements Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Exception;
|
||||
|
||||
/**
|
||||
* Imagine-specific runtime exception
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Advanced;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Point;
|
||||
|
||||
/**
|
||||
* A border filter
|
||||
*/
|
||||
class Border implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var ColorInterface
|
||||
*/
|
||||
private $color;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $width;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $height;
|
||||
|
||||
/**
|
||||
* Constructs Border filter with given color, width and height
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
* @param integer $width Width of the border on the left and right sides of the image
|
||||
* @param integer $height Height of the border on the top and bottom sides of the image
|
||||
*/
|
||||
public function __construct(ColorInterface $color, $width = 1, $height = 1)
|
||||
{
|
||||
$this->color = $color;
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
$size = $image->getSize();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$draw = $image->draw();
|
||||
|
||||
// Draw top and bottom lines
|
||||
$draw
|
||||
->line(
|
||||
new Point(0, 0),
|
||||
new Point($width - 1, 0),
|
||||
$this->color,
|
||||
$this->height
|
||||
)
|
||||
->line(
|
||||
new Point($width - 1, $height - 1),
|
||||
new Point(0, $height - 1),
|
||||
$this->color,
|
||||
$this->height
|
||||
)
|
||||
;
|
||||
|
||||
// Draw sides
|
||||
$draw
|
||||
->line(
|
||||
new Point(0, 0),
|
||||
new Point(0, $height - 1),
|
||||
$this->color,
|
||||
$this->width
|
||||
)
|
||||
->line(
|
||||
new Point($width - 1, 0),
|
||||
new Point($width - 1, $height - 1),
|
||||
$this->color,
|
||||
$this->width
|
||||
)
|
||||
;
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Advanced;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\ImagineInterface;
|
||||
|
||||
/**
|
||||
* A canvas filter
|
||||
*/
|
||||
class Canvas implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var BoxInterface
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var PointInterface
|
||||
*/
|
||||
private $placement;
|
||||
|
||||
/**
|
||||
* @var ColorInterface
|
||||
*/
|
||||
private $background;
|
||||
|
||||
/**
|
||||
* @var ImagineInterface
|
||||
*/
|
||||
private $imagine;
|
||||
|
||||
/**
|
||||
* Constructs Canvas filter with given width and height and the placement of the current image
|
||||
* inside the new canvas
|
||||
*
|
||||
* @param ImagineInterface $imagine
|
||||
* @param BoxInterface $size
|
||||
* @param PointInterface $placement
|
||||
* @param ColorInterface $background
|
||||
*/
|
||||
public function __construct(ImagineInterface $imagine, BoxInterface $size, PointInterface $placement = null, ColorInterface $background = null)
|
||||
{
|
||||
$this->imagine = $imagine;
|
||||
$this->size = $size;
|
||||
$this->placement = $placement ?: new Point(0, 0);
|
||||
$this->background = $background;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
$canvas = $this->imagine->create($this->size, $this->background);
|
||||
$canvas->paste($image, $this->placement);
|
||||
|
||||
return $canvas;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Advanced;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Point;
|
||||
|
||||
/**
|
||||
* The Grayscale filter calculates the gray-value based on RGB.
|
||||
*/
|
||||
class Grayscale extends OnPixelBased implements FilterInterface
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(function (ImageInterface $image, Point $point) {
|
||||
$color = $image->getColorAt($point);
|
||||
$image->draw()->dot($point, $color->grayscale());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Advanced;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Point;
|
||||
|
||||
/**
|
||||
* The OnPixelBased takes a callable, and for each pixel, this callable is called with the
|
||||
* image (\Imagine\Image\ImageInterface) and the current point (\Imagine\Image\Point)
|
||||
*/
|
||||
class OnPixelBased implements FilterInterface
|
||||
{
|
||||
protected $callback;
|
||||
|
||||
public function __construct($callback)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new InvalidArgumentException('$callback has to be callable');
|
||||
}
|
||||
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies scheduled transformation to ImageInterface instance
|
||||
* Returns processed ImageInterface instance
|
||||
*
|
||||
* @param ImageInterface $image
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
$w = $image->getSize()->getWidth();
|
||||
$h = $image->getSize()->getHeight();
|
||||
|
||||
for ($x = 0; $x < $w; $x++) {
|
||||
for ($y = 0; $y < $h; $y++) {
|
||||
call_user_func($this->callback, $image, new Point($x, $y));
|
||||
}
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Advanced;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* The RelativeResize filter allows images to be resized relative to their
|
||||
* existing dimensions.
|
||||
*/
|
||||
class RelativeResize implements FilterInterface
|
||||
{
|
||||
private $method;
|
||||
private $parameter;
|
||||
|
||||
/**
|
||||
* Constructs a RelativeResize filter with the given method and argument.
|
||||
*
|
||||
* @param string $method BoxInterface method
|
||||
* @param mixed $parameter Parameter for BoxInterface method
|
||||
*/
|
||||
public function __construct($method, $parameter)
|
||||
{
|
||||
if (!in_array($method, array('heighten', 'increase', 'scale', 'widen'))) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported method: ', $method));
|
||||
}
|
||||
|
||||
$this->method = $method;
|
||||
$this->parameter = $parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->resize(call_user_func(array($image->getSize(), $this->method), $this->parameter));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* An apply mask filter
|
||||
*/
|
||||
class ApplyMask implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var ImageInterface
|
||||
*/
|
||||
private $mask;
|
||||
|
||||
/**
|
||||
* @param ImageInterface $mask
|
||||
*/
|
||||
public function __construct(ImageInterface $mask)
|
||||
{
|
||||
$this->mask = $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->applyMask($this->mask);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Rotates an image automatically based on exif information.
|
||||
*
|
||||
* Your attention please: This filter requires the use of the
|
||||
* ExifMetadataReader to work.
|
||||
*
|
||||
* @see https://imagine.readthedocs.org/en/latest/usage/metadata.html
|
||||
*/
|
||||
class Autorotate implements FilterInterface
|
||||
{
|
||||
private $color;
|
||||
|
||||
/**
|
||||
* @param string|array|ColorInterface $color A color
|
||||
*/
|
||||
public function __construct($color = '000000')
|
||||
{
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
$metadata = $image->metadata();
|
||||
|
||||
switch (isset($metadata['ifd0.Orientation']) ? $metadata['ifd0.Orientation'] : null) {
|
||||
case 1: // top-left
|
||||
break;
|
||||
case 2: // top-right
|
||||
$image->flipHorizontally();
|
||||
break;
|
||||
case 3: // bottom-right
|
||||
$image->rotate(180, $this->getColor($image));
|
||||
break;
|
||||
case 4: // bottom-left
|
||||
$image->flipHorizontally();
|
||||
$image->rotate(180, $this->getColor($image));
|
||||
break;
|
||||
case 5: // left-top
|
||||
$image->flipHorizontally();
|
||||
$image->rotate(-90, $this->getColor($image));
|
||||
break;
|
||||
case 6: // right-top
|
||||
$image->rotate(90, $this->getColor($image));
|
||||
break;
|
||||
case 7: // right-bottom
|
||||
$image->flipHorizontally();
|
||||
$image->rotate(90, $this->getColor($image));
|
||||
break;
|
||||
case 8: // left-bottom
|
||||
$image->rotate(-90, $this->getColor($image));
|
||||
break;
|
||||
default: // Invalid orientation
|
||||
break;
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function getColor(ImageInterface $image)
|
||||
{
|
||||
if ($this->color instanceof ColorInterface) {
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
return $image->palette()->color($this->color);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* A copy filter
|
||||
*/
|
||||
class Copy implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->copy();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A crop filter
|
||||
*/
|
||||
class Crop implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var PointInterface
|
||||
*/
|
||||
private $start;
|
||||
|
||||
/**
|
||||
* @var BoxInterface
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* Constructs a Crop filter with given x, y, coordinates and crop width and
|
||||
* height values
|
||||
*
|
||||
* @param PointInterface $start
|
||||
* @param BoxInterface $size
|
||||
*/
|
||||
public function __construct(PointInterface $start, BoxInterface $size)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->crop($this->start, $this->size);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\Fill\FillInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* A fill filter
|
||||
*/
|
||||
class Fill implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var FillInterface
|
||||
*/
|
||||
private $fill;
|
||||
|
||||
/**
|
||||
* @param FillInterface $fill
|
||||
*/
|
||||
public function __construct(FillInterface $fill)
|
||||
{
|
||||
$this->fill = $fill;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->fill($this->fill);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A "flip horizontally" filter
|
||||
*/
|
||||
class FlipHorizontally implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->flipHorizontally();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A "flip vertically" filter
|
||||
*/
|
||||
class FlipVertically implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->flipVertically();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A paste filter
|
||||
*/
|
||||
class Paste implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var ImageInterface
|
||||
*/
|
||||
private $image;
|
||||
|
||||
/**
|
||||
* @var PointInterface
|
||||
*/
|
||||
private $start;
|
||||
|
||||
/**
|
||||
* Constructs a Paste filter with given ImageInterface to paste and x, y
|
||||
* coordinates of target position
|
||||
*
|
||||
* @param ImageInterface $image
|
||||
* @param PointInterface $start
|
||||
*/
|
||||
public function __construct(ImageInterface $image, PointInterface $start)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->paste($this->image, $this->start);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Filter\FilterInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
|
||||
/**
|
||||
* A resize filter
|
||||
*/
|
||||
class Resize implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var BoxInterface
|
||||
*/
|
||||
private $size;
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* Constructs Resize filter with given width and height
|
||||
*
|
||||
* @param BoxInterface $size
|
||||
* @param string $filter
|
||||
*/
|
||||
public function __construct(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
$this->size = $size;
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->resize($this->size, $this->filter);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A rotate filter
|
||||
*/
|
||||
class Rotate implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $angle;
|
||||
|
||||
/**
|
||||
* @var ColorInterface
|
||||
*/
|
||||
private $background;
|
||||
|
||||
/**
|
||||
* Constructs Rotate filter with given angle and background color
|
||||
*
|
||||
* @param integer $angle
|
||||
* @param ColorInterface $background
|
||||
*/
|
||||
public function __construct($angle, ColorInterface $background = null)
|
||||
{
|
||||
$this->angle = $angle;
|
||||
$this->background = $background;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->rotate($this->angle, $this->background);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A save filter
|
||||
*/
|
||||
class Save implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Constructs Save filter with given path and options
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($path = null, array $options = array())
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->save($this->path, $this->options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A show filter
|
||||
*/
|
||||
class Show implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Constructs the Show filter with given format and options
|
||||
*
|
||||
* @param string $format
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($format, array $options = array())
|
||||
{
|
||||
$this->format = $format;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->show($this->format, $this->options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A strip filter
|
||||
*/
|
||||
class Strip implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->strip();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A thumbnail filter
|
||||
*/
|
||||
class Thumbnail implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* @var BoxInterface
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $filter;
|
||||
|
||||
/**
|
||||
* Constructs the Thumbnail filter with given width, height and mode
|
||||
*
|
||||
* @param BoxInterface $size
|
||||
* @param string $mode
|
||||
* @param string $filter
|
||||
*/
|
||||
public function __construct(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
$this->size = $size;
|
||||
$this->mode = $mode;
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return $image->thumbnail($this->size, $this->mode, $this->filter);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter\Basic;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Palette\RGB;
|
||||
use Imagine\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A filter to render web-optimized images
|
||||
*/
|
||||
class WebOptimization implements FilterInterface
|
||||
{
|
||||
private $palette;
|
||||
private $path;
|
||||
private $options;
|
||||
|
||||
public function __construct($path = null, array $options = array())
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->options = array_replace(array(
|
||||
'resolution-units' => ImageInterface::RESOLUTION_PIXELSPERINCH,
|
||||
'resolution-y' => 72,
|
||||
'resolution-x' => 72,
|
||||
), $options);
|
||||
$this->palette = new RGB();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
$image
|
||||
->usePalette($this->palette)
|
||||
->strip();
|
||||
|
||||
if (is_callable($this->path)) {
|
||||
$path = call_user_func($this->path, $image);
|
||||
} elseif (null !== $this->path) {
|
||||
$path = $this->path;
|
||||
} else {
|
||||
return $image;
|
||||
}
|
||||
|
||||
return $image->save($path, $this->options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter;
|
||||
|
||||
use Imagine\Image\ImageInterface;
|
||||
|
||||
/**
|
||||
* Interface for imagine filters
|
||||
*/
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* Applies scheduled transformation to ImageInterface instance
|
||||
* Returns processed ImageInterface instance
|
||||
*
|
||||
* @param ImageInterface $image
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function apply(ImageInterface $image);
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Image\ImagineInterface;
|
||||
|
||||
/**
|
||||
* ImagineAware base class
|
||||
*/
|
||||
abstract class ImagineAware implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* An ImagineInterface instance.
|
||||
*
|
||||
* @var ImagineInterface
|
||||
*/
|
||||
private $imagine;
|
||||
|
||||
/**
|
||||
* Set ImagineInterface instance.
|
||||
*
|
||||
* @param ImagineInterface $imagine An ImagineInterface instance
|
||||
*/
|
||||
public function setImagine(ImagineInterface $imagine)
|
||||
{
|
||||
$this->imagine = $imagine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ImagineInterface instance.
|
||||
*
|
||||
* @return ImagineInterface
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function getImagine()
|
||||
{
|
||||
if (!$this->imagine instanceof ImagineInterface) {
|
||||
throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to filter constructor', get_class($this)));
|
||||
}
|
||||
|
||||
return $this->imagine;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Filter;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Filter\Basic\ApplyMask;
|
||||
use Imagine\Filter\Basic\Copy;
|
||||
use Imagine\Filter\Basic\Crop;
|
||||
use Imagine\Filter\Basic\Fill;
|
||||
use Imagine\Filter\Basic\FlipVertically;
|
||||
use Imagine\Filter\Basic\FlipHorizontally;
|
||||
use Imagine\Filter\Basic\Paste;
|
||||
use Imagine\Filter\Basic\Resize;
|
||||
use Imagine\Filter\Basic\Rotate;
|
||||
use Imagine\Filter\Basic\Save;
|
||||
use Imagine\Filter\Basic\Show;
|
||||
use Imagine\Filter\Basic\Strip;
|
||||
use Imagine\Filter\Basic\Thumbnail;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\ImagineInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Fill\FillInterface;
|
||||
use Imagine\Image\ManipulatorInterface;
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* A transformation filter
|
||||
*/
|
||||
final class Transformation implements FilterInterface, ManipulatorInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $filters = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $sorted;
|
||||
|
||||
/**
|
||||
* An ImagineInterface instance.
|
||||
*
|
||||
* @var ImagineInterface
|
||||
*/
|
||||
private $imagine;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param ImagineInterface $imagine An ImagineInterface instance
|
||||
*/
|
||||
public function __construct(ImagineInterface $imagine = null)
|
||||
{
|
||||
$this->imagine = $imagine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a given FilterInterface onto given ImageInterface and returns
|
||||
* modified ImageInterface
|
||||
*
|
||||
* @param ImageInterface $image
|
||||
* @param FilterInterface $filter
|
||||
*
|
||||
* @return ImageInterface
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function applyFilter(ImageInterface $image, FilterInterface $filter)
|
||||
{
|
||||
if ($filter instanceof ImagineAware) {
|
||||
if ($this->imagine === null) {
|
||||
throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to Transformation constructor', get_class($filter)));
|
||||
}
|
||||
$filter->setImagine($this->imagine);
|
||||
}
|
||||
|
||||
return $filter->apply($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of filters sorted by their priority. Filters with same priority will be returned in the order they were added.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
if (null === $this->sorted) {
|
||||
if (!empty($this->filters)) {
|
||||
ksort($this->filters);
|
||||
$this->sorted = call_user_func_array('array_merge', $this->filters);
|
||||
} else {
|
||||
$this->sorted = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function apply(ImageInterface $image)
|
||||
{
|
||||
return array_reduce(
|
||||
$this->getFilters(),
|
||||
array($this, 'applyFilter'),
|
||||
$image
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return $this->add(new Copy());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function crop(PointInterface $start, BoxInterface $size)
|
||||
{
|
||||
return $this->add(new Crop($start, $size));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flipHorizontally()
|
||||
{
|
||||
return $this->add(new FlipHorizontally());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flipVertically()
|
||||
{
|
||||
return $this->add(new FlipVertically());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function strip()
|
||||
{
|
||||
return $this->add(new Strip());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function paste(ImageInterface $image, PointInterface $start)
|
||||
{
|
||||
return $this->add(new Paste($image, $start));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function applyMask(ImageInterface $mask)
|
||||
{
|
||||
return $this->add(new ApplyMask($mask));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fill(FillInterface $fill)
|
||||
{
|
||||
return $this->add(new Fill($fill));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
return $this->add(new Resize($size, $filter));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rotate($angle, ColorInterface $background = null)
|
||||
{
|
||||
return $this->add(new Rotate($angle, $background));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save($path = null, array $options = array())
|
||||
{
|
||||
return $this->add(new Save($path, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function show($format, array $options = array())
|
||||
{
|
||||
return $this->add(new Show($format, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
return $this->add(new Thumbnail($size, $mode, $filter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a given FilterInterface in an internal array of filters for
|
||||
* later application to an instance of ImageInterface
|
||||
*
|
||||
* @param FilterInterface $filter
|
||||
* @param int $priority
|
||||
* @return Transformation
|
||||
*/
|
||||
public function add(FilterInterface $filter, $priority = 0)
|
||||
{
|
||||
$this->filters[$priority][] = $filter;
|
||||
$this->sorted = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Draw\DrawerInterface;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* Drawer implementation using the GD library
|
||||
*/
|
||||
final class Drawer implements DrawerInterface
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $info;
|
||||
|
||||
/**
|
||||
* Constructs Drawer with a given gd image resource
|
||||
*
|
||||
* @param resource $resource
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->loadGdInfo();
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw arc operation failed');
|
||||
}
|
||||
|
||||
if (false === imagearc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color))) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw arc operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw arc operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does not work properly because of a bug in GD
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$style = IMG_ARC_CHORD;
|
||||
} else {
|
||||
$style = IMG_ARC_CHORD | IMG_ARC_NOFILL;
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$callback = 'imagefilledellipse';
|
||||
} else {
|
||||
$callback = 'imageellipse';
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw ellipse operation failed');
|
||||
}
|
||||
|
||||
if (false === $callback($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $this->getColor($color))) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw ellipse operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw ellipse operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw line operation failed');
|
||||
}
|
||||
|
||||
if (false === imageline($this->resource, $start->getX(), $start->getY(), $end->getX(), $end->getY(), $this->getColor($color))) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw line operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw line operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$style = IMG_ARC_EDGED;
|
||||
} else {
|
||||
$style = IMG_ARC_EDGED | IMG_ARC_NOFILL;
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw chord operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dot(PointInterface $position, ColorInterface $color)
|
||||
{
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw point operation failed');
|
||||
}
|
||||
|
||||
if (false === imagesetpixel($this->resource, $position->getX(), $position->getY(), $this->getColor($color))) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw point operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw point operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
imagesetthickness($this->resource, max(1, (int) $thickness));
|
||||
|
||||
if (count($coordinates) < 3) {
|
||||
throw new InvalidArgumentException(sprintf('A polygon must consist of at least 3 points, %d given', count($coordinates)));
|
||||
}
|
||||
|
||||
$points = call_user_func_array('array_merge', array_map(function (PointInterface $p) {
|
||||
return array($p->getX(), $p->getY());
|
||||
}, $coordinates));
|
||||
|
||||
if ($fill) {
|
||||
$callback = 'imagefilledpolygon';
|
||||
} else {
|
||||
$callback = 'imagepolygon';
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Draw polygon operation failed');
|
||||
}
|
||||
|
||||
if (false === $callback($this->resource, $points, count($coordinates), $this->getColor($color))) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Draw polygon operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Draw polygon operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
|
||||
{
|
||||
if (!$this->info['FreeType Support']) {
|
||||
throw new RuntimeException('GD is not compiled with FreeType support');
|
||||
}
|
||||
|
||||
$angle = -1 * $angle;
|
||||
$fontsize = $font->getSize();
|
||||
$fontfile = $font->getFile();
|
||||
$x = $position->getX();
|
||||
$y = $position->getY() + $fontsize;
|
||||
|
||||
if ($width !== null) {
|
||||
$string = $this->wrapText($string, $font, $angle, $width);
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, true)) {
|
||||
throw new RuntimeException('Font mask operation failed');
|
||||
}
|
||||
|
||||
if (false === imagefttext($this->resource, $fontsize, $angle, $x, $y, $this->getColor($font->getColor()), $fontfile, $string)) {
|
||||
imagealphablending($this->resource, false);
|
||||
throw new RuntimeException('Font mask operation failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($this->resource, false)) {
|
||||
throw new RuntimeException('Font mask operation failed');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Generates a GD color from Color instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
if (!$color instanceof RGBColor) {
|
||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
||||
}
|
||||
|
||||
$gdColor = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), (100 - $color->getAlpha()) * 127 / 100);
|
||||
if (false === $gdColor) {
|
||||
throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha()));
|
||||
}
|
||||
|
||||
return $gdColor;
|
||||
}
|
||||
|
||||
private function loadGdInfo()
|
||||
{
|
||||
if (!function_exists('gd_info')) {
|
||||
throw new RuntimeException('Gd not installed');
|
||||
}
|
||||
|
||||
$this->info = gd_info();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Fits a string into box with given width
|
||||
*/
|
||||
private function wrapText($string, AbstractFont $font, $angle, $width)
|
||||
{
|
||||
$result = '';
|
||||
$words = explode(' ', $string);
|
||||
foreach ($words as $word) {
|
||||
$teststring = $result . ' ' . $word;
|
||||
$testbox = imagettfbbox($font->getSize(), $angle, $font->getFile(), $teststring);
|
||||
if ($testbox[2] > $width) {
|
||||
$result .= ($result == '' ? '' : "\n") . $word;
|
||||
} else {
|
||||
$result .= ($result == '' ? '' : ' ') . $word;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Effects\EffectsInterface;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
||||
|
||||
/**
|
||||
* Effects implementation using the GD library
|
||||
*/
|
||||
class Effects implements EffectsInterface
|
||||
{
|
||||
private $resource;
|
||||
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gamma($correction)
|
||||
{
|
||||
if (false === imagegammacorrect($this->resource, 1.0, $correction)) {
|
||||
throw new RuntimeException('Failed to apply gamma correction to the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function negative()
|
||||
{
|
||||
if (false === imagefilter($this->resource, IMG_FILTER_NEGATE)) {
|
||||
throw new RuntimeException('Failed to negate the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function grayscale()
|
||||
{
|
||||
if (false === imagefilter($this->resource, IMG_FILTER_GRAYSCALE)) {
|
||||
throw new RuntimeException('Failed to grayscale the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function colorize(ColorInterface $color)
|
||||
{
|
||||
if (!$color instanceof RGBColor) {
|
||||
throw new RuntimeException('Colorize effects only accepts RGB color in GD context');
|
||||
}
|
||||
|
||||
if (false === imagefilter($this->resource, IMG_FILTER_COLORIZE, $color->getRed(), $color->getGreen(), $color->getBlue())) {
|
||||
throw new RuntimeException('Failed to colorize the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sharpen()
|
||||
{
|
||||
$sharpenMatrix = array(array(-1,-1,-1), array(-1,16,-1), array(-1,-1,-1));
|
||||
$divisor = array_sum(array_map('array_sum', $sharpenMatrix));
|
||||
|
||||
if (false === imageconvolution($this->resource, $sharpenMatrix, $divisor, 0)) {
|
||||
throw new RuntimeException('Failed to sharpen the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blur($sigma = 1)
|
||||
{
|
||||
if (false === imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR)) {
|
||||
throw new RuntimeException('Failed to blur the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\Box;
|
||||
|
||||
/**
|
||||
* Font implementation using the GD library
|
||||
*/
|
||||
final class Font extends AbstractFont
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function box($string, $angle = 0)
|
||||
{
|
||||
if (!function_exists('imageftbbox')) {
|
||||
throw new RuntimeException('GD must have been compiled with `--with-freetype-dir` option to use the Font feature.');
|
||||
}
|
||||
|
||||
$angle = -1 * $angle;
|
||||
$info = imageftbbox($this->size, $angle, $this->file, $string);
|
||||
$xs = array($info[0], $info[2], $info[4], $info[6]);
|
||||
$ys = array($info[1], $info[3], $info[5], $info[7]);
|
||||
$width = abs(max($xs) - min($xs));
|
||||
$height = abs(max($ys) - min($ys));
|
||||
|
||||
return new Box($width, $height);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,735 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Image\AbstractImage;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Box;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Fill\FillInterface;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
||||
use Imagine\Image\ProfileInterface;
|
||||
use Imagine\Image\Palette\RGB;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\OutOfBoundsException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Image implementation using the GD library
|
||||
*/
|
||||
final class Image extends AbstractImage
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* @var Layers|null
|
||||
*/
|
||||
private $layers;
|
||||
|
||||
/**
|
||||
* @var PaletteInterface
|
||||
*/
|
||||
private $palette;
|
||||
|
||||
/**
|
||||
* Constructs a new Image instance
|
||||
*
|
||||
* @param resource $resource
|
||||
* @param PaletteInterface $palette
|
||||
* @param MetadataBag $metadata
|
||||
*/
|
||||
public function __construct($resource, PaletteInterface $palette, MetadataBag $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->palette = $palette;
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the current image resource is destroyed
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (is_resource($this->resource) && 'gd' === get_resource_type($this->resource)) {
|
||||
imagedestroy($this->resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Gd resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getGdResource()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function copy()
|
||||
{
|
||||
$size = $this->getSize();
|
||||
$copy = $this->createImage($size, 'copy');
|
||||
|
||||
if (false === imagecopy($copy, $this->resource, 0, 0, 0, 0, $size->getWidth(), $size->getHeight())) {
|
||||
throw new RuntimeException('Image copy operation failed');
|
||||
}
|
||||
|
||||
return new Image($copy, $this->palette, $this->metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function crop(PointInterface $start, BoxInterface $size)
|
||||
{
|
||||
if (!$start->in($this->getSize())) {
|
||||
throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
|
||||
}
|
||||
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$dest = $this->createImage($size, 'crop');
|
||||
|
||||
if (false === imagecopy($dest, $this->resource, 0, 0, $start->getX(), $start->getY(), $width, $height)) {
|
||||
throw new RuntimeException('Image crop operation failed');
|
||||
}
|
||||
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $dest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function paste(ImageInterface $image, PointInterface $start)
|
||||
{
|
||||
if (!$image instanceof self) {
|
||||
throw new InvalidArgumentException(sprintf('Gd\Image can only paste() Gd\Image instances, %s given', get_class($image)));
|
||||
}
|
||||
|
||||
$size = $image->getSize();
|
||||
if (!$this->getSize()->contains($size, $start)) {
|
||||
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
|
||||
}
|
||||
|
||||
imagealphablending($this->resource, true);
|
||||
imagealphablending($image->resource, true);
|
||||
|
||||
if (false === imagecopy($this->resource, $image->resource, $start->getX(), $start->getY(), 0, 0, $size->getWidth(), $size->getHeight())) {
|
||||
throw new RuntimeException('Image paste operation failed');
|
||||
}
|
||||
|
||||
imagealphablending($this->resource, false);
|
||||
imagealphablending($image->resource, false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
if (ImageInterface::FILTER_UNDEFINED !== $filter) {
|
||||
throw new InvalidArgumentException('Unsupported filter type, GD only supports ImageInterface::FILTER_UNDEFINED filter');
|
||||
}
|
||||
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$dest = $this->createImage($size, 'resize');
|
||||
|
||||
imagealphablending($this->resource, true);
|
||||
imagealphablending($dest, true);
|
||||
|
||||
if (false === imagecopyresampled($dest, $this->resource, 0, 0, 0, 0, $width, $height, imagesx($this->resource), imagesy($this->resource))) {
|
||||
throw new RuntimeException('Image resize operation failed');
|
||||
}
|
||||
|
||||
imagealphablending($this->resource, false);
|
||||
imagealphablending($dest, false);
|
||||
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $dest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function rotate($angle, ColorInterface $background = null)
|
||||
{
|
||||
$color = $background ? $background : $this->palette->color('fff');
|
||||
$resource = imagerotate($this->resource, -1 * $angle, $this->getColor($color));
|
||||
|
||||
if (false === $resource) {
|
||||
throw new RuntimeException('Image rotate operation failed');
|
||||
}
|
||||
|
||||
imagedestroy($this->resource);
|
||||
$this->resource = $resource;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function save($path = null, array $options = array())
|
||||
{
|
||||
$path = null === $path ? (isset($this->metadata['filepath']) ? $this->metadata['filepath'] : $path) : $path;
|
||||
|
||||
if (null === $path) {
|
||||
throw new RuntimeException('You can omit save path only if image has been open from a file');
|
||||
}
|
||||
|
||||
if (isset($options['format'])) {
|
||||
$format = $options['format'];
|
||||
} elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) {
|
||||
$format = $extension;
|
||||
} else {
|
||||
$originalPath = isset($this->metadata['filepath']) ? $this->metadata['filepath'] : null;
|
||||
$format = pathinfo($originalPath, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
$this->saveOrOutput($format, $options, $path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function show($format, array $options = array())
|
||||
{
|
||||
header('Content-type: '.$this->getMimeType($format));
|
||||
|
||||
$this->saveOrOutput($format, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($format, array $options = array())
|
||||
{
|
||||
ob_start();
|
||||
$this->saveOrOutput($format, $options);
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->get('png');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function flipHorizontally()
|
||||
{
|
||||
$size = $this->getSize();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
$dest = $this->createImage($size, 'flip');
|
||||
|
||||
for ($i = 0; $i < $width; $i++) {
|
||||
if (false === imagecopy($dest, $this->resource, $i, 0, ($width - 1) - $i, 0, 1, $height)) {
|
||||
throw new RuntimeException('Horizontal flip operation failed');
|
||||
}
|
||||
}
|
||||
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $dest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function flipVertically()
|
||||
{
|
||||
$size = $this->getSize();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
$dest = $this->createImage($size, 'flip');
|
||||
|
||||
for ($i = 0; $i < $height; $i++) {
|
||||
if (false === imagecopy($dest, $this->resource, 0, $i, 0, ($height - 1) - $i, $width, 1)) {
|
||||
throw new RuntimeException('Vertical flip operation failed');
|
||||
}
|
||||
}
|
||||
|
||||
imagedestroy($this->resource);
|
||||
|
||||
$this->resource = $dest;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
final public function strip()
|
||||
{
|
||||
// GD strips profiles and comment, so there's nothing to do here
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function draw()
|
||||
{
|
||||
return new Drawer($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function effects()
|
||||
{
|
||||
return new Effects($this->resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
return new Box(imagesx($this->resource), imagesy($this->resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function applyMask(ImageInterface $mask)
|
||||
{
|
||||
if (!$mask instanceof self) {
|
||||
throw new InvalidArgumentException('Cannot mask non-gd images');
|
||||
}
|
||||
|
||||
$size = $this->getSize();
|
||||
$maskSize = $mask->getSize();
|
||||
|
||||
if ($size != $maskSize) {
|
||||
throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size));
|
||||
}
|
||||
|
||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
||||
$position = new Point($x, $y);
|
||||
$color = $this->getColorAt($position);
|
||||
$maskColor = $mask->getColorAt($position);
|
||||
$round = (int) round(max($color->getAlpha(), (100 - $color->getAlpha()) * $maskColor->getRed() / 255));
|
||||
|
||||
if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($color->dissolve($round - $color->getAlpha())))) {
|
||||
throw new RuntimeException('Apply mask operation failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function fill(FillInterface $fill)
|
||||
{
|
||||
$size = $this->getSize();
|
||||
|
||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
||||
if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($fill->getColor(new Point($x, $y))))) {
|
||||
throw new RuntimeException('Fill operation failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mask()
|
||||
{
|
||||
$mask = $this->copy();
|
||||
|
||||
if (false === imagefilter($mask->resource, IMG_FILTER_GRAYSCALE)) {
|
||||
throw new RuntimeException('Mask operation failed');
|
||||
}
|
||||
|
||||
return $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function histogram()
|
||||
{
|
||||
$size = $this->getSize();
|
||||
$colors = array();
|
||||
|
||||
for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) {
|
||||
for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) {
|
||||
$colors[] = $this->getColorAt(new Point($x, $y));
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getColorAt(PointInterface $point)
|
||||
{
|
||||
if (!$point->in($this->getSize())) {
|
||||
throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
|
||||
}
|
||||
|
||||
$index = imagecolorat($this->resource, $point->getX(), $point->getY());
|
||||
$info = imagecolorsforindex($this->resource, $index);
|
||||
|
||||
return $this->palette->color(array($info['red'], $info['green'], $info['blue']), max(min(100 - (int) round($info['alpha'] / 127 * 100), 100), 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function layers()
|
||||
{
|
||||
if (null === $this->layers) {
|
||||
$this->layers = new Layers($this, $this->palette, $this->resource);
|
||||
}
|
||||
|
||||
return $this->layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
**/
|
||||
public function interlace($scheme)
|
||||
{
|
||||
static $supportedInterlaceSchemes = array(
|
||||
ImageInterface::INTERLACE_NONE => 0,
|
||||
ImageInterface::INTERLACE_LINE => 1,
|
||||
ImageInterface::INTERLACE_PLANE => 1,
|
||||
ImageInterface::INTERLACE_PARTITION => 1,
|
||||
);
|
||||
|
||||
if (!array_key_exists($scheme, $supportedInterlaceSchemes)) {
|
||||
throw new InvalidArgumentException('Unsupported interlace type');
|
||||
}
|
||||
|
||||
imageinterlace($this->resource, $supportedInterlaceSchemes[$scheme]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function palette()
|
||||
{
|
||||
return $this->palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function profile(ProfileInterface $profile)
|
||||
{
|
||||
throw new RuntimeException('GD driver does not support color profiles');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function usePalette(PaletteInterface $palette)
|
||||
{
|
||||
if (!$palette instanceof RGB) {
|
||||
throw new RuntimeException('GD driver only supports RGB palette');
|
||||
}
|
||||
|
||||
$this->palette = $palette;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Performs save or show operation using one of GD's image... functions
|
||||
*
|
||||
* @param string $format
|
||||
* @param array $options
|
||||
* @param string $filename
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function saveOrOutput($format, array $options, $filename = null)
|
||||
{
|
||||
$format = $this->normalizeFormat($format);
|
||||
|
||||
if (!$this->supported($format)) {
|
||||
throw new InvalidArgumentException(sprintf('Saving image in "%s" format is not supported, please use one of the following extensions: "%s"', $format, implode('", "', $this->supported())));
|
||||
}
|
||||
|
||||
$save = 'image'.$format;
|
||||
$args = array(&$this->resource, $filename);
|
||||
|
||||
// Preserve BC until version 1.0
|
||||
if (isset($options['quality']) && !isset($options['png_compression_level'])) {
|
||||
$options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100);
|
||||
}
|
||||
if (isset($options['filters']) && !isset($options['png_compression_filter'])) {
|
||||
$options['png_compression_filter'] = $options['filters'];
|
||||
}
|
||||
|
||||
$options = $this->updateSaveOptions($options);
|
||||
|
||||
if ($format === 'jpeg' && isset($options['jpeg_quality'])) {
|
||||
$args[] = $options['jpeg_quality'];
|
||||
}
|
||||
|
||||
if ($format === 'png') {
|
||||
if (isset($options['png_compression_level'])) {
|
||||
if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) {
|
||||
throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
|
||||
}
|
||||
$args[] = $options['png_compression_level'];
|
||||
} else {
|
||||
$args[] = -1; // use default level
|
||||
}
|
||||
|
||||
if (isset($options['png_compression_filter'])) {
|
||||
if (~PNG_ALL_FILTERS & $options['png_compression_filter']) {
|
||||
throw new InvalidArgumentException('png_compression_filter option should be a combination of the PNG_FILTER_XXX constants');
|
||||
}
|
||||
$args[] = $options['png_compression_filter'];
|
||||
}
|
||||
}
|
||||
|
||||
if (($format === 'wbmp' || $format === 'xbm') && isset($options['foreground'])) {
|
||||
$args[] = $options['foreground'];
|
||||
}
|
||||
|
||||
$this->setExceptionHandler();
|
||||
|
||||
if (false === call_user_func_array($save, $args)) {
|
||||
throw new RuntimeException('Save operation failed');
|
||||
}
|
||||
|
||||
$this->resetExceptionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Generates a GD image
|
||||
*
|
||||
* @param BoxInterface $size
|
||||
* @param string the operation initiating the creation
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*
|
||||
*/
|
||||
private function createImage(BoxInterface $size, $operation)
|
||||
{
|
||||
$resource = imagecreatetruecolor($size->getWidth(), $size->getHeight());
|
||||
|
||||
if (false === $resource) {
|
||||
throw new RuntimeException('Image '.$operation.' failed');
|
||||
}
|
||||
|
||||
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
|
||||
throw new RuntimeException('Image '.$operation.' failed');
|
||||
}
|
||||
|
||||
if (function_exists('imageantialias')) {
|
||||
imageantialias($resource, true);
|
||||
}
|
||||
|
||||
$transparent = imagecolorallocatealpha($resource, 255, 255, 255, 127);
|
||||
imagefill($resource, 0, 0, $transparent);
|
||||
imagecolortransparent($resource, $transparent);
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Generates a GD color from Color instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return integer A color identifier
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
if (!$color instanceof RGBColor) {
|
||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
||||
}
|
||||
|
||||
$index = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100));
|
||||
|
||||
if (false === $index) {
|
||||
throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha()));
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Normalizes a given format name
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeFormat($format)
|
||||
{
|
||||
$format = strtolower($format);
|
||||
|
||||
if ('jpg' === $format || 'pjpeg' === $format) {
|
||||
$format = 'jpeg';
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Checks whether a given format is supported by GD library
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private function supported($format = null)
|
||||
{
|
||||
$formats = array('gif', 'jpeg', 'png', 'wbmp', 'xbm');
|
||||
|
||||
if (null === $format) {
|
||||
return $formats;
|
||||
}
|
||||
|
||||
return in_array($format, $formats);
|
||||
}
|
||||
|
||||
private function setExceptionHandler()
|
||||
{
|
||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
|
||||
if (0 === error_reporting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new RuntimeException($errstr, $errno, new \ErrorException($errstr, 0, $errno, $errfile, $errline));
|
||||
}, E_WARNING | E_NOTICE);
|
||||
}
|
||||
|
||||
private function resetExceptionHandler()
|
||||
{
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Get the mime type based on format.
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string mime-type
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function getMimeType($format)
|
||||
{
|
||||
$format = $this->normalizeFormat($format);
|
||||
|
||||
if (!$this->supported($format)) {
|
||||
throw new RuntimeException('Invalid format');
|
||||
}
|
||||
|
||||
static $mimeTypes = array(
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'wbmp' => 'image/vnd.wap.wbmp',
|
||||
'xbm' => 'image/xbm',
|
||||
);
|
||||
|
||||
return $mimeTypes[$format];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Image\AbstractImagine;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Palette\RGB;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\RGB as RGBColor;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Imagine implementation using the GD library
|
||||
*/
|
||||
final class Imagine extends AbstractImagine
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $info;
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->loadGdInfo();
|
||||
$this->requireGdVersion('2.0.1');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(BoxInterface $size, ColorInterface $color = null)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$resource = imagecreatetruecolor($width, $height);
|
||||
|
||||
if (false === $resource) {
|
||||
throw new RuntimeException('Create operation failed');
|
||||
}
|
||||
|
||||
$palette = null !== $color ? $color->getPalette() : new RGB();
|
||||
$color = $color ? $color : $palette->color('fff');
|
||||
|
||||
if (!$color instanceof RGBColor) {
|
||||
throw new InvalidArgumentException('GD driver only supports RGB colors');
|
||||
}
|
||||
|
||||
$index = imagecolorallocatealpha($resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100));
|
||||
|
||||
if (false === $index) {
|
||||
throw new RuntimeException('Unable to allocate color');
|
||||
}
|
||||
|
||||
if (false === imagefill($resource, 0, 0, $index)) {
|
||||
throw new RuntimeException('Could not set background color fill');
|
||||
}
|
||||
|
||||
if ($color->getAlpha() >= 95) {
|
||||
imagecolortransparent($resource, $index);
|
||||
}
|
||||
|
||||
return $this->wrap($resource, $palette, new MetadataBag());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($path)
|
||||
{
|
||||
$path = $this->checkPath($path);
|
||||
$data = @file_get_contents($path);
|
||||
|
||||
if (false === $data) {
|
||||
throw new RuntimeException(sprintf('Failed to open file %s', $path));
|
||||
}
|
||||
|
||||
$resource = @imagecreatefromstring($data);
|
||||
|
||||
if (!is_resource($resource)) {
|
||||
throw new RuntimeException(sprintf('Unable to open image %s', $path));
|
||||
}
|
||||
|
||||
return $this->wrap($resource, new RGB(), $this->getMetadataReader()->readFile($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($string)
|
||||
{
|
||||
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($resource)
|
||||
{
|
||||
if (!is_resource($resource)) {
|
||||
throw new InvalidArgumentException('Variable does not contain a stream resource');
|
||||
}
|
||||
|
||||
$content = stream_get_contents($resource);
|
||||
|
||||
if (false === $content) {
|
||||
throw new InvalidArgumentException('Cannot read resource content');
|
||||
}
|
||||
|
||||
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function font($file, $size, ColorInterface $color)
|
||||
{
|
||||
if (!$this->info['FreeType Support']) {
|
||||
throw new RuntimeException('GD is not compiled with FreeType support');
|
||||
}
|
||||
|
||||
return new Font($file, $size, $color);
|
||||
}
|
||||
|
||||
private function wrap($resource, PaletteInterface $palette, MetadataBag $metadata)
|
||||
{
|
||||
if (!imageistruecolor($resource)) {
|
||||
list($width, $height) = array(imagesx($resource), imagesy($resource));
|
||||
|
||||
// create transparent truecolor canvas
|
||||
$truecolor = imagecreatetruecolor($width, $height);
|
||||
$transparent = imagecolorallocatealpha($truecolor, 255, 255, 255, 127);
|
||||
|
||||
imagefill($truecolor, 0, 0, $transparent);
|
||||
imagecolortransparent($truecolor, $transparent);
|
||||
|
||||
imagecopymerge($truecolor, $resource, 0, 0, 0, 0, $width, $height, 100);
|
||||
|
||||
imagedestroy($resource);
|
||||
$resource = $truecolor;
|
||||
}
|
||||
|
||||
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
|
||||
throw new RuntimeException('Could not set alphablending, savealpha and antialias values');
|
||||
}
|
||||
|
||||
if (function_exists('imageantialias')) {
|
||||
imageantialias($resource, true);
|
||||
}
|
||||
|
||||
return new Image($resource, $palette, $metadata);
|
||||
}
|
||||
|
||||
private function loadGdInfo()
|
||||
{
|
||||
if (!function_exists('gd_info')) {
|
||||
throw new RuntimeException('Gd not installed');
|
||||
}
|
||||
|
||||
$this->info = gd_info();
|
||||
}
|
||||
|
||||
private function requireGdVersion($version)
|
||||
{
|
||||
if (version_compare(GD_VERSION, $version, '<')) {
|
||||
throw new RuntimeException(sprintf('GD2 version %s or higher is required', $version));
|
||||
}
|
||||
}
|
||||
|
||||
private function doLoad($string, MetadataBag $metadata)
|
||||
{
|
||||
$resource = @imagecreatefromstring($string);
|
||||
|
||||
if (!is_resource($resource)) {
|
||||
throw new RuntimeException('An image could not be created from the given input');
|
||||
}
|
||||
|
||||
return $this->wrap($resource, new RGB(), $metadata);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gd;
|
||||
|
||||
use Imagine\Image\AbstractLayers;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
|
||||
class Layers extends AbstractLayers
|
||||
{
|
||||
private $image;
|
||||
private $offset;
|
||||
private $resource;
|
||||
private $palette;
|
||||
|
||||
public function __construct(Image $image, PaletteInterface $palette, $resource)
|
||||
{
|
||||
if (!is_resource($resource)) {
|
||||
throw new RuntimeException('Invalid Gd resource provided');
|
||||
}
|
||||
|
||||
$this->image = $image;
|
||||
$this->resource = $resource;
|
||||
$this->offset = 0;
|
||||
$this->palette = $palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function merge()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coalesce()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function animate($format, $delay, $loops)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return new Image($this->resource, $this->palette, new MetadataBag());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
++$this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->offset < 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return 0 === $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (0 === $offset) {
|
||||
return new Image($this->resource, $this->palette, new MetadataBag());
|
||||
}
|
||||
|
||||
throw new RuntimeException('GD only supports one layer at offset 0');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new NotSupportedException('GD does not support layer set');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new NotSupportedException('GD does not support layer unset');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Draw\DrawerInterface;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* Drawer implementation using the Gmagick PHP extension
|
||||
*/
|
||||
final class Drawer implements DrawerInterface
|
||||
{
|
||||
/**
|
||||
* @var \Gmagick
|
||||
*/
|
||||
private $gmagick;
|
||||
|
||||
/**
|
||||
* @param \Gmagick $gmagick
|
||||
*/
|
||||
public function __construct(\Gmagick $gmagick)
|
||||
{
|
||||
$this->gmagick = $gmagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
$x = $center->getX();
|
||||
$y = $center->getY();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$arc = new \GmagickDraw();
|
||||
|
||||
$arc->setstrokecolor($pixel);
|
||||
$arc->setstrokewidth(max(1, (int) $thickness));
|
||||
$arc->setfillcolor('transparent');
|
||||
$arc->arc(
|
||||
$x - $width / 2,
|
||||
$y - $height / 2,
|
||||
$x + $width / 2,
|
||||
$y + $height / 2,
|
||||
$start,
|
||||
$end
|
||||
);
|
||||
|
||||
$this->gmagick->drawImage($arc);
|
||||
|
||||
$pixel = null;
|
||||
|
||||
$arc = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$x = $center->getX();
|
||||
$y = $center->getY();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$chord = new \GmagickDraw();
|
||||
|
||||
$chord->setstrokecolor($pixel);
|
||||
$chord->setstrokewidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$chord->setfillcolor($pixel);
|
||||
} else {
|
||||
$x1 = round($x + $width / 2 * cos(deg2rad($start)));
|
||||
$y1 = round($y + $height / 2 * sin(deg2rad($start)));
|
||||
$x2 = round($x + $width / 2 * cos(deg2rad($end)));
|
||||
$y2 = round($y + $height / 2 * sin(deg2rad($end)));
|
||||
|
||||
$this->line(new Point($x1, $y1), new Point($x2, $y2), $color);
|
||||
|
||||
$chord->setfillcolor('transparent');
|
||||
}
|
||||
|
||||
$chord->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end);
|
||||
|
||||
$this->gmagick->drawImage($chord);
|
||||
|
||||
$pixel = null;
|
||||
|
||||
$chord = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$ellipse = new \GmagickDraw();
|
||||
|
||||
$ellipse->setstrokecolor($pixel);
|
||||
$ellipse->setstrokewidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$ellipse->setfillcolor($pixel);
|
||||
} else {
|
||||
$ellipse->setfillcolor('transparent');
|
||||
}
|
||||
|
||||
$ellipse->ellipse(
|
||||
$center->getX(),
|
||||
$center->getY(),
|
||||
$width / 2,
|
||||
$height / 2,
|
||||
0, 360
|
||||
);
|
||||
|
||||
$this->gmagick->drawImage($ellipse);
|
||||
|
||||
$pixel = null;
|
||||
|
||||
$ellipse = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$line = new \GmagickDraw();
|
||||
|
||||
$line->setstrokecolor($pixel);
|
||||
$line->setstrokewidth(max(1, (int) $thickness));
|
||||
$line->setfillcolor($pixel);
|
||||
$line->line(
|
||||
$start->getX(),
|
||||
$start->getY(),
|
||||
$end->getX(),
|
||||
$end->getY()
|
||||
);
|
||||
|
||||
$this->gmagick->drawImage($line);
|
||||
|
||||
$pixel = null;
|
||||
|
||||
$line = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw line operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$x1 = round($center->getX() + $width / 2 * cos(deg2rad($start)));
|
||||
$y1 = round($center->getY() + $height / 2 * sin(deg2rad($start)));
|
||||
$x2 = round($center->getX() + $width / 2 * cos(deg2rad($end)));
|
||||
$y2 = round($center->getY() + $height / 2 * sin(deg2rad($end)));
|
||||
|
||||
if ($fill) {
|
||||
$this->chord($center, $size, $start, $end, $color, true, $thickness);
|
||||
$this->polygon(
|
||||
array(
|
||||
$center,
|
||||
new Point($x1, $y1),
|
||||
new Point($x2, $y2),
|
||||
),
|
||||
$color,
|
||||
true,
|
||||
$thickness
|
||||
);
|
||||
} else {
|
||||
$this->arc($center, $size, $start, $end, $color, $thickness);
|
||||
$this->line($center, new Point($x1, $y1), $color, $thickness);
|
||||
$this->line($center, new Point($x2, $y2), $color, $thickness);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dot(PointInterface $position, ColorInterface $color)
|
||||
{
|
||||
$x = $position->getX();
|
||||
$y = $position->getY();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$point = new \GmagickDraw();
|
||||
|
||||
$point->setfillcolor($pixel);
|
||||
$point->point($x, $y);
|
||||
|
||||
$this->gmagick->drawimage($point);
|
||||
|
||||
$pixel = null;
|
||||
$point = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw point operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
if (count($coordinates) < 3) {
|
||||
throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates)));
|
||||
}
|
||||
|
||||
$points = array_map(function (PointInterface $p) {
|
||||
return array('x' => $p->getX(), 'y' => $p->getY());
|
||||
}, $coordinates);
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$polygon = new \GmagickDraw();
|
||||
|
||||
$polygon->setstrokecolor($pixel);
|
||||
$polygon->setstrokewidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$polygon->setfillcolor($pixel);
|
||||
} else {
|
||||
$polygon->setfillcolor('transparent');
|
||||
}
|
||||
|
||||
$polygon->polygon($points);
|
||||
|
||||
$this->gmagick->drawImage($polygon);
|
||||
|
||||
unset($pixel, $polygon);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
|
||||
{
|
||||
try {
|
||||
$pixel = $this->getColor($font->getColor());
|
||||
$text = new \GmagickDraw();
|
||||
|
||||
$text->setfont($font->getFile());
|
||||
/**
|
||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
||||
*
|
||||
* ensure font resolution is the same as GD's hard-coded 96
|
||||
*/
|
||||
$text->setfontsize((int) ($font->getSize() * (96 / 72)));
|
||||
$text->setfillcolor($pixel);
|
||||
|
||||
$info = $this->gmagick->queryfontmetrics($text, $string);
|
||||
$rad = deg2rad($angle);
|
||||
$cos = cos($rad);
|
||||
$sin = sin($rad);
|
||||
|
||||
$x1 = round(0 * $cos - 0 * $sin);
|
||||
$x2 = round($info['textWidth'] * $cos - $info['textHeight'] * $sin);
|
||||
$y1 = round(0 * $sin + 0 * $cos);
|
||||
$y2 = round($info['textWidth'] * $sin + $info['textHeight'] * $cos);
|
||||
|
||||
$xdiff = 0 - min($x1, $x2);
|
||||
$ydiff = 0 - min($y1, $y2);
|
||||
|
||||
if ($width !== null) {
|
||||
throw new NotSupportedException('Gmagick doesn\'t support queryfontmetrics function for multiline text', 1);
|
||||
}
|
||||
|
||||
$this->gmagick->annotateimage($text, $position->getX() + $x1 + $xdiff, $position->getY() + $y2 + $ydiff, $angle, $string);
|
||||
|
||||
unset($pixel, $text);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Draw text operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specifically formatted color string from Color instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return \GmagickPixel
|
||||
*
|
||||
* @throws InvalidArgumentException In case a non-opaque color is passed
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
if (!$color->isOpaque()) {
|
||||
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
|
||||
}
|
||||
|
||||
return new \GmagickPixel((string) $color);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Effects\EffectsInterface;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
|
||||
/**
|
||||
* Effects implementation using the Gmagick PHP extension
|
||||
*/
|
||||
class Effects implements EffectsInterface
|
||||
{
|
||||
private $gmagick;
|
||||
|
||||
public function __construct(\Gmagick $gmagick)
|
||||
{
|
||||
$this->gmagick = $gmagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gamma($correction)
|
||||
{
|
||||
try {
|
||||
$this->gmagick->gammaimage($correction);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to apply gamma correction to the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function negative()
|
||||
{
|
||||
if (!method_exists($this->gmagick, 'negateimage')) {
|
||||
throw new NotSupportedException('Gmagick version 1.1.0 RC3 is required for negative effect');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->gmagick->negateimage(false, \Gmagick::CHANNEL_ALL);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to negate the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function grayscale()
|
||||
{
|
||||
try {
|
||||
$this->gmagick->setImageType(2);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to grayscale the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function colorize(ColorInterface $color)
|
||||
{
|
||||
throw new NotSupportedException('Gmagick does not support colorize');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sharpen()
|
||||
{
|
||||
throw new NotSupportedException('Gmagick does not support sharpen yet');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blur($sigma = 1)
|
||||
{
|
||||
try {
|
||||
$this->gmagick->blurImage(0, $sigma);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to blur the image', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\Box;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Font implementation using the Gmagick PHP extension
|
||||
*/
|
||||
final class Font extends AbstractFont
|
||||
{
|
||||
/**
|
||||
* @var \Gmagick
|
||||
*/
|
||||
private $gmagick;
|
||||
|
||||
/**
|
||||
* @param \Gmagick $gmagick
|
||||
* @param string $file
|
||||
* @param integer $size
|
||||
* @param ColorInterface $color
|
||||
*/
|
||||
public function __construct(\Gmagick $gmagick, $file, $size, ColorInterface $color)
|
||||
{
|
||||
$this->gmagick = $gmagick;
|
||||
|
||||
parent::__construct($file, $size, $color);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function box($string, $angle = 0)
|
||||
{
|
||||
$text = new \GmagickDraw();
|
||||
|
||||
$text->setfont($this->file);
|
||||
/**
|
||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
||||
*
|
||||
* ensure font resolution is the same as GD's hard-coded 96
|
||||
*/
|
||||
$text->setfontsize((int) ($this->size * (96 / 72)));
|
||||
$text->setfontstyle(\Gmagick::STYLE_OBLIQUE);
|
||||
|
||||
$info = $this->gmagick->queryfontmetrics($text, $string);
|
||||
|
||||
$box = new Box($info['textWidth'], $info['textHeight']);
|
||||
|
||||
return $box;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,786 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Exception\OutOfBoundsException;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractImage;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Box;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Fill\FillInterface;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Image\ProfileInterface;
|
||||
|
||||
/**
|
||||
* Image implementation using the Gmagick PHP extension
|
||||
*/
|
||||
final class Image extends AbstractImage
|
||||
{
|
||||
/**
|
||||
* @var \Gmagick
|
||||
*/
|
||||
private $gmagick;
|
||||
/**
|
||||
* @var Layers
|
||||
*/
|
||||
private $layers;
|
||||
|
||||
/**
|
||||
* @var PaletteInterface
|
||||
*/
|
||||
private $palette;
|
||||
|
||||
private static $colorspaceMapping = array(
|
||||
PaletteInterface::PALETTE_CMYK => \Gmagick::COLORSPACE_CMYK,
|
||||
PaletteInterface::PALETTE_RGB => \Gmagick::COLORSPACE_RGB,
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a new Image instance
|
||||
*
|
||||
* @param \Gmagick $gmagick
|
||||
* @param PaletteInterface $palette
|
||||
* @param MetadataBag $metadata
|
||||
*/
|
||||
public function __construct(\Gmagick $gmagick, PaletteInterface $palette, MetadataBag $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->gmagick = $gmagick;
|
||||
$this->setColorspace($palette);
|
||||
$this->layers = new Layers($this, $this->palette, $this->gmagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys allocated gmagick resources
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->gmagick instanceof \Gmagick) {
|
||||
$this->gmagick->clear();
|
||||
$this->gmagick->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns gmagick instance
|
||||
*
|
||||
* @return Gmagick
|
||||
*/
|
||||
public function getGmagick()
|
||||
{
|
||||
return $this->gmagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
return new self(clone $this->gmagick, $this->palette, clone $this->metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function crop(PointInterface $start, BoxInterface $size)
|
||||
{
|
||||
if (!$start->in($this->getSize())) {
|
||||
throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->gmagick->cropimage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY());
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Crop operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function flipHorizontally()
|
||||
{
|
||||
try {
|
||||
$this->gmagick->flopimage();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Horizontal flip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function flipVertically()
|
||||
{
|
||||
try {
|
||||
$this->gmagick->flipimage();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function strip()
|
||||
{
|
||||
try {
|
||||
try {
|
||||
$this->profile($this->palette->profile());
|
||||
} catch (\Exception $e) {
|
||||
// here we discard setting the profile as the previous incorporated profile
|
||||
// is corrupted, let's now strip the image
|
||||
}
|
||||
$this->gmagick->stripimage();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Strip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function paste(ImageInterface $image, PointInterface $start)
|
||||
{
|
||||
if (!$image instanceof self) {
|
||||
throw new InvalidArgumentException(sprintf('Gmagick\Image can only paste() Gmagick\Image instances, %s given', get_class($image)));
|
||||
}
|
||||
|
||||
if (!$this->getSize()->contains($image->getSize(), $start)) {
|
||||
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->gmagick->compositeimage($image->gmagick, \Gmagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY());
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Paste operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
static $supportedFilters = array(
|
||||
ImageInterface::FILTER_UNDEFINED => \Gmagick::FILTER_UNDEFINED,
|
||||
ImageInterface::FILTER_BESSEL => \Gmagick::FILTER_BESSEL,
|
||||
ImageInterface::FILTER_BLACKMAN => \Gmagick::FILTER_BLACKMAN,
|
||||
ImageInterface::FILTER_BOX => \Gmagick::FILTER_BOX,
|
||||
ImageInterface::FILTER_CATROM => \Gmagick::FILTER_CATROM,
|
||||
ImageInterface::FILTER_CUBIC => \Gmagick::FILTER_CUBIC,
|
||||
ImageInterface::FILTER_GAUSSIAN => \Gmagick::FILTER_GAUSSIAN,
|
||||
ImageInterface::FILTER_HANNING => \Gmagick::FILTER_HANNING,
|
||||
ImageInterface::FILTER_HAMMING => \Gmagick::FILTER_HAMMING,
|
||||
ImageInterface::FILTER_HERMITE => \Gmagick::FILTER_HERMITE,
|
||||
ImageInterface::FILTER_LANCZOS => \Gmagick::FILTER_LANCZOS,
|
||||
ImageInterface::FILTER_MITCHELL => \Gmagick::FILTER_MITCHELL,
|
||||
ImageInterface::FILTER_POINT => \Gmagick::FILTER_POINT,
|
||||
ImageInterface::FILTER_QUADRATIC => \Gmagick::FILTER_QUADRATIC,
|
||||
ImageInterface::FILTER_SINC => \Gmagick::FILTER_SINC,
|
||||
ImageInterface::FILTER_TRIANGLE => \Gmagick::FILTER_TRIANGLE
|
||||
);
|
||||
|
||||
if (!array_key_exists($filter, $supportedFilters)) {
|
||||
throw new InvalidArgumentException('Unsupported filter type');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->gmagick->resizeimage($size->getWidth(), $size->getHeight(), $supportedFilters[$filter], 1);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Resize operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function rotate($angle, ColorInterface $background = null)
|
||||
{
|
||||
try {
|
||||
$background = $background ?: $this->palette->color('fff');
|
||||
$pixel = $this->getColor($background);
|
||||
|
||||
$this->gmagick->rotateimage($pixel, $angle);
|
||||
|
||||
unset($pixel);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Rotate operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Applies options before save or output
|
||||
*
|
||||
* @param \Gmagick $image
|
||||
* @param array $options
|
||||
* @param string $path
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function applyImageOptions(\Gmagick $image, array $options, $path)
|
||||
{
|
||||
if (isset($options['format'])) {
|
||||
$format = $options['format'];
|
||||
} elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) {
|
||||
$format = $extension;
|
||||
} else {
|
||||
$format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
$format = strtolower($format);
|
||||
|
||||
$options = $this->updateSaveOptions($options);
|
||||
|
||||
if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) {
|
||||
$image->setCompressionQuality($options['jpeg_quality']);
|
||||
}
|
||||
|
||||
if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') {
|
||||
// first digit: compression level (default: 7)
|
||||
if (isset($options['png_compression_level'])) {
|
||||
if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) {
|
||||
throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
|
||||
}
|
||||
$compression = $options['png_compression_level'] * 10;
|
||||
} else {
|
||||
$compression = 70;
|
||||
}
|
||||
|
||||
// second digit: compression filter (default: 5)
|
||||
if (isset($options['png_compression_filter'])) {
|
||||
if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) {
|
||||
throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9');
|
||||
}
|
||||
$compression += $options['png_compression_filter'];
|
||||
} else {
|
||||
$compression += 5;
|
||||
}
|
||||
|
||||
$image->setCompressionQuality($compression);
|
||||
}
|
||||
|
||||
if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
|
||||
if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) {
|
||||
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
} elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) {
|
||||
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERINCH);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Unsupported image unit format');
|
||||
}
|
||||
|
||||
$image->setimageresolution($options['resolution-x'], $options['resolution-y']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function save($path = null, array $options = array())
|
||||
{
|
||||
$path = null === $path ? $this->gmagick->getImageFilename() : $path;
|
||||
|
||||
if ('' === trim($path)) {
|
||||
throw new RuntimeException('You can omit save path only if image has been open from a file');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->prepareOutput($options, $path);
|
||||
$allFrames = !isset($options['animated']) || false === $options['animated'];
|
||||
$this->gmagick->writeimage($path, $allFrames);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Save operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function show($format, array $options = array())
|
||||
{
|
||||
header('Content-type: '.$this->getMimeType($format));
|
||||
echo $this->get($format, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($format, array $options = array())
|
||||
{
|
||||
try {
|
||||
$options['format'] = $format;
|
||||
$this->prepareOutput($options);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Get operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this->gmagick->getimagesblob();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param string $path
|
||||
*/
|
||||
private function prepareOutput(array $options, $path = null)
|
||||
{
|
||||
if (isset($options['format'])) {
|
||||
$this->gmagick->setimageformat($options['format']);
|
||||
}
|
||||
|
||||
if (isset($options['animated']) && true === $options['animated']) {
|
||||
$format = isset($options['format']) ? $options['format'] : 'gif';
|
||||
$delay = isset($options['animated.delay']) ? $options['animated.delay'] : null;
|
||||
$loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0;
|
||||
|
||||
$options['flatten'] = false;
|
||||
|
||||
$this->layers->animate($format, $delay, $loops);
|
||||
} else {
|
||||
$this->layers->merge();
|
||||
}
|
||||
$this->applyImageOptions($this->gmagick, $options, $path);
|
||||
|
||||
// flatten only if image has multiple layers
|
||||
if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) {
|
||||
$this->flatten();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->get('png');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function draw()
|
||||
{
|
||||
return new Drawer($this->gmagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function effects()
|
||||
{
|
||||
return new Effects($this->gmagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
try {
|
||||
$i = $this->gmagick->getimageindex();
|
||||
$this->gmagick->setimageindex(0); //rewind
|
||||
$width = $this->gmagick->getimagewidth();
|
||||
$height = $this->gmagick->getimageheight();
|
||||
$this->gmagick->setimageindex($i);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Get size operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return new Box($width, $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function applyMask(ImageInterface $mask)
|
||||
{
|
||||
if (!$mask instanceof self) {
|
||||
throw new InvalidArgumentException('Can only apply instances of Imagine\Gmagick\Image as masks');
|
||||
}
|
||||
|
||||
$size = $this->getSize();
|
||||
$maskSize = $mask->getSize();
|
||||
|
||||
if ($size != $maskSize) {
|
||||
throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size));
|
||||
}
|
||||
|
||||
try {
|
||||
$mask = $mask->copy();
|
||||
$this->gmagick->compositeimage($mask->gmagick, \Gmagick::COMPOSITE_DEFAULT, 0, 0);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mask()
|
||||
{
|
||||
$mask = $this->copy();
|
||||
|
||||
try {
|
||||
$mask->gmagick->modulateimage(100, 0, 100);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Mask operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function fill(FillInterface $fill)
|
||||
{
|
||||
try {
|
||||
$draw = new \GmagickDraw();
|
||||
$size = $this->getSize();
|
||||
|
||||
$w = $size->getWidth();
|
||||
$h = $size->getHeight();
|
||||
|
||||
for ($x = 0; $x <= $w; $x++) {
|
||||
for ($y = 0; $y <= $h; $y++) {
|
||||
$pixel = $this->getColor($fill->getColor(new Point($x, $y)));
|
||||
|
||||
$draw->setfillcolor($pixel);
|
||||
$draw->point($x, $y);
|
||||
|
||||
$pixel = null;
|
||||
}
|
||||
}
|
||||
|
||||
$this->gmagick->drawimage($draw);
|
||||
|
||||
$draw = null;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Fill operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function histogram()
|
||||
{
|
||||
try {
|
||||
$pixels = $this->gmagick->getimagehistogram();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$image = $this;
|
||||
|
||||
return array_map(function (\GmagickPixel $pixel) use ($image) {
|
||||
return $image->pixelToColor($pixel);
|
||||
}, $pixels);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getColorAt(PointInterface $point)
|
||||
{
|
||||
if (!$point->in($this->getSize())) {
|
||||
throw new InvalidArgumentException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
|
||||
}
|
||||
|
||||
try {
|
||||
$cropped = clone $this->gmagick;
|
||||
$histogram = $cropped
|
||||
->cropImage(1, 1, $point->getX(), $point->getY())
|
||||
->getImageHistogram();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Unable to get the pixel');
|
||||
}
|
||||
|
||||
$pixel = array_shift($histogram);
|
||||
|
||||
unset($histogram, $cropped);
|
||||
|
||||
return $this->pixelToColor($pixel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color given a pixel, depending the Palette context
|
||||
*
|
||||
* Note : this method is public for PHP 5.3 compatibility
|
||||
*
|
||||
* @param \GmagickPixel $pixel
|
||||
*
|
||||
* @return ColorInterface
|
||||
*
|
||||
* @throws InvalidArgumentException In case a unknown color is requested
|
||||
*/
|
||||
public function pixelToColor(\GmagickPixel $pixel)
|
||||
{
|
||||
static $colorMapping = array(
|
||||
ColorInterface::COLOR_RED => \Gmagick::COLOR_RED,
|
||||
ColorInterface::COLOR_GREEN => \Gmagick::COLOR_GREEN,
|
||||
ColorInterface::COLOR_BLUE => \Gmagick::COLOR_BLUE,
|
||||
ColorInterface::COLOR_CYAN => \Gmagick::COLOR_CYAN,
|
||||
ColorInterface::COLOR_MAGENTA => \Gmagick::COLOR_MAGENTA,
|
||||
ColorInterface::COLOR_YELLOW => \Gmagick::COLOR_YELLOW,
|
||||
ColorInterface::COLOR_KEYLINE => \Gmagick::COLOR_BLACK,
|
||||
// There is no gray component in \Gmagick, let's use one of the RGB comp
|
||||
ColorInterface::COLOR_GRAY => \Gmagick::COLOR_RED,
|
||||
);
|
||||
|
||||
if ($this->palette->supportsAlpha()) {
|
||||
try {
|
||||
$alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_ALPHA) * 100);
|
||||
} catch (\GmagickPixelException $e) {
|
||||
$alpha = null;
|
||||
}
|
||||
} else {
|
||||
$alpha = null;
|
||||
}
|
||||
|
||||
$palette = $this->palette();
|
||||
|
||||
return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) {
|
||||
if (!isset($colorMapping[$color])) {
|
||||
throw new InvalidArgumentException(sprintf('Color %s is not mapped in Gmagick', $color));
|
||||
}
|
||||
$multiplier = 255;
|
||||
if ($palette->name() === PaletteInterface::PALETTE_CMYK) {
|
||||
$multiplier = 100;
|
||||
}
|
||||
|
||||
return $pixel->getcolorvalue($colorMapping[$color]) * $multiplier;
|
||||
}, $this->palette->pixelDefinition()), $alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function layers()
|
||||
{
|
||||
return $this->layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function interlace($scheme)
|
||||
{
|
||||
static $supportedInterlaceSchemes = array(
|
||||
ImageInterface::INTERLACE_NONE => \Gmagick::INTERLACE_NO,
|
||||
ImageInterface::INTERLACE_LINE => \Gmagick::INTERLACE_LINE,
|
||||
ImageInterface::INTERLACE_PLANE => \Gmagick::INTERLACE_PLANE,
|
||||
ImageInterface::INTERLACE_PARTITION => \Gmagick::INTERLACE_PARTITION,
|
||||
);
|
||||
|
||||
if (!array_key_exists($scheme, $supportedInterlaceSchemes)) {
|
||||
throw new InvalidArgumentException('Unsupported interlace type');
|
||||
}
|
||||
|
||||
$this->gmagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function usePalette(PaletteInterface $palette)
|
||||
{
|
||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver',$palette->name()));
|
||||
}
|
||||
|
||||
if ($this->palette->name() === $palette->name()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
$hasICCProfile = (Boolean) $this->gmagick->getimageprofile('ICM');
|
||||
} catch (\GmagickException $e) {
|
||||
$hasICCProfile = false;
|
||||
}
|
||||
|
||||
if (!$hasICCProfile) {
|
||||
$this->profile($this->palette->profile());
|
||||
}
|
||||
|
||||
$this->profile($palette->profile());
|
||||
|
||||
$this->setColorspace($palette);
|
||||
$this->palette = $palette;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function palette()
|
||||
{
|
||||
return $this->palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function profile(ProfileInterface $profile)
|
||||
{
|
||||
try {
|
||||
$this->gmagick->profileimage('ICM', $profile->data());
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Flatten the image.
|
||||
*/
|
||||
private function flatten()
|
||||
{
|
||||
/**
|
||||
* @see http://pecl.php.net/bugs/bug.php?id=22435
|
||||
*/
|
||||
if (method_exists($this->gmagick, 'flattenImages')) {
|
||||
try {
|
||||
$this->gmagick = $this->gmagick->flattenImages();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Flatten operation failed', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specifically formatted color string from Color instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return \GmagickPixel
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
if (!$color->isOpaque()) {
|
||||
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
|
||||
}
|
||||
|
||||
return new \GmagickPixel((string) $color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Get the mime type based on format.
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string mime-type
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function getMimeType($format)
|
||||
{
|
||||
static $mimeTypes = array(
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'wbmp' => 'image/vnd.wap.wbmp',
|
||||
'xbm' => 'image/xbm',
|
||||
);
|
||||
|
||||
if (!isset($mimeTypes[$format])) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(", ", array_keys($mimeTypes)), $format));
|
||||
}
|
||||
|
||||
return $mimeTypes[$format];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets colorspace and image type, assigns the palette.
|
||||
*
|
||||
* @param PaletteInterface $palette
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function setColorspace(PaletteInterface $palette)
|
||||
{
|
||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name()));
|
||||
}
|
||||
|
||||
$this->gmagick->setimagecolorspace(static::$colorspaceMapping[$palette->name()]);
|
||||
$this->palette = $palette;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Image\AbstractImagine;
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Palette\Grayscale;
|
||||
use Imagine\Image\Palette\CMYK;
|
||||
use Imagine\Image\Palette\RGB;
|
||||
use Imagine\Image\Palette\Color\CMYK as CMYKColor;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Imagine implementation using the Gmagick PHP extension
|
||||
*/
|
||||
class Imagine extends AbstractImagine
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!class_exists('Gmagick')) {
|
||||
throw new RuntimeException('Gmagick not installed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($path)
|
||||
{
|
||||
$path = $this->checkPath($path);
|
||||
|
||||
try {
|
||||
$gmagick = new \Gmagick($path);
|
||||
$image = new Image($gmagick, $this->createPalette($gmagick), $this->getMetadataReader()->readFile($path));
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(BoxInterface $size, ColorInterface $color = null)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$palette = null !== $color ? $color->getPalette() : new RGB();
|
||||
$color = null !== $color ? $color : $palette->color('fff');
|
||||
|
||||
try {
|
||||
$gmagick = new \Gmagick();
|
||||
// Gmagick does not support creation of CMYK GmagickPixel
|
||||
// see https://bugs.php.net/bug.php?id=64466
|
||||
if ($color instanceof CMYKColor) {
|
||||
$switchPalette = $palette;
|
||||
$palette = new RGB();
|
||||
$pixel = new \GmagickPixel($palette->color((string) $color));
|
||||
} else {
|
||||
$switchPalette = null;
|
||||
$pixel = new \GmagickPixel((string) $color);
|
||||
}
|
||||
|
||||
if ($color->getPalette()->supportsAlpha() && $color->getAlpha() < 100) {
|
||||
throw new NotSupportedException('alpha transparency is not supported');
|
||||
}
|
||||
|
||||
$gmagick->newimage($width, $height, $pixel->getcolor(false));
|
||||
$gmagick->setimagecolorspace(\Gmagick::COLORSPACE_TRANSPARENT);
|
||||
$gmagick->setimagebackgroundcolor($pixel);
|
||||
|
||||
$image = new Image($gmagick, $palette, new MetadataBag());
|
||||
|
||||
if ($switchPalette) {
|
||||
$image->usePalette($switchPalette);
|
||||
}
|
||||
|
||||
return $image;
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Could not create empty image', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($string)
|
||||
{
|
||||
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($resource)
|
||||
{
|
||||
if (!is_resource($resource)) {
|
||||
throw new InvalidArgumentException('Variable does not contain a stream resource');
|
||||
}
|
||||
|
||||
$content = stream_get_contents($resource);
|
||||
|
||||
if (false === $content) {
|
||||
throw new InvalidArgumentException('Couldn\'t read given resource');
|
||||
}
|
||||
|
||||
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function font($file, $size, ColorInterface $color)
|
||||
{
|
||||
$gmagick = new \Gmagick();
|
||||
$gmagick->newimage(1, 1, 'transparent');
|
||||
|
||||
return new Font($gmagick, $file, $size, $color);
|
||||
}
|
||||
|
||||
private function createPalette(\Gmagick $gmagick)
|
||||
{
|
||||
switch ($gmagick->getimagecolorspace()) {
|
||||
case \Gmagick::COLORSPACE_SRGB:
|
||||
case \Gmagick::COLORSPACE_RGB:
|
||||
return new RGB();
|
||||
case \Gmagick::COLORSPACE_CMYK:
|
||||
return new CMYK();
|
||||
case \Gmagick::COLORSPACE_GRAY:
|
||||
return new Grayscale();
|
||||
default:
|
||||
throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported');
|
||||
}
|
||||
}
|
||||
|
||||
private function doLoad($content, MetadataBag $metadata)
|
||||
{
|
||||
try {
|
||||
$gmagick = new \Gmagick();
|
||||
$gmagick->readimageblob($content);
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException(
|
||||
'Could not load image from string', $e->getCode(), $e
|
||||
);
|
||||
}
|
||||
|
||||
return new Image($gmagick, $this->createPalette($gmagick), $metadata);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Gmagick;
|
||||
|
||||
use Imagine\Image\AbstractLayers;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
use Imagine\Exception\OutOfBoundsException;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
|
||||
class Layers extends AbstractLayers
|
||||
{
|
||||
/**
|
||||
* @var Image
|
||||
*/
|
||||
private $image;
|
||||
|
||||
/**
|
||||
* @var \Gmagick
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $layers = array();
|
||||
|
||||
/**
|
||||
* @var PaletteInterface
|
||||
*/
|
||||
private $palette;
|
||||
|
||||
public function __construct(Image $image, PaletteInterface $palette, \Gmagick $resource)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->resource = $resource;
|
||||
$this->palette = $palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function merge()
|
||||
{
|
||||
foreach ($this->layers as $offset => $image) {
|
||||
try {
|
||||
$this->resource->setimageindex($offset);
|
||||
$this->resource->setimage($image->getGmagick());
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coalesce()
|
||||
{
|
||||
throw new NotSupportedException('Gmagick does not support coalescing');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function animate($format, $delay, $loops)
|
||||
{
|
||||
if ('gif' !== strtolower($format)) {
|
||||
throw new NotSupportedException('Animated picture is currently only supported on gif');
|
||||
}
|
||||
|
||||
if (!is_int($loops) || $loops < 0) {
|
||||
throw new InvalidArgumentException('Loops must be a positive integer.');
|
||||
}
|
||||
|
||||
if (null !== $delay && (!is_int($delay) || $delay < 0)) {
|
||||
throw new InvalidArgumentException('Delay must be either null or a positive integer.');
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this as $offset => $layer) {
|
||||
$this->resource->setimageindex($offset);
|
||||
$this->resource->setimageformat($format);
|
||||
|
||||
if (null !== $delay) {
|
||||
$this->resource->setimagedelay($delay / 10);
|
||||
}
|
||||
|
||||
$this->resource->setimageiterations($loops);
|
||||
}
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to animate layers', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->extractAt($this->offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to extract layer at given offset
|
||||
*
|
||||
* @param integer $offset
|
||||
* @return Image
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function extractAt($offset)
|
||||
{
|
||||
if (!isset($this->layers[$offset])) {
|
||||
try {
|
||||
$this->resource->setimageindex($offset);
|
||||
$this->layers[$offset] = new Image($this->resource->getimage(), $this->palette, new MetadataBag());
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->layers[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
++$this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->offset < count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
try {
|
||||
return $this->resource->getnumberimages();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return is_int($offset) && $offset >= 0 && $offset < count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->extractAt($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $image)
|
||||
{
|
||||
if (!$image instanceof Image) {
|
||||
throw new InvalidArgumentException('Only a Gmagick Image can be used as layer');
|
||||
}
|
||||
|
||||
if (null === $offset) {
|
||||
$offset = count($this) - 1;
|
||||
} else {
|
||||
if (!is_int($offset)) {
|
||||
throw new InvalidArgumentException('Invalid offset for layer, it must be an integer');
|
||||
}
|
||||
|
||||
if (count($this) < $offset || 0 > $offset) {
|
||||
throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset));
|
||||
}
|
||||
|
||||
if (isset($this[$offset])) {
|
||||
unset($this[$offset]);
|
||||
$offset = $offset - 1;
|
||||
}
|
||||
}
|
||||
|
||||
$frame = $image->getGmagick();
|
||||
|
||||
try {
|
||||
if (count($this) > 0) {
|
||||
$this->resource->setimageindex($offset);
|
||||
$this->resource->nextimage();
|
||||
}
|
||||
$this->resource->addimage($frame);
|
||||
|
||||
/**
|
||||
* ugly hack to bypass issue https://bugs.php.net/bug.php?id=64623
|
||||
*/
|
||||
if (count($this) == 2) {
|
||||
$this->resource->setimageindex($offset+1);
|
||||
$this->resource->nextimage();
|
||||
$this->resource->addimage($frame);
|
||||
unset($this[0]);
|
||||
}
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Unable to set the layer', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->layers = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
try {
|
||||
$this->extractAt($offset);
|
||||
} catch (RuntimeException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->resource->setimageindex($offset);
|
||||
$this->resource->removeimage();
|
||||
} catch (\GmagickException $e) {
|
||||
throw new RuntimeException('Unable to remove layer', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Abstract font base class
|
||||
*/
|
||||
abstract class AbstractFont implements FontInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* @var ColorInterface
|
||||
*/
|
||||
protected $color;
|
||||
|
||||
/**
|
||||
* Constructs a font with specified $file, $size and $color
|
||||
*
|
||||
* The font size is to be specified in points (e.g. 10pt means 10)
|
||||
*
|
||||
* @param string $file
|
||||
* @param integer $size
|
||||
* @param ColorInterface $color
|
||||
*/
|
||||
public function __construct($file, $size, ColorInterface $color)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->size = $size;
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
final public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
final public function getSize()
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
final public function getColor()
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
|
||||
abstract class AbstractImage implements ImageInterface
|
||||
{
|
||||
/**
|
||||
* @var MetadataBag
|
||||
*/
|
||||
protected $metadata;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
if ($mode !== ImageInterface::THUMBNAIL_INSET &&
|
||||
$mode !== ImageInterface::THUMBNAIL_OUTBOUND) {
|
||||
throw new InvalidArgumentException('Invalid mode specified');
|
||||
}
|
||||
|
||||
$imageSize = $this->getSize();
|
||||
$ratios = array(
|
||||
$size->getWidth() / $imageSize->getWidth(),
|
||||
$size->getHeight() / $imageSize->getHeight()
|
||||
);
|
||||
|
||||
$thumbnail = $this->copy();
|
||||
|
||||
$thumbnail->usePalette($this->palette());
|
||||
$thumbnail->strip();
|
||||
// if target width is larger than image width
|
||||
// AND target height is longer than image height
|
||||
if ($size->contains($imageSize)) {
|
||||
return $thumbnail;
|
||||
}
|
||||
|
||||
if ($mode === ImageInterface::THUMBNAIL_INSET) {
|
||||
$ratio = min($ratios);
|
||||
} else {
|
||||
$ratio = max($ratios);
|
||||
}
|
||||
|
||||
if ($mode === ImageInterface::THUMBNAIL_OUTBOUND) {
|
||||
if (!$imageSize->contains($size)) {
|
||||
$size = new Box(
|
||||
min($imageSize->getWidth(), $size->getWidth()),
|
||||
min($imageSize->getHeight(), $size->getHeight())
|
||||
);
|
||||
} else {
|
||||
$imageSize = $thumbnail->getSize()->scale($ratio);
|
||||
$thumbnail->resize($imageSize, $filter);
|
||||
}
|
||||
$thumbnail->crop(new Point(
|
||||
max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)),
|
||||
max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2))
|
||||
), $size);
|
||||
} else {
|
||||
if (!$imageSize->contains($size)) {
|
||||
$imageSize = $imageSize->scale($ratio);
|
||||
$thumbnail->resize($imageSize, $filter);
|
||||
} else {
|
||||
$imageSize = $thumbnail->getSize()->scale($ratio);
|
||||
$thumbnail->resize($imageSize, $filter);
|
||||
}
|
||||
}
|
||||
|
||||
return $thumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given array of save options for backward compatibility with legacy names
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function updateSaveOptions(array $options)
|
||||
{
|
||||
// Preserve BC until version 1.0
|
||||
if (isset($options['quality']) && !isset($options['jpeg_quality'])) {
|
||||
$options['jpeg_quality'] = $options['quality'];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function metadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assures the metadata instance will be cloned, too
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
if ($this->metadata !== null) {
|
||||
$this->metadata = clone $this->metadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
use Imagine\Image\Metadata\DefaultMetadataReader;
|
||||
use Imagine\Image\Metadata\ExifMetadataReader;
|
||||
use Imagine\Image\Metadata\MetadataReaderInterface;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
|
||||
abstract class AbstractImagine implements ImagineInterface
|
||||
{
|
||||
/** @var MetadataReaderInterface */
|
||||
private $metadataReader;
|
||||
|
||||
/**
|
||||
* @param MetadataReaderInterface $metadataReader
|
||||
*
|
||||
* @return ImagineInterface
|
||||
*/
|
||||
public function setMetadataReader(MetadataReaderInterface $metadataReader)
|
||||
{
|
||||
$this->metadataReader = $metadataReader;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MetadataReaderInterface
|
||||
*/
|
||||
public function getMetadataReader()
|
||||
{
|
||||
if (null === $this->metadataReader) {
|
||||
if (ExifMetadataReader::isSupported()) {
|
||||
$this->metadataReader = new ExifMetadataReader();
|
||||
} else {
|
||||
$this->metadataReader = new DefaultMetadataReader();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->metadataReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a path that could be used with ImagineInterface::open and returns
|
||||
* a proper string.
|
||||
*
|
||||
* @param string|object $path
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws InvalidArgumentException In case the given path is invalid.
|
||||
*/
|
||||
protected function checkPath($path)
|
||||
{
|
||||
// provide compatibility with objects such as \SplFileInfo
|
||||
if (is_object($path) && method_exists($path, '__toString')) {
|
||||
$path = (string) $path;
|
||||
}
|
||||
|
||||
$handle = @fopen($path, 'r');
|
||||
|
||||
if (false === $handle) {
|
||||
throw new InvalidArgumentException(sprintf('File %s does not exist.', $path));
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
abstract class AbstractLayers implements LayersInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add(ImageInterface $image)
|
||||
{
|
||||
$this[] = $image;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($offset, ImageInterface $image)
|
||||
{
|
||||
$this[$offset] = $image;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function remove($offset)
|
||||
{
|
||||
unset($this[$offset]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($offset)
|
||||
{
|
||||
return $this[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($offset)
|
||||
{
|
||||
return isset($this[$offset]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* A box implementation
|
||||
*/
|
||||
final class Box implements BoxInterface
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $width;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $height;
|
||||
|
||||
/**
|
||||
* Constructs the Size with given width and height
|
||||
*
|
||||
* @param integer $width
|
||||
* @param integer $height
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($width, $height)
|
||||
{
|
||||
if ($height < 1 || $width < 1) {
|
||||
throw new InvalidArgumentException(sprintf('Length of either side cannot be 0 or negative, current size is %sx%s', $width, $height));
|
||||
}
|
||||
|
||||
$this->width = (int) $width;
|
||||
$this->height = (int) $height;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWidth()
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHeight()
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scale($ratio)
|
||||
{
|
||||
return new Box(round($ratio * $this->width), round($ratio * $this->height));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function increase($size)
|
||||
{
|
||||
return new Box((int) $size + $this->width, (int) $size + $this->height);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(BoxInterface $box, PointInterface $start = null)
|
||||
{
|
||||
$start = $start ? $start : new Point(0, 0);
|
||||
|
||||
return $start->in($this) && $this->width >= $box->getWidth() + $start->getX() && $this->height >= $box->getHeight() + $start->getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function square()
|
||||
{
|
||||
return $this->width * $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return sprintf('%dx%d px', $this->width, $this->height);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function widen($width)
|
||||
{
|
||||
return $this->scale($width / $this->width);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function heighten($height)
|
||||
{
|
||||
return $this->scale($height / $this->height);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image;
|
||||
|
||||
/**
|
||||
* Interface for a box
|
||||
*/
|
||||
interface BoxInterface
|
||||
{
|
||||
/**
|
||||
* Gets current image height
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getHeight();
|
||||
|
||||
/**
|
||||
* Gets current image width
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getWidth();
|
||||
|
||||
/**
|
||||
* Creates new BoxInterface instance with ratios applied to both sides
|
||||
*
|
||||
* @param float $ratio
|
||||
*
|
||||
* @return BoxInterface
|
||||
*/
|
||||
public function scale($ratio);
|
||||
|
||||
/**
|
||||
* Creates new BoxInterface, adding given size to both sides
|
||||
*
|
||||
* @param integer $size
|
||||
*
|
||||
* @return BoxInterface
|
||||
*/
|
||||
public function increase($size);
|
||||
|
||||
/**
|
||||
* Checks whether current box can fit given box at a given start position,
|
||||
* start position defaults to top left corner xy(0,0)
|
||||
*
|
||||
* @param BoxInterface $box
|
||||
* @param PointInterface $start
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function contains(BoxInterface $box, PointInterface $start = null);
|
||||
|
||||
/**
|
||||
* Gets current box square, useful for getting total number of pixels in a
|
||||
* given box
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function square();
|
||||
|
||||
/**
|
||||
* Returns a string representation of the current box
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Resizes box to given width, constraining proportions and returns the new box
|
||||
*
|
||||
* @param integer $width
|
||||
*
|
||||
* @return BoxInterface
|
||||
*/
|
||||
public function widen($width);
|
||||
|
||||
/**
|
||||
* Resizes box to given height, constraining proportions and returns the new box
|
||||
*
|
||||
* @param integer $height
|
||||
*
|
||||
* @return BoxInterface
|
||||
*/
|
||||
public function heighten($height);
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image\Fill;
|
||||
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* Interface for the fill
|
||||
*/
|
||||
interface FillInterface
|
||||
{
|
||||
/**
|
||||
* Gets color of the fill for the given position
|
||||
*
|
||||
* @param PointInterface $position
|
||||
*
|
||||
* @return ColorInterface
|
||||
*/
|
||||
public function getColor(PointInterface $position);
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Image\Fill\Gradient;
|
||||
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* Horizontal gradient fill
|
||||
*/
|
||||
final class Horizontal extends Linear
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDistance(PointInterface $position)
|
||||
{
|
||||
return $position->getX();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue