+
-
+
{{ action }}
-
+
diff --git a/frontend/src/components/utils/upload/FileUpload.vue b/dashboard/src/components/utils/upload/FileUpload.vue
similarity index 94%
rename from frontend/src/components/utils/upload/FileUpload.vue
rename to dashboard/src/components/utils/upload/FileUpload.vue
index 84272a18..53928d22 100644
--- a/frontend/src/components/utils/upload/FileUpload.vue
+++ b/dashboard/src/components/utils/upload/FileUpload.vue
@@ -35,14 +35,6 @@ limitations under the License.
const DEFAULT_STATUS_MSG = 'Drag your file here to upload or click to browse';
export default {
- $_veeValidate: {
- value() {
- return this.getFormData();
- },
- name() {
- return this.name;
- },
- },
name: 'FileUpload',
props: ['name', 'accept'],
data() {
diff --git a/frontend/src/components/utils/upload/FileUploadService.js b/dashboard/src/components/utils/upload/FileUploadService.js
similarity index 91%
rename from frontend/src/components/utils/upload/FileUploadService.js
rename to dashboard/src/components/utils/upload/FileUploadService.js
index 2c457c1b..19a8f0dc 100644
--- a/frontend/src/components/utils/upload/FileUploadService.js
+++ b/dashboard/src/components/utils/upload/FileUploadService.js
@@ -17,7 +17,7 @@ import axios from 'axios';
export default {
upload(url, formData, success, failure) {
- axios.post(url, formData, { headers: { 'x-handleError': false } })
+ axios.post(url, formData, { handleError: false })
.then(success)
.catch(failure);
},
diff --git a/frontend/src/directives/FocusDirective.js b/dashboard/src/directives/FocusDirective.js
similarity index 100%
rename from frontend/src/directives/FocusDirective.js
rename to dashboard/src/directives/FocusDirective.js
diff --git a/frontend/src/directives/SkillsOnMountDirective.js b/dashboard/src/directives/SkillsOnMountDirective.js
similarity index 92%
rename from frontend/src/directives/SkillsOnMountDirective.js
rename to dashboard/src/directives/SkillsOnMountDirective.js
index 77895b38..a8379340 100644
--- a/frontend/src/directives/SkillsOnMountDirective.js
+++ b/dashboard/src/directives/SkillsOnMountDirective.js
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import Vue from 'vue';
-import { SkillsReporter } from '@skills/skills-client-vue';
+import { SkillsReporter } from '@skilltree/skills-client-vue';
// Register a global custom directive
Vue.directive('skills-onMount', {
diff --git a/frontend/src/filters/DateFilter.js b/dashboard/src/filters/DateFilter.js
similarity index 92%
rename from frontend/src/filters/DateFilter.js
rename to dashboard/src/filters/DateFilter.js
index 6d167896..56606c0a 100644
--- a/frontend/src/filters/DateFilter.js
+++ b/dashboard/src/filters/DateFilter.js
@@ -16,10 +16,9 @@
import Vue from 'vue';
import moment from 'moment';
-const dateFormatter = value => moment(value).format('YYYY-MM-DD HH:mm');
+const dateFormatter = (value) => moment(value).format('YYYY-MM-DD HH:mm');
Vue.filter('date', dateFormatter);
-
// this allows to call this function from an js code; to learn more about that read about javascript modules
// import DateFilter from 'src/DateFilter.js'
// DateFilter(dateStrVAlue)
diff --git a/frontend/src/filters/NumberFilter.js b/dashboard/src/filters/NumberFilter.js
similarity index 93%
rename from frontend/src/filters/NumberFilter.js
rename to dashboard/src/filters/NumberFilter.js
index f6de0eac..ec808991 100644
--- a/frontend/src/filters/NumberFilter.js
+++ b/dashboard/src/filters/NumberFilter.js
@@ -16,10 +16,9 @@
import Vue from 'vue';
import numeral from 'numeral';
-const numberFormatter = value => numeral(value).format('0,0');
+const numberFormatter = (value) => numeral(value).format('0,0');
Vue.filter('number', numberFormatter);
-
// this allows to call this function from an js code; to learn more about that read about javascript modules
// import NumberFilter from 'src/NumberFilter.js'
// NumberFilter(myNumber)
diff --git a/frontend/src/filters/TruncateFilter.js b/dashboard/src/filters/TruncateFilter.js
similarity index 99%
rename from frontend/src/filters/TruncateFilter.js
rename to dashboard/src/filters/TruncateFilter.js
index b99d5d6a..499a1fa4 100644
--- a/frontend/src/filters/TruncateFilter.js
+++ b/dashboard/src/filters/TruncateFilter.js
@@ -33,7 +33,6 @@ const truncateFormatter = (strValue, truncateTo = 30) => {
};
Vue.filter('truncate', truncateFormatter);
-
// this allows to call this function from an js code; to learn more about that read about javascript modules
// import NumberFilter from 'src/NumberFilter.js'
// NumberFilter(myNumber)
diff --git a/frontend/src/interceptors/clientVersionInterceptor.js b/dashboard/src/interceptors/clientVersionInterceptor.js
similarity index 78%
rename from frontend/src/interceptors/clientVersionInterceptor.js
rename to dashboard/src/interceptors/clientVersionInterceptor.js
index 0304fe71..90f09484 100644
--- a/frontend/src/interceptors/clientVersionInterceptor.js
+++ b/dashboard/src/interceptors/clientVersionInterceptor.js
@@ -17,8 +17,10 @@ import axios from 'axios';
import store from '../store/store';
function handleFunction(config) {
- const incomingVersion = config.headers['skills-client-lib-version'];
- store.dispatch('updateLibVersionIfDifferent', incomingVersion);
+ if (config && config.headers && config.headers['skills-client-lib-version']) {
+ const incomingVersion = config.headers['skills-client-lib-version'];
+ store.dispatch('updateLibVersionIfDifferent', incomingVersion);
+ }
}
// apply interceptor on response
diff --git a/frontend/src/interceptors/errorHandler.js b/dashboard/src/interceptors/errorHandler.js
similarity index 70%
rename from frontend/src/interceptors/errorHandler.js
rename to dashboard/src/interceptors/errorHandler.js
index 5b0829ee..102a4c22 100644
--- a/frontend/src/interceptors/errorHandler.js
+++ b/dashboard/src/interceptors/errorHandler.js
@@ -17,13 +17,9 @@ import axios from 'axios';
import router from '../router';
import store from '../store/store';
-
function errorResponseHandler(error) {
// check if the caller wants to handle the error with displaying the errorPage/dialog
- if ((Object.prototype.hasOwnProperty.call(error.config, 'handleError') && error.config.handleError === false)
- || (error.config && error.config.headers && error.config.headers['x-handleError'] === false)) {
- // config.handleError does not appear to be propagated here regardless of whether or not it's set on the axios call
- // only properties defined on AxiosRequestConfig make it here
+ if (Object.prototype.hasOwnProperty.call(error.config, 'handleError') && error.config.handleError === false) {
return Promise.reject(error);
}
@@ -39,16 +35,26 @@ function errorResponseHandler(error) {
router.push(loginRoute);
}
} else if (errorCode === 403) {
- router.push({ name: 'NotAuthorizedPage' });
+ let explanation;
+ if (error.response && error.response.data && error.response.data.explanation) {
+ ({ explanation } = error.response.data);
+ }
+ router.push({ name: 'NotAuthorizedPage', params: { explanation } });
+ } else if (errorCode === 404) {
+ let explanation;
+ if (error.response && error.response.data && error.response.data.explanation) {
+ ({ explanation } = error.response.data);
+ }
+ router.push({ name: 'NotFoundPage', params: { explanation } });
} else {
router.push({ name: 'ErrorPage' });
}
- return Promise.reject(error);
+ return Promise.resolve({ data: {} });
}
// apply interceptor on response
axios.interceptors.response.use(
- response => response,
+ (response) => response,
errorResponseHandler,
);
diff --git a/frontend/src/main.js b/dashboard/src/main.js
similarity index 54%
rename from frontend/src/main.js
rename to dashboard/src/main.js
index a8103b17..e1339989 100644
--- a/frontend/src/main.js
+++ b/dashboard/src/main.js
@@ -18,8 +18,11 @@
import Vue from 'vue';
import BootstrapVue from 'bootstrap-vue';
import { ClientTable, ServerTable } from 'vue-tables-2';
-import { SkillsDirective } from '@skills/skills-client-vue';
-import VeeValidate from 'vee-validate';
+import { SkillsConfiguration, SkillsDirective, SkillsReporter } from '@skilltree/skills-client-vue';
+import {
+ localize, ValidationProvider, ValidationObserver, setInteractionMode,
+} from 'vee-validate';
+import en from 'vee-validate/dist/locale/en.json';
import VueApexCharts from 'vue-apexcharts';
import Vuex from 'vuex';
import InceptionConfigurer from './InceptionConfigurer';
@@ -38,13 +41,18 @@ import store from './store/store';
Vue.use(ClientTable, {}, false, 'bootstrap4', 'default');
Vue.use(ServerTable, {}, false, 'bootstrap4', 'default');
-Vue.use(VeeValidate);
+Vue.component('ValidationProvider', ValidationProvider);
+Vue.component('ValidationObserver', ValidationObserver);
Vue.use(Vuex);
Vue.use(VueApexCharts);
Vue.use(BootstrapVue);
Vue.use(SkillsDirective);
-VeeValidate.setMode('betterEager', () => ({ on: ['input'], debounce: 500 }));
+localize({
+ en,
+});
+
+setInteractionMode('custom', () => ({ on: ['input', 'change'] }));
Vue.component('apexchart', VueApexCharts);
@@ -58,6 +66,51 @@ require('./interceptors/clientVersionInterceptor');
require('vue-multiselect/dist/vue-multiselect.min.css');
+const isActiveProjectIdChange = (to, from) => to.params.projectId !== from.params.projectId;
+const isLoggedIn = () => store.getters.isAuthenticated;
+const isPki = () => store.getters.isPkiAuthenticated;
+
+router.beforeEach((to, from, next) => {
+ const requestAccountPath = '/request-root-account';
+ if (!isPki() && !isLoggedIn() && to.path !== requestAccountPath && store.getters.config.needToBootstrap) {
+ next({ path: requestAccountPath });
+ } else if (!isPki() && to.path === requestAccountPath && !store.getters.config.needToBootstrap) {
+ next({ path: '/' });
+ } else {
+ if (from.path !== '/error') {
+ store.commit('previousUrl', from.fullPath);
+ }
+ if (isActiveProjectIdChange(to, from)) {
+ store.commit('currentProjectId', to.params.projectId);
+ }
+ if (to.matched.some((record) => record.meta.requiresAuth)) {
+ // this route requires auth, check if logged in if not, redirect to login page.
+ if (!isLoggedIn()) {
+ const newRoute = { query: { redirect: to.fullPath } };
+ if (isPki()) {
+ newRoute.name = 'HomePage';
+ } else {
+ newRoute.name = 'Login';
+ }
+ next(newRoute);
+ } else {
+ next();
+ }
+ } else {
+ next();
+ }
+ }
+});
+
+router.afterEach((to) => {
+ if (to.meta.reportSkillId) {
+ SkillsConfiguration.afterConfigure()
+ .then(() => {
+ SkillsReporter.reportSkill(to.meta.reportSkillId);
+ });
+ }
+});
+
store.dispatch('loadConfigState').finally(() => {
RegisterValidators.init();
store.dispatch('restoreSessionIfAvailable').finally(() => {
diff --git a/frontend/src/router/index.js b/dashboard/src/router/index.js
similarity index 72%
rename from frontend/src/router/index.js
rename to dashboard/src/router/index.js
index 534fff0e..f4af45be 100644
--- a/frontend/src/router/index.js
+++ b/dashboard/src/router/index.js
@@ -15,7 +15,6 @@
*/
import Vue from 'vue';
import Router from 'vue-router';
-import { SkillsReporter, SkillsConfiguration } from '@skills/skills-client-vue';
import HomePage from '@/components/HomePage';
import MyProjects from '@/components/projects/MyProjects';
import LoginForm from '@/components/access/Login';
@@ -23,12 +22,12 @@ import RequestAccountForm from '@/components/access/RequestAccess';
import ProjectPage from '@/components/projects/ProjectPage';
import ErrorPage from '@/components/utils/ErrorPage';
import NotAuthorizedPage from '@/components/utils/NotAuthorizedPage';
+import NotFoundPage from '@/components/utils/NotFoundPage';
import SubjectPage from '@/components/subjects/SubjectPage';
import BadgePage from '@/components/badges/BadgePage';
import GlobalBadgePage from '@/components/badges/global/GlobalBadgePage';
import SkillPage from '@/components/skills/SkillPage';
import UserPage from '@/components/users/UserPage';
-import store from '@/store/store';
import GlobalSettings from '@/components/settings/GlobalSettings';
import GFMDescription from '@//components/utils/GFMDescription';
import InceptionSkills from '@//components/inception/InceptionSkills';
@@ -54,7 +53,13 @@ import UserSkillsPerformed from '@//components/users/UserSkillsPerformed';
import GeneralSettings from '@//components/settings/GeneralSettings';
import SecuritySettings from '@//components/settings/SecuritySettings';
import EmailSettings from '@//components/settings/EmailSettings';
+import SystemSettings from '@//components/settings/SystemSettings';
import { SECTION } from '@//components/metrics/SectionHelper';
+import ResetPassword from '@//components/access/ResetPassword';
+import RequestPasswordReset from '@//components/access/RequestPasswordReset';
+import RequestResetConfirmation from '@//components/access/RequestResetConfirmation';
+import ResetConfirmation from '@//components/access/ResetConfirmation';
+import ResetNotSupportedPage from '@//components/access/ResetNotSupportedPage';
Vue.use(Router);
@@ -98,6 +103,58 @@ const router = new Router({
requiresAuth: false,
},
},
+ {
+ path: '/forgot-password',
+ name: 'ForgotPassword',
+ component: RequestPasswordReset,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/reset-password/:resetToken',
+ name: 'ResetPassword',
+ component: ResetPassword,
+ props: true,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/forgot-password-confirmation',
+ name: 'RequestResetConfirmation',
+ component: RequestResetConfirmation,
+ props: true,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/reset-password-confirmation',
+ name: 'ResetConfirmation',
+ component: ResetConfirmation,
+ props: true,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/reset-not-supported',
+ name: 'ResetNotSupportedPage',
+ component: ResetNotSupportedPage,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/request-root-account',
+ name: 'RequestRootAccount',
+ component: RequestAccountForm,
+ props: { isRootAccount: true },
+ meta: {
+ requiresAuth: false,
+ },
+ },
{
path: '/error',
name: 'ErrorPage',
@@ -110,6 +167,16 @@ const router = new Router({
path: '/not-authorized',
name: 'NotAuthorizedPage',
component: NotAuthorizedPage,
+ props: true,
+ meta: {
+ requiresAuth: false,
+ },
+ },
+ {
+ path: '/not-found',
+ name: 'NotFoundPage',
+ component: NotFoundPage,
+ props: true,
meta: {
requiresAuth: false,
},
@@ -268,6 +335,69 @@ const router = new Router({
meta: { requiresAuth: true, reportSkillId: 'VisitUserStats', metricsSection: SECTION.USERS },
}],
},
+ {
+ path: '/projects/:projectId/subjects/:subjectId/users/:userId',
+ component: UserPage,
+ meta: { requiresAuth: true },
+ children: [{
+ name: 'ClientDisplayPreviewSubject',
+ path: '',
+ component: ClientDisplayPreview,
+ meta: { requiresAuth: true, reportSkillId: 'VisitClientDisplay' },
+ }, {
+ name: 'UserSkillEventsSubject',
+ path: 'skillEvents',
+ component: UserSkillsPerformed,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserPerformedSkills' },
+ }, {
+ name: 'UserMetricsSubject',
+ path: 'metrics',
+ component: SectionMetrics,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserStats', metricsSection: SECTION.USERS },
+ }],
+ },
+ {
+ path: '/projects/:projectId/subjects/:subjectId/skills/:skillId/users/:userId',
+ component: UserPage,
+ meta: { requiresAuth: true },
+ children: [{
+ name: 'ClientDisplayPreviewSkill',
+ path: '',
+ component: ClientDisplayPreview,
+ meta: { requiresAuth: true, reportSkillId: 'VisitClientDisplay' },
+ }, {
+ name: 'UserSkillEventsSkill',
+ path: 'skillEvents',
+ component: UserSkillsPerformed,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserPerformedSkills' },
+ }, {
+ name: 'UserMetricsSkill',
+ path: 'metrics',
+ component: SectionMetrics,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserStats', metricsSection: SECTION.USERS },
+ }],
+ },
+ {
+ path: '/projects/:projectId/badges/:badgeId/users/:userId',
+ component: UserPage,
+ meta: { requiresAuth: true },
+ children: [{
+ name: 'ClientDisplayPreviewBadge',
+ path: '',
+ component: ClientDisplayPreview,
+ meta: { requiresAuth: true, reportSkillId: 'VisitClientDisplay' },
+ }, {
+ name: 'UserSkillEventsBadge',
+ path: 'skillEvents',
+ component: UserSkillsPerformed,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserPerformedSkills' },
+ }, {
+ name: 'UserMetricsBadge',
+ path: 'metrics',
+ component: SectionMetrics,
+ meta: { requiresAuth: true, reportSkillId: 'VisitUserStats', metricsSection: SECTION.USERS },
+ }],
+ },
{
path: '/settings',
component: GlobalSettings,
@@ -289,6 +419,11 @@ const router = new Router({
path: 'email',
component: EmailSettings,
meta: { requiresAuth: true },
+ }, {
+ name: 'SystemSettings',
+ path: 'system',
+ component: SystemSettings,
+ meta: { requiresAuth: true },
}],
},
{
@@ -341,41 +476,4 @@ const router = new Router({
],
});
-const isActiveProjectIdChange = (to, from) => to.params.projectId !== from.params.projectId;
-const isLoggedIn = () => store.getters.isAuthenticated;
-
-router.beforeEach((to, from, next) => {
- if (from.path !== '/error') {
- store.commit('previousUrl', from.fullPath);
- }
- if (isActiveProjectIdChange(to, from)) {
- store.commit('currentProjectId', to.params.projectId);
- }
- if (to.matched.some(record => record.meta.requiresAuth)) {
- // this route requires auth, check if logged in if not, redirect to login page.
- if (!isLoggedIn()) {
- const newRoute = { query: { redirect: to.fullPath } };
- if (store.getters.isPkiAuthenticated) {
- newRoute.name = 'HomePage';
- } else {
- newRoute.name = 'Login';
- }
- next(newRoute);
- } else {
- next();
- }
- } else {
- next();
- }
-});
-
-router.afterEach((to) => {
- if (to.meta.reportSkillId) {
- SkillsConfiguration.afterConfigure()
- .then(() => {
- SkillsReporter.reportSkill(to.meta.reportSkillId);
- });
- }
-});
-
export default router;
diff --git a/frontend/src/store/modules/access.js b/dashboard/src/store/modules/access.js
similarity index 74%
rename from frontend/src/store/modules/access.js
rename to dashboard/src/store/modules/access.js
index 47094b2c..91810f32 100644
--- a/frontend/src/store/modules/access.js
+++ b/dashboard/src/store/modules/access.js
@@ -21,7 +21,15 @@ const actions = {
AccessService.hasRole('ROLE_SUPERVISOR').then((result) => {
commit('supervisor', result);
resolve(result);
- }).catch(error => reject(error));
+ }).catch((error) => reject(error));
+ });
+ },
+ isRoot({ commit }) {
+ return new Promise((resolve, reject) => {
+ AccessService.hasRole('ROLE_SUPER_DUPER_USER').then((result) => {
+ commit('root', result);
+ resolve(result);
+ }).catch((error) => reject(error));
});
},
};
@@ -30,16 +38,23 @@ const mutations = {
supervisor(state, value) {
state.isSupervisor = value;
},
+ root(state, value) {
+ state.isRoot = value;
+ },
};
const getters = {
isSupervisor(state) {
return state.isSupervisor;
},
+ isRoot(state) {
+ return state.isRoot;
+ },
};
const state = {
isSupervisor: null,
+ isRoot: null,
};
export default {
diff --git a/frontend/src/store/modules/auth.js b/dashboard/src/store/modules/auth.js
similarity index 76%
rename from frontend/src/store/modules/auth.js
rename to dashboard/src/store/modules/auth.js
index 2a0797cf..99c15f40 100644
--- a/frontend/src/store/modules/auth.js
+++ b/dashboard/src/store/modules/auth.js
@@ -14,24 +14,21 @@
* limitations under the License.
*/
import axios from 'axios';
-import { SkillsConfiguration } from '@skills/skills-client-vue';
+import { SkillsConfiguration } from '@skilltree/skills-client-vue';
import router from '../../router';
const getters = {
userInfo(state) {
return state.userInfo;
},
- isAuthenticated(state) {
+ isAuthenticated(state, gettersParam) {
return (
state.token !== null
- || state.pkiAuth
+ || gettersParam.isPkiAuthenticated
|| state.localAuth
|| state.oAuthAuth
) && state.userInfo !== null;
},
- isPkiAuthenticated(state) {
- return state.pkiAuth;
- },
};
const mutations = {
@@ -68,57 +65,69 @@ const mutations = {
},
};
+const handleLogin = (commit, dispatch, result) => {
+ const token = result.headers.authorization;
+ let expirationDate;
+ // special handling for oAuth
+ if (result.headers.tokenexpirationtimestamp) {
+ expirationDate = new Date(Number(result.headers.tokenexpirationtimestamp));
+ dispatch('setLogoutTimer', expirationDate);
+ }
+ commit('authUser', {
+ token,
+ expirationDate,
+ });
+};
+
const actions = {
signup({ commit, dispatch }, authData) {
return new Promise((resolve, reject) => {
- axios.put('/createAccount', authData)
+ const url = authData.isRootAccount ? '/createRootAccount' : '/createAccount';
+ axios.put(url, authData)
.then((result) => {
if (result) {
- const token = result.headers.authorization;
- let expirationDate;
- if (result.headers.tokenexpirationtimestamp) {
- expirationDate = new Date(Number(result.headers.tokenexpirationtimestamp));
- dispatch('setLogoutTimer', expirationDate);
- }
- commit('authUser', { token, expirationDate });
+ handleLogin(commit, dispatch, result);
dispatch('fetchUser')
.then(() => {
- resolve(result);
+ if (authData.isRootAccount) {
+ // when creating root account for the first time, reload the config state
+ // at a minimum it will update the flag indicating whether root user needs to be created
+ dispatch('loadConfigState')
+ .then(() => {
+ resolve(result);
+ });
+ } else {
+ resolve(result);
+ }
});
}
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
login({ commit, dispatch }, authData) {
return new Promise((resolve, reject) => {
axios.post('/performLogin', authData, { handleError: false })
.then((result) => {
- const token = result.headers.authorization;
- let expirationDate;
- if (result.headers.tokenexpirationtimestamp) {
- expirationDate = new Date(Number(result.headers.tokenexpirationtimestamp));
- dispatch('setLogoutTimer', expirationDate);
- }
- commit('authUser', { token, expirationDate });
+ handleLogin(commit, dispatch, result);
dispatch('fetchUser')
.then(() => {
resolve(result);
});
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
oAuth2Login({ commit }, oAuthId) {
commit('oAuth2AuthUser');
window.location = `/oauth2/authorization/${oAuthId}`;
},
- restoreSessionIfAvailable({ commit, dispatch, state }) {
+ restoreSessionIfAvailable({
+ commit, dispatch, state, getters: gettersParam,
+ }) {
return new Promise((resolve, reject) => {
let reAuthenticated = false;
const token = localStorage.getItem('token');
- const localAuth = (localStorage.getItem('localAuth') === 'true');
- const oAuthAuth = (localStorage.getItem('oAuthAuth') === 'true');
if (token) {
let tokenExpired = true;
let expirationDate = localStorage.getItem('expirationDate');
@@ -145,17 +154,13 @@ const actions = {
dispatch('fetchUser', false).then(() => {
if (state.userInfo) {
reAuthenticated = true;
- if (!localAuth && !oAuthAuth) {
- state.pkiAuth = true;
- } else {
- state.localAuth = true;
- }
+ state.localAuth = !gettersParam.isPkiAuthenticated;
} else {
// cannot obtain userInfo, so clear any other lingering auth data
commit('clearAuthData');
}
resolve(reAuthenticated);
- }).catch(error => reject(error));
+ }).catch((error) => reject(error));
}
});
},
@@ -180,7 +185,7 @@ const actions = {
}
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
@@ -188,7 +193,6 @@ const actions = {
const state = {
token: null,
userInfo: null,
- pkiAuth: false,
localAuth: false,
oAuthAuth: false,
};
diff --git a/frontend/src/store/modules/badges.js b/dashboard/src/store/modules/badges.js
similarity index 94%
rename from frontend/src/store/modules/badges.js
rename to dashboard/src/store/modules/badges.js
index e3cf2d52..ecc56b71 100644
--- a/frontend/src/store/modules/badges.js
+++ b/dashboard/src/store/modules/badges.js
@@ -36,7 +36,7 @@ const actions = {
commit('setBadge', response);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
loadGlobalBadgeDetailsState({ commit }, payload) {
@@ -46,7 +46,7 @@ const actions = {
commit('setBadge', response);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
diff --git a/frontend/src/store/modules/config.js b/dashboard/src/store/modules/config.js
similarity index 90%
rename from frontend/src/store/modules/config.js
rename to dashboard/src/store/modules/config.js
index 5eb4c176..a9145cc6 100644
--- a/frontend/src/store/modules/config.js
+++ b/dashboard/src/store/modules/config.js
@@ -19,6 +19,9 @@ const getters = {
config(state) {
return state.config;
},
+ isPkiAuthenticated(state) {
+ return state.config.authMode === 'PKI';
+ },
};
const mutations = {
@@ -35,7 +38,7 @@ const actions = {
commit('setConfig', response);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
diff --git a/frontend/src/store/modules/libVersion.js b/dashboard/src/store/modules/libVersion.js
similarity index 100%
rename from frontend/src/store/modules/libVersion.js
rename to dashboard/src/store/modules/libVersion.js
diff --git a/frontend/src/store/modules/projects.js b/dashboard/src/store/modules/projects.js
similarity index 96%
rename from frontend/src/store/modules/projects.js
rename to dashboard/src/store/modules/projects.js
index 76aea86b..788e7185 100644
--- a/frontend/src/store/modules/projects.js
+++ b/dashboard/src/store/modules/projects.js
@@ -35,7 +35,7 @@ const actions = {
commit('setProject', response);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
diff --git a/frontend/src/store/modules/subjects.js b/dashboard/src/store/modules/subjects.js
similarity index 96%
rename from frontend/src/store/modules/subjects.js
rename to dashboard/src/store/modules/subjects.js
index fd35002e..278e20ea 100644
--- a/frontend/src/store/modules/subjects.js
+++ b/dashboard/src/store/modules/subjects.js
@@ -35,7 +35,7 @@ const actions = {
commit('setSubject', response);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
diff --git a/frontend/src/store/modules/users.js b/dashboard/src/store/modules/users.js
similarity index 97%
rename from frontend/src/store/modules/users.js
rename to dashboard/src/store/modules/users.js
index 25b193d4..b1fa8852 100644
--- a/frontend/src/store/modules/users.js
+++ b/dashboard/src/store/modules/users.js
@@ -54,7 +54,7 @@ const actions = {
commit('setUserTotalPoints', response.userTotalPoints);
resolve(response);
})
- .catch(error => reject(error));
+ .catch((error) => reject(error));
});
},
};
diff --git a/frontend/src/store/store.js b/dashboard/src/store/store.js
similarity index 92%
rename from frontend/src/store/store.js
rename to dashboard/src/store/store.js
index 0cb6c9a1..227b6937 100644
--- a/frontend/src/store/store.js
+++ b/dashboard/src/store/store.js
@@ -30,6 +30,7 @@ export default new Vuex.Store({
state: {
projectId: '',
previousUrl: '',
+ projectSearch: '',
},
mutations: {
currentProjectId(state, projectId) {
@@ -38,6 +39,9 @@ export default new Vuex.Store({
previousUrl(state, previousUrl) {
state.previousUrl = previousUrl;
},
+ projectSearch(state, projectSearch) {
+ state.projectSearch = projectSearch;
+ },
},
modules: {
auth,
diff --git a/frontend/src/styles/palette.scss b/dashboard/src/styles/palette.scss
similarity index 100%
rename from frontend/src/styles/palette.scss
rename to dashboard/src/styles/palette.scss
diff --git a/frontend/src/styles/utils.css b/dashboard/src/styles/utils.css
similarity index 100%
rename from frontend/src/styles/utils.css
rename to dashboard/src/styles/utils.css
diff --git a/frontend/src/validators/CustomDescriptionValidator.js b/dashboard/src/validators/CustomDescriptionValidator.js
similarity index 78%
rename from frontend/src/validators/CustomDescriptionValidator.js
rename to dashboard/src/validators/CustomDescriptionValidator.js
index c1845d3d..ecf5a339 100644
--- a/frontend/src/validators/CustomDescriptionValidator.js
+++ b/dashboard/src/validators/CustomDescriptionValidator.js
@@ -13,23 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import VeeValidate from 'vee-validate';
+import { extend } from 'vee-validate';
import store from '../store/store';
import CustomValidatorService from './CustomValidatorsService';
const validator = {
- getMessage: field => `${field} - ${store.getters.config.paragraphValidationMessage}.`,
+ message: (field) => `${field} - ${store.getters.config.paragraphValidationMessage}.`,
validate(value) {
if (!store.getters.config.paragraphValidationRegex) {
return true;
}
- return CustomValidatorService.validateDescription(value).then(result => result.valid);
+ return CustomValidatorService.validateDescription(value).then((result) => result.valid);
},
};
-VeeValidate.Validator.extend('customDescriptionValidator', validator, {
- immediate: false,
-});
+extend('customDescriptionValidator', validator);
export default validator;
diff --git a/frontend/src/validators/CustomNameValidator.js b/dashboard/src/validators/CustomNameValidator.js
similarity index 74%
rename from frontend/src/validators/CustomNameValidator.js
rename to dashboard/src/validators/CustomNameValidator.js
index d2667d7c..7f8ed211 100644
--- a/frontend/src/validators/CustomNameValidator.js
+++ b/dashboard/src/validators/CustomNameValidator.js
@@ -13,23 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import VeeValidate from 'vee-validate';
+import { extend } from 'vee-validate';
import store from '../store/store';
import CustomValidatorService from './CustomValidatorsService';
const validator = {
- getMessage: field => `${field} - ${store.getters.config.nameValidationMessage}.`,
+ message: (field) => `${field} - ${store.getters.config.nameValidationMessage}.`,
validate(value) {
if (!store.getters.config.nameValidationRegex) {
return true;
}
- return CustomValidatorService.validateName(value).then(result => result.valid);
+ return CustomValidatorService.validateName(value).then((result) => result.valid);
},
};
-VeeValidate.Validator.extend('customNameValidator', validator, {
- immediate: false,
-});
+extend('customNameValidator', validator);
export default validator;
diff --git a/frontend/src/validators/CustomValidatorsService.js b/dashboard/src/validators/CustomValidatorsService.js
similarity index 88%
rename from frontend/src/validators/CustomValidatorsService.js
rename to dashboard/src/validators/CustomValidatorsService.js
index f45c070c..fa15a0bf 100644
--- a/frontend/src/validators/CustomValidatorsService.js
+++ b/dashboard/src/validators/CustomValidatorsService.js
@@ -20,12 +20,12 @@ export default {
const body = {
value: description,
};
- return axios.post('/app/validation/description', body).then(result => result.data);
+ return axios.post('/app/validation/description', body).then((result) => result.data);
},
validateName(name) {
const body = {
value: name,
};
- return axios.post('/app/validation/name', body).then(result => result.data);
+ return axios.post('/app/validation/name', body).then((result) => result.data);
},
};
diff --git a/frontend/src/validators/NumConvertUtil.js b/dashboard/src/validators/NumConvertUtil.js
similarity index 100%
rename from frontend/src/validators/NumConvertUtil.js
rename to dashboard/src/validators/NumConvertUtil.js
diff --git a/frontend/src/validators/OptionalNumericValidator.js b/dashboard/src/validators/OptionalNumericValidator.js
similarity index 82%
rename from frontend/src/validators/OptionalNumericValidator.js
rename to dashboard/src/validators/OptionalNumericValidator.js
index 4eaaf41c..c695d1d2 100644
--- a/frontend/src/validators/OptionalNumericValidator.js
+++ b/dashboard/src/validators/OptionalNumericValidator.js
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import VeeValidate from 'vee-validate';
+import { extend } from 'vee-validate';
const numericRegex = /^[0-9]+$/;
const validator = {
- getMessage: field => `The ${field} field may only contain numeric characters.`,
+ message: (field) => `${field} may only contain numeric characters.`,
validate(value) {
const testValue = (val) => {
const strValue = String(val);
@@ -37,8 +37,6 @@ const validator = {
},
};
-VeeValidate.Validator.extend('optionalNumeric', validator, {
- immediate: false,
-});
+extend('optionalNumeric', validator);
export default validator;
diff --git a/dashboard/src/validators/RegisterValidators.js b/dashboard/src/validators/RegisterValidators.js
new file mode 100644
index 00000000..f8d8bc05
--- /dev/null
+++ b/dashboard/src/validators/RegisterValidators.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { extend, localize } from 'vee-validate';
+import { required } from 'vee-validate/dist/rules';
+import './OptionalNumericValidator';
+import './CustomDescriptionValidator';
+import './CustomNameValidator';
+import ValidatorFactory from './ValidatorFactory';
+import store from '../store/store';
+
+export default {
+ init() {
+ extend('maxDescriptionLength', ValidatorFactory.newCharLengthValidator(store.getters.config.descriptionMaxLength));
+ extend('maxFirstNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxFirstNameLength));
+ extend('maxLastNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxLastNameLength));
+ extend('maxNicknameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxNicknameLength));
+
+ extend('minUsernameLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minUsernameLength));
+
+ extend('minPasswordLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minPasswordLength));
+ extend('maxPasswordLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxPasswordLength));
+
+ extend('minIdLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minIdLength));
+ extend('maxIdLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxIdLength));
+
+ extend('minNameLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minNameLength));
+
+ extend('maxBadgeNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxBadgeNameLength));
+ extend('maxProjectNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxProjectNameLength));
+ extend('maxSkillNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxSkillNameLength));
+ extend('maxSubjectNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxSubjectNameLength));
+ extend('maxLevelNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxLevelNameLength));
+
+ extend('maxSkillVersion', ValidatorFactory.newMaxNumValidator(store.getters.config.maxSkillVersion));
+ extend('maxPointIncrement', ValidatorFactory.newMaxNumValidator(store.getters.config.maxPointIncrement));
+ extend('maxNumPerformToCompletion', ValidatorFactory.newMaxNumValidator(store.getters.config.maxNumPerformToCompletion));
+ extend('maxNumPointIncrementMaxOccurrences', ValidatorFactory.newMaxNumValidator(store.getters.config.maxNumPointIncrementMaxOccurrences));
+
+ extend('userNoSpaceInUserIdInNonPkiMode', ValidatorFactory.newUserObjNoSpacesValidatorInNonPkiMode(store.getters.isPkiAuthenticated));
+
+ extend('required', required);
+
+ localize({
+ en: {
+ messages: {
+ alpha: '{_field_} may only contain alphabetic characters',
+ alpha_num: '{_field_} may only contain alpha-numeric characters',
+ alpha_dash: '{_field_} may contain alpha-numeric characters as well as dashes and underscores',
+ alpha_spaces: '{_field_} may only contain alphabetic characters as well as spaces',
+ between: '{_field_} must be between {min} and {max}',
+ confirmed: '{_field_} confirmation does not match',
+ digits: '{_field_} must be numeric and exactly contain {length} digits',
+ dimensions: '{_field_} must be {width} pixels by {height} pixels',
+ email: '{_field_} must be a valid email',
+ excluded: '{_field_} is not a valid value',
+ ext: '{_field_} is not a valid file',
+ image: '{_field_} must be an image',
+ integer: '{_field_} must be an integer',
+ length: '{_field_} must be {length} long',
+ max_value: '{_field_} must be {max} or less',
+ max: '{_field_} may not be greater than {length} characters',
+ mimes: '{_field_} must have a valid file type',
+ min_value: '{_field_} must be {min} or more',
+ min: '{_field_} must be at least {length} characters',
+ numeric: '{_field_} may only contain numeric characters',
+ oneOf: '{_field_} is not a valid value',
+ regex: '{_field_} format is invalid',
+ required_if: '{_field_} is required',
+ required: '{_field_} is required',
+ size: '{_field_} size must be less than {size}KB',
+ },
+ },
+ });
+ },
+};
diff --git a/frontend/src/validators/ValidatorFactory.js b/dashboard/src/validators/ValidatorFactory.js
similarity index 80%
rename from frontend/src/validators/ValidatorFactory.js
rename to dashboard/src/validators/ValidatorFactory.js
index e687b0c0..2846e84c 100644
--- a/frontend/src/validators/ValidatorFactory.js
+++ b/dashboard/src/validators/ValidatorFactory.js
@@ -18,7 +18,7 @@ import NumConvertUtil from './NumConvertUtil';
export default {
newCharLengthValidator(maxLength) {
return {
- getMessage: field => `${field} cannot exceed ${maxLength} characters.`,
+ message: (field) => `${field} cannot exceed ${maxLength} characters.`,
validate(value) {
if (value.length > NumConvertUtil.toInt(maxLength)) {
return false;
@@ -29,7 +29,7 @@ export default {
},
newCharMinLengthValidator(maxLength) {
return {
- getMessage: field => `${field} cannot be less than ${maxLength} characters.`,
+ message: (field) => `${field} cannot be less than ${maxLength} characters.`,
validate(value) {
if (value.length < NumConvertUtil.toInt(maxLength)) {
return false;
@@ -40,7 +40,7 @@ export default {
},
newMaxNumValidator(maxNum) {
return {
- getMessage: field => `${field} cannot exceed ${maxNum}.`,
+ message: (field) => `${field} cannot exceed ${maxNum}.`,
validate(value) {
if (NumConvertUtil.toInt(value) > NumConvertUtil.toInt(maxNum)) {
return false;
@@ -51,13 +51,11 @@ export default {
},
newUserObjNoSpacesValidatorInNonPkiMode(isPkiMode) {
return {
- getMessage: field => `The ${field} field may not contain spaces`,
+ message: (field) => `${field} may not contain spaces`,
validate(value) {
if (isPkiMode || !value.userId) {
return true;
}
- // const isValid = !value.userId.match(/^[0-9a-zA-Z]+$/);
- // return !isValid;
const hasSpaces = value.userId.indexOf(' ') >= 0;
return !hasSpaces;
},
diff --git a/frontend/test/e2e/custom-assertions/elementCount.js b/dashboard/test/e2e/custom-assertions/elementCount.js
similarity index 100%
rename from frontend/test/e2e/custom-assertions/elementCount.js
rename to dashboard/test/e2e/custom-assertions/elementCount.js
diff --git a/frontend/test/e2e/nightwatch.conf.js b/dashboard/test/e2e/nightwatch.conf.js
similarity index 100%
rename from frontend/test/e2e/nightwatch.conf.js
rename to dashboard/test/e2e/nightwatch.conf.js
diff --git a/frontend/test/e2e/runner.js b/dashboard/test/e2e/runner.js
similarity index 100%
rename from frontend/test/e2e/runner.js
rename to dashboard/test/e2e/runner.js
diff --git a/frontend/test/e2e/specs/test.js b/dashboard/test/e2e/specs/test.js
similarity index 100%
rename from frontend/test/e2e/specs/test.js
rename to dashboard/test/e2e/specs/test.js
diff --git a/frontend/test/unit/.eslintrc b/dashboard/test/unit/.eslintrc
similarity index 100%
rename from frontend/test/unit/.eslintrc
rename to dashboard/test/unit/.eslintrc
diff --git a/frontend/test/unit/jest.conf.js b/dashboard/test/unit/jest.conf.js
similarity index 100%
rename from frontend/test/unit/jest.conf.js
rename to dashboard/test/unit/jest.conf.js
diff --git a/frontend/test/unit/setup.js b/dashboard/test/unit/setup.js
similarity index 100%
rename from frontend/test/unit/setup.js
rename to dashboard/test/unit/setup.js
diff --git a/frontend/test/unit/specs/HelloWorld.spec.js b/dashboard/test/unit/specs/HelloWorld.spec.js
similarity index 100%
rename from frontend/test/unit/specs/HelloWorld.spec.js
rename to dashboard/test/unit/specs/HelloWorld.spec.js
diff --git a/frontend/vue.config.js b/dashboard/vue.config.js
similarity index 95%
rename from frontend/vue.config.js
rename to dashboard/vue.config.js
index 9fab002e..89a00578 100644
--- a/frontend/vue.config.js
+++ b/dashboard/vue.config.js
@@ -66,6 +66,7 @@ module.exports = {
'/logout': proxyConf,
'/createAccount': proxyConf,
'/grantFirstRoot': proxyConf,
+ '/createRootAccount': proxyConf,
'/oauth2': proxyConf,
'/login': proxyConf,
'/static': proxyConf,
@@ -75,6 +76,9 @@ module.exports = {
'/public': proxyConf,
'/metrics' : proxyConf,
'/skills-websocket' : proxyConf,
+ '/resetPassword' : proxyConf,
+ '/performPasswordReset' : proxyConf,
+ '/isFeatureSupported' : proxyConf,
},
},
configureWebpack: {
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000..a844dca0
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,44 @@
+FROM openjdk:14.0.2-slim-buster
+
+# install netcat so start script can use nc command to wait for DB to come up
+RUN apt-get update
+RUN apt-get -y install netcat-openbsd
+RUN apt-get -y install wget
+
+ARG BUILD_DATE
+ARG VERSION
+ARG VCS_REF=unspecified
+
+LABEL org.label-schema.build-date=$BUILD_DATE
+LABEL org.label-schema.license=Apache-2.0
+LABEL org.label-schema.name=SkillTree
+LABEL org.label-schema.schema-version=$VERSION
+LABEL org.label-schema.url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.label-schema.usage=https://code.nsa.gov/skills-docs/
+LABEL org.label-schema.vcs-ref=$VCS_REF
+LABEL org.label-schema.vendor=SkillTree
+LABEL org.label-schema.vcs-url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.label-schema.vendor=SkillTree
+LABEL org.label-schema.version=7.8.0
+
+LABEL org.opencontainers.image.created=$BUILD_DATE
+LABEL org.opencontainers.image.documentation=https://code.nsa.gov/skills-docs/
+LABEL org.opencontainers.image.licenses=Apache-2.0
+LABEL org.opencontainers.image.revision=$VCS_REF
+LABEL org.opencontainers.image.source=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.opencontainers.image.title=SkillTree
+LABEL org.opencontainers.image.url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.opencontainers.image.vendor=SkillTree
+LABEL org.opencontainers.image.version=$VERSION
+
+VOLUME /tmp
+
+EXPOSE 80
+EXPOSE 8443
+
+RUN mkdir /data
+
+COPY skills-service.jar skills.jar
+COPY startup.sh startup.sh
+
+ENTRYPOINT ["bash", "/startup.sh"]
diff --git a/docker/build-and-push.sh b/docker/build-and-push.sh
new file mode 100755
index 00000000..4b8b5ebc
--- /dev/null
+++ b/docker/build-and-push.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+# exit if a command returns non-zero exit code
+set -e
+
+IMG_NAME=${1:-"skilltree/skills-service"}
+DOCKER_USER=${2:-"${docker_username}"}
+DOCKER_PASS=${3:-"${docker_password}"}
+
+./build-docker-image.sh $IMG_NAME
+
+docker login --username "${DOCKER_USER}" --password "${DOCKER_PASS}"
+docker push $IMG_NAME
diff --git a/docker/build-docker-image.sh b/docker/build-docker-image.sh
new file mode 100755
index 00000000..5f09e312
--- /dev/null
+++ b/docker/build-docker-image.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# exit if a command returns non-zero exit code
+set -e
+echo "Building docker image..."
+
+BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
+BUILD_DATE_TAG=$(date -u +'%Y%m%dT%H%M%SZ')
+VERSION=$(cd ../ && mvn org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate -Dexpression=project.version -q -DforceStdout)
+JAR_LOC=$(ls ../service/target/*.jar)
+IMG_NAME=${1:-"skilltree/skills-service"}
+if [ "$JAR_LOC" = "" ]
+then
+ echo "Failed to find jar"
+ exit
+fi
+rm -f skills-service.jar
+cp $JAR_LOC skills-service.jar
+
+IMAGE_TAG="${VERSION}"
+if [[ "$VERSION" == *SNAPSHOT ]]
+then
+ IMAGE_TAG="${VERSION}_${BUILD_DATE_TAG}"
+fi
+
+echo "-------------------"
+echo "BUILD_DATE=[$BUILD_DATE]"
+echo "VERSION=[$VERSION]"
+echo "JAR_LOC=[$JAR_LOC]"
+echo "VCS_REF=[$VCS_REF]"
+echo "IMG_NAME=[$IMG_NAME]"
+echo "IMAGE_TAG=[$IMAGE_TAG]"
+echo "-------------------"
+
+docker build --no-cache=true --build-arg BUILD_DATE=$BUILD_DATE --build-arg VERSION=$VERSION --build-arg VCS_REF=$GITHUB_SHA -t $IMG_NAME -t "${IMG_NAME}:${IMAGE_TAG}" .
diff --git a/docker/skills-stomp-broker/Dockerfile b/docker/skills-stomp-broker/Dockerfile
new file mode 100644
index 00000000..aefbace8
--- /dev/null
+++ b/docker/skills-stomp-broker/Dockerfile
@@ -0,0 +1,26 @@
+FROM rabbitmq:3.8-management-alpine
+
+ADD join.sh /
+COPY enabled_plugins /etc/rabbitmq/enabled_plugins
+
+RUN apk add --no-cache bind-tools
+
+RUN sed -i 's/exec "$@"/\
+ sh -c "while ! nc -z localhost 15672; do sleep 0.1; done; sleep 3; .\/join.sh" \&\
+ \nexec "$@"/' /usr/local/bin/docker-entrypoint.sh
+
+LABEL org.label-schema.license=Apache-2.0
+LABEL org.label-schema.name=SkillTree
+LABEL org.label-schema.url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.label-schema.usage=https://code.nsa.gov/skills-docs/
+LABEL org.label-schema.vendor=SkillTree
+LABEL org.label-schema.vcs-url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.label-schema.vendor=SkillTree
+LABEL org.label-schema.version=7.8.0
+
+LABEL org.opencontainers.image.documentation=https://code.nsa.gov/skills-docs/
+LABEL org.opencontainers.image.licenses=Apache-2.0
+LABEL org.opencontainers.image.source=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.opencontainers.image.title=SkillTree
+LABEL org.opencontainers.image.url=https://github.com/NationalSecurityAgency/skills-service
+LABEL org.opencontainers.image.vendor=SkillTree
\ No newline at end of file
diff --git a/docker/skills-stomp-broker/enabled_plugins b/docker/skills-stomp-broker/enabled_plugins
new file mode 100644
index 00000000..33953aef
--- /dev/null
+++ b/docker/skills-stomp-broker/enabled_plugins
@@ -0,0 +1 @@
+[rabbitmq_federation_management,rabbitmq_management,rabbitmq_stomp].
diff --git a/docker/skills-stomp-broker/join.sh b/docker/skills-stomp-broker/join.sh
new file mode 100755
index 00000000..2f1f1d34
--- /dev/null
+++ b/docker/skills-stomp-broker/join.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+#
+# Script to join rabbitmq cluster.
+#
+# This script is call in background once the mangement ui of this node is
+# running (see Dockerfile).
+#
+#
+
+# Wait a random amount of seconds (between 1 and 10 seconds) to get in
+# parallel started instances a littel bit out of sync.
+echo "Wait random duration..."
+sleep $[ ( $RANDOM % 10 ) + 10 ]s
+
+echo "Try to join rabbitmq cluster..."
+
+# busybox 'nslookup' required
+# Try to determine all hosts of this service (given by env var `SERVICE_NAME`).
+# Sometimes wrong hostnames are returned, hence the retry functionality.
+for i in `seq 5`
+do
+ if [[ "$i" > "5" ]]
+ then
+ echo "Retry count exceeded"
+ exit 1
+ fi
+ retry=false
+# nodes=`nslookup tasks.$SERVICE_NAME 2>/dev/null | grep -v $(hostname) | grep Address | awk '{print $4}' | cut -d. -f1-3`
+ nodes=`dig +short tasks.$SERVICE_NAME | xargs -r -n 1 dig +short -x | grep -v $(hostname) | cut -d. -f1-3`
+ for node in $nodes
+ do
+ if [[ "$node" != $SERVICE_NAME* ]]
+ then
+ retry=true
+ break
+ fi
+ done
+ if ! $retry; then break; fi
+done
+
+# If the service is configured with just one replica this rabbitmq instance is
+# running in standalone mode and no further cluster joining arithmetic is
+# required. If there are multiple nodes configured stop the app to start
+# setting up a cluster.
+echo
+if [[ ${#nodes} > 0 ]]
+then
+ echo "Found nodes of cluster:"
+ echo $nodes
+ rabbitmqctl stop_app
+ rabbitmqctl reset
+else
+ echo "Found standalone setup."
+ exit 0
+fi
+echo
+
+# Join cluster by trying one node after each other. If successfully joined, start the rabbitmq
+# app again
+while true
+do
+ for node in $nodes
+ do
+ # manually force a start by setting the env variable FORCE_START
+ if [[ -f /tmp/FORCE_START ]]
+ then
+ echo "Startup forced manually."
+ echo
+ rabbitmqctl start_app
+ exit 0
+ fi
+ # of peer is reachable try to join the cluster of that host
+ echo "Try to reach $node"
+ if nc -z "$node" 15672
+ then
+ rabbitmqctl join_cluster rabbit@$node
+ if [[ $? == "0" ]]
+ then
+ echo
+ echo "Start app after joining cluster"
+
+ rabbitmqctl start_app
+
+ echo
+ echo "Try to cleanup old nodes of same slot..."
+ for n in `rabbitmqctl cluster_status | awk '/disc/,/]}]}/' | grep -o "$SERVICE_NAME[^']*"`
+ do
+ if [[ $n == "$SERVICE_NAME.$SLOT."* && $n != $HOSTNAME ]]
+ then
+ echo
+ echo "removing node $n from cluster"
+ rabbitmqctl forget_cluster_node rabbit@$n
+ fi
+ done
+ echo
+ echo "Successfully joined cluster"
+ exit 0
+ fi
+ elif [[ "$SLOT" == "$MASTER_SLOT" ]]
+ then
+ echo "Startup due to claimed master role on slot $MASTER_SLOT."
+ echo
+ rabbitmqctl start_app
+ exit 0
+ fi
+ done
+
+ sleep 10
+done
+
+echo "Failed to join cluster."
+exit 1
diff --git a/docker/startup.sh b/docker/startup.sh
new file mode 100755
index 00000000..4e472254
--- /dev/null
+++ b/docker/startup.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+echo "Starting Skills Service"
+JAVA_OPTS="${JAVA_OPTS} -Dlogging.file=/logs/webapp.log"
+
+if [ ! -z "${EXTRA_JAVA_OPTS}" ]
+then
+ JAVA_OPTS="${EXTRA_JAVA_OPTS} ${JAVA_OPTS}"
+ echo "Added EXTRA_JAVA_OPTS to JAVA_OPTS = [$EXTRA_JAVA_OPTS]"
+fi
+
+echo "JAVA_OPTS=${JAVA_OPTS}"
+echo -e "SPRING_PROPS=${SPRING_PROPS}"
+
+# support both \n and , as a prop separator
+echo -e $SPRING_PROPS | sed -r 's$([^\])[,]\s?$\1\n$g; s$\\,$,$g' >> application.properties
+
+pid=0
+term_handler() {
+ echo "SIGTERM handler was called"
+ if [ $pid -ne 0 ]; then
+ echo "exec: kill -SIGTERM $pid"
+ kill -SIGTERM "$pid"
+ echo "exec: wait $pid"
+ wait "$pid"
+ fi
+ exit 143; # 128 + 15 -- SIGTERM
+}
+trap term_handler SIGTERM
+
+java ${DEBUG_OPTS} ${JAVA_OPTS} -jar skills.jar &
+pid="$!"
+
+# wait forever
+while true
+do
+ tail -f /dev/null & wait ${!}
+done
diff --git a/e2e-tests/.gitignore b/e2e-tests/.gitignore
index 806acc7f..9caf9567 100644
--- a/e2e-tests/.gitignore
+++ b/e2e-tests/.gitignore
@@ -1,6 +1,7 @@
.DS_Store
/node_modules/
/dist/
+/logs/
npm-debug.log*
yarn-debug.log*
yarn-errpr.log*
@@ -11,6 +12,7 @@ selenium-debug.log
# Editor directories and files
.idea
.vscode
+.history
*.suo
*.ntvs*
*.njsproj
diff --git a/e2e-tests/cypress.json b/e2e-tests/cypress.json
index f302dd39..d6abff94 100644
--- a/e2e-tests/cypress.json
+++ b/e2e-tests/cypress.json
@@ -1,4 +1,10 @@
{
- "projectId": "skillstests1",
- "baseUrl": "http://localhost:8080"
+ "projectId": "7kivjf",
+ "baseUrl": "http://localhost:8080",
+ "requestTimeout": 10000,
+ "defaultCommandTimeout": 10000,
+ "retries": {
+ "runMode": 2,
+ "openMode": 0
+ }
}
diff --git a/e2e-tests/cypress/db/clear.sql b/e2e-tests/cypress/db/clear.sql
new file mode 100644
index 00000000..aa06cdfc
--- /dev/null
+++ b/e2e-tests/cypress/db/clear.sql
@@ -0,0 +1,21 @@
+-- Copyright 2020 SkillTree
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+-- creating Inception project is expensive so lets not delete it
+delete from PROJECT_DEFINITION;
+delete from USER_ATTRS;
+delete from USER_ROLES;
+delete from USER_ROLES;
+delete from GLOBAL_BADGE_LEVEL_DEFINITION;
+delete from SKILL_DEFINITION;
+delete from SETTINGS;
diff --git a/e2e-tests/cypress/db/dropTables.sql b/e2e-tests/cypress/db/dropTables.sql
new file mode 100644
index 00000000..3f6f1061
--- /dev/null
+++ b/e2e-tests/cypress/db/dropTables.sql
@@ -0,0 +1,49 @@
+-- Copyright 2020 SkillTree
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- https://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+-- creating Inception project is expensive so lets not delete it
+drop table databasechangeloglock;
+
+drop table databasechangelog;
+
+drop table skills_db_locks;
+
+drop table skill_relationship_definition;
+
+drop table skill_share_definition;
+
+drop table user_roles;
+
+drop table user_performed_skill;
+
+drop table user_points;
+
+drop table user_achievement;
+
+drop table settings;
+
+drop table global_badge_level_definition;
+
+drop table level_definition;
+
+drop table skill_definition;
+
+drop table custom_icons;
+
+drop table project_definition;
+
+drop table password_reset_token;
+
+drop table users;
+
+drop table user_attrs;
diff --git a/e2e-tests/cypress/db/reset.sql b/e2e-tests/cypress/db/reset.sql
index 9329e447..7c033c79 100644
--- a/e2e-tests/cypress/db/reset.sql
+++ b/e2e-tests/cypress/db/reset.sql
@@ -18,3 +18,4 @@ delete from USER_ROLES where USER_ID = 'skills@skills.org' and ROLE_NAME = 'ROLE
delete from USER_ROLES where USER_ID = 'root@skills.org' and ROLE_NAME = 'ROLE_SUPERVISOR';
delete from GLOBAL_BADGE_LEVEL_DEFINITION;
delete from SKILL_DEFINITION where PROJECT_ID is null;
+delete from SETTINGS;
diff --git a/e2e-tests/cypress/fonts/Type1/.uuid b/e2e-tests/cypress/fonts/Type1/.uuid
new file mode 100644
index 00000000..003633d4
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/.uuid
@@ -0,0 +1 @@
+8fbe70ce-396c-462a-88a5-77af70944b2e
\ No newline at end of file
diff --git a/e2e-tests/cypress/fonts/Type1/UTBI____.afm b/e2e-tests/cypress/fonts/Type1/UTBI____.afm
new file mode 100644
index 00000000..4af327dc
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTBI____.afm
@@ -0,0 +1,1017 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Wed Oct 2 18:46:03 1991
+Comment UniqueID 36546
+Comment VMusage 34429 41321
+FontName Utopia-BoldItalic
+FullName Utopia Bold Italic
+FamilyName Utopia
+Weight Bold
+ItalicAngle -13
+IsFixedPitch false
+FontBBox -141 -250 1297 916
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.001
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 502
+Ascender 742
+Descender -242
+StartCharMetrics 228
+C 32 ; WX 210 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 285 ; N exclam ; B 70 -12 371 707 ;
+C 34 ; WX 455 ; N quotedbl ; B 177 407 531 707 ;
+C 35 ; WX 560 ; N numbersign ; B 72 0 641 668 ;
+C 36 ; WX 560 ; N dollar ; B 67 -104 623 748 ;
+C 37 ; WX 896 ; N percent ; B 141 -31 896 702 ;
+C 38 ; WX 752 ; N ampersand ; B 97 -12 771 680 ;
+C 39 ; WX 246 ; N quoteright ; B 130 387 329 707 ;
+C 40 ; WX 350 ; N parenleft ; B 122 -135 473 699 ;
+C 41 ; WX 350 ; N parenright ; B 3 -135 354 699 ;
+C 42 ; WX 500 ; N asterisk ; B 156 315 563 707 ;
+C 43 ; WX 600 ; N plus ; B 118 0 602 490 ;
+C 44 ; WX 280 ; N comma ; B 26 -167 242 180 ;
+C 45 ; WX 392 ; N hyphen ; B 106 203 389 298 ;
+C 46 ; WX 280 ; N period ; B 67 -12 247 166 ;
+C 47 ; WX 260 ; N slash ; B 19 -15 405 707 ;
+C 48 ; WX 560 ; N zero ; B 92 -12 618 680 ;
+C 49 ; WX 560 ; N one ; B 107 0 505 680 ;
+C 50 ; WX 560 ; N two ; B 39 0 613 680 ;
+C 51 ; WX 560 ; N three ; B 56 -12 602 680 ;
+C 52 ; WX 560 ; N four ; B 63 0 592 668 ;
+C 53 ; WX 560 ; N five ; B 58 -12 628 668 ;
+C 54 ; WX 560 ; N six ; B 91 -12 621 680 ;
+C 55 ; WX 560 ; N seven ; B 147 -12 667 668 ;
+C 56 ; WX 560 ; N eight ; B 72 -12 619 680 ;
+C 57 ; WX 560 ; N nine ; B 83 -12 605 680 ;
+C 58 ; WX 280 ; N colon ; B 67 -12 315 490 ;
+C 59 ; WX 280 ; N semicolon ; B 26 -167 315 490 ;
+C 60 ; WX 600 ; N less ; B 101 5 579 495 ;
+C 61 ; WX 600 ; N equal ; B 118 103 602 397 ;
+C 62 ; WX 600 ; N greater ; B 121 5 599 495 ;
+C 63 ; WX 454 ; N question ; B 150 -12 550 707 ;
+C 64 ; WX 828 ; N at ; B 125 -15 877 707 ;
+C 65 ; WX 634 ; N A ; B -24 0 674 692 ;
+C 66 ; WX 680 ; N B ; B 40 0 724 692 ;
+C 67 ; WX 672 ; N C ; B 111 -15 777 707 ;
+C 68 ; WX 774 ; N D ; B 40 0 819 692 ;
+C 69 ; WX 622 ; N E ; B 40 0 722 692 ;
+C 70 ; WX 585 ; N F ; B 40 0 718 692 ;
+C 71 ; WX 726 ; N G ; B 111 -15 791 707 ;
+C 72 ; WX 800 ; N H ; B 40 0 915 692 ;
+C 73 ; WX 386 ; N I ; B 40 0 501 692 ;
+C 74 ; WX 388 ; N J ; B -15 -114 512 692 ;
+C 75 ; WX 688 ; N K ; B 40 -6 858 692 ;
+C 76 ; WX 586 ; N L ; B 40 0 626 692 ;
+C 77 ; WX 921 ; N M ; B 35 0 1033 692 ;
+C 78 ; WX 741 ; N N ; B 30 0 873 692 ;
+C 79 ; WX 761 ; N O ; B 113 -15 803 707 ;
+C 80 ; WX 660 ; N P ; B 40 0 729 692 ;
+C 81 ; WX 761 ; N Q ; B 113 -193 803 707 ;
+C 82 ; WX 681 ; N R ; B 40 0 731 692 ;
+C 83 ; WX 551 ; N S ; B 66 -15 605 707 ;
+C 84 ; WX 616 ; N T ; B 126 0 757 692 ;
+C 85 ; WX 776 ; N U ; B 150 -15 902 692 ;
+C 86 ; WX 630 ; N V ; B 127 0 818 692 ;
+C 87 ; WX 920 ; N W ; B 115 0 1097 692 ;
+C 88 ; WX 630 ; N X ; B -21 0 779 692 ;
+C 89 ; WX 622 ; N Y ; B 127 0 800 692 ;
+C 90 ; WX 618 ; N Z ; B 5 0 749 692 ;
+C 91 ; WX 350 ; N bracketleft ; B 91 -128 463 692 ;
+C 92 ; WX 460 ; N backslash ; B 149 -15 460 707 ;
+C 93 ; WX 350 ; N bracketright ; B 13 -128 385 692 ;
+C 94 ; WX 600 ; N asciicircum ; B 114 215 602 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 246 ; N quoteleft ; B 149 399 348 719 ;
+C 97 ; WX 596 ; N a ; B 61 -12 647 502 ;
+C 98 ; WX 586 ; N b ; B 69 -12 627 742 ;
+C 99 ; WX 456 ; N c ; B 73 -12 533 502 ;
+C 100 ; WX 609 ; N d ; B 64 -12 686 742 ;
+C 101 ; WX 476 ; N e ; B 73 -12 532 502 ;
+C 102 ; WX 348 ; N f ; B -94 -242 588 742 ; L i fi ; L l fl ;
+C 103 ; WX 522 ; N g ; B 21 -242 644 512 ;
+C 104 ; WX 629 ; N h ; B 79 -12 666 742 ;
+C 105 ; WX 339 ; N i ; B 101 -12 392 720 ;
+C 106 ; WX 333 ; N j ; B -85 -242 399 720 ;
+C 107 ; WX 570 ; N k ; B 74 -12 639 742 ;
+C 108 ; WX 327 ; N l ; B 97 -12 395 742 ;
+C 109 ; WX 914 ; N m ; B 81 -12 952 502 ;
+C 110 ; WX 635 ; N n ; B 80 -12 674 502 ;
+C 111 ; WX 562 ; N o ; B 77 -12 591 502 ;
+C 112 ; WX 606 ; N p ; B 35 -242 648 502 ;
+C 113 ; WX 584 ; N q ; B 64 -242 639 513 ;
+C 114 ; WX 440 ; N r ; B 86 -12 532 502 ;
+C 115 ; WX 417 ; N s ; B 45 -12 467 502 ;
+C 116 ; WX 359 ; N t ; B 103 -12 463 641 ;
+C 117 ; WX 634 ; N u ; B 106 -12 678 502 ;
+C 118 ; WX 518 ; N v ; B 103 -12 582 502 ;
+C 119 ; WX 795 ; N w ; B 105 -12 861 502 ;
+C 120 ; WX 516 ; N x ; B 9 -12 581 502 ;
+C 121 ; WX 489 ; N y ; B -14 -242 567 502 ;
+C 122 ; WX 466 ; N z ; B 18 -12 541 490 ;
+C 123 ; WX 340 ; N braceleft ; B 125 -128 474 692 ;
+C 124 ; WX 265 ; N bar ; B 152 -250 256 750 ;
+C 125 ; WX 340 ; N braceright ; B -7 -128 342 692 ;
+C 126 ; WX 600 ; N asciitilde ; B 105 157 606 338 ;
+C 161 ; WX 285 ; N exclamdown ; B 22 -217 323 502 ;
+C 162 ; WX 560 ; N cent ; B 115 -21 646 668 ;
+C 163 ; WX 560 ; N sterling ; B 31 0 618 679 ;
+C 164 ; WX 100 ; N fraction ; B -141 -27 405 695 ;
+C 165 ; WX 560 ; N yen ; B 100 0 711 668 ;
+C 166 ; WX 560 ; N florin ; B 19 -135 670 691 ;
+C 167 ; WX 568 ; N section ; B 99 -115 594 707 ;
+C 168 ; WX 560 ; N currency ; B 95 73 613 596 ;
+C 169 ; WX 246 ; N quotesingle ; B 169 376 320 707 ;
+C 170 ; WX 455 ; N quotedblleft ; B 149 399 557 719 ;
+C 171 ; WX 560 ; N guillemotleft ; B 125 37 568 464 ;
+C 172 ; WX 360 ; N guilsinglleft ; B 125 37 368 464 ;
+C 173 ; WX 360 ; N guilsinglright ; B 93 37 336 464 ;
+C 174 ; WX 651 ; N fi ; B -94 -242 690 742 ;
+C 175 ; WX 652 ; N fl ; B -94 -242 720 742 ;
+C 177 ; WX 500 ; N endash ; B 47 209 566 292 ;
+C 178 ; WX 514 ; N dagger ; B 136 -125 580 707 ;
+C 179 ; WX 490 ; N daggerdbl ; B 67 -119 563 707 ;
+C 180 ; WX 280 ; N periodcentered ; B 102 161 282 339 ;
+C 182 ; WX 580 ; N paragraph ; B 145 -101 688 692 ;
+C 183 ; WX 465 ; N bullet ; B 134 174 489 529 ;
+C 184 ; WX 246 ; N quotesinglbase ; B 18 -153 217 167 ;
+C 185 ; WX 455 ; N quotedblbase ; B 18 -153 426 167 ;
+C 186 ; WX 455 ; N quotedblright ; B 130 387 538 707 ;
+C 187 ; WX 560 ; N guillemotright ; B 93 37 537 464 ;
+C 188 ; WX 1000 ; N ellipsis ; B 120 -12 966 166 ;
+C 189 ; WX 1297 ; N perthousand ; B 141 -31 1297 702 ;
+C 191 ; WX 454 ; N questiondown ; B 25 -217 426 502 ;
+C 193 ; WX 400 ; N grave ; B 144 511 416 740 ;
+C 194 ; WX 400 ; N acute ; B 221 511 493 740 ;
+C 195 ; WX 400 ; N circumflex ; B 128 520 506 747 ;
+C 196 ; WX 400 ; N tilde ; B 129 549 537 697 ;
+C 197 ; WX 400 ; N macron ; B 168 592 494 664 ;
+C 198 ; WX 400 ; N breve ; B 181 556 504 714 ;
+C 199 ; WX 402 ; N dotaccent ; B 255 561 413 710 ;
+C 200 ; WX 400 ; N dieresis ; B 141 561 539 710 ;
+C 202 ; WX 400 ; N ring ; B 201 529 458 762 ;
+C 203 ; WX 400 ; N cedilla ; B 120 -246 327 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 193 546 517 750 ;
+C 206 ; WX 350 ; N ogonek ; B 73 -246 288 0 ;
+C 207 ; WX 400 ; N caron ; B 165 520 543 747 ;
+C 208 ; WX 1000 ; N emdash ; B 47 209 1066 292 ;
+C 225 ; WX 890 ; N AE ; B -72 0 993 692 ;
+C 227 ; WX 444 ; N ordfeminine ; B 97 265 517 590 ;
+C 232 ; WX 592 ; N Lslash ; B 46 0 632 692 ;
+C 233 ; WX 761 ; N Oslash ; B 112 -51 804 734 ;
+C 234 ; WX 1016 ; N OE ; B 111 0 1119 692 ;
+C 235 ; WX 412 ; N ordmasculine ; B 121 265 481 590 ;
+C 241 ; WX 789 ; N ae ; B 61 -12 845 509 ;
+C 245 ; WX 339 ; N dotlessi ; B 101 -12 378 502 ;
+C 248 ; WX 339 ; N lslash ; B 53 -12 455 742 ;
+C 249 ; WX 562 ; N oslash ; B 77 -69 591 549 ;
+C 250 ; WX 811 ; N oe ; B 77 -12 867 502 ;
+C 251 ; WX 628 ; N germandbls ; B -94 -242 727 742 ;
+C -1 ; WX 402 ; N onesuperior ; B 119 272 396 680 ;
+C -1 ; WX 600 ; N minus ; B 118 210 602 290 ;
+C -1 ; WX 375 ; N degree ; B 128 360 460 680 ;
+C -1 ; WX 562 ; N oacute ; B 77 -12 591 740 ;
+C -1 ; WX 761 ; N Odieresis ; B 113 -15 803 881 ;
+C -1 ; WX 562 ; N odieresis ; B 77 -12 620 710 ;
+C -1 ; WX 622 ; N Eacute ; B 40 0 722 904 ;
+C -1 ; WX 634 ; N ucircumflex ; B 106 -12 678 747 ;
+C -1 ; WX 940 ; N onequarter ; B 139 -27 884 695 ;
+C -1 ; WX 600 ; N logicalnot ; B 118 95 602 397 ;
+C -1 ; WX 622 ; N Ecircumflex ; B 40 0 722 905 ;
+C -1 ; WX 940 ; N onehalf ; B 125 -27 933 695 ;
+C -1 ; WX 761 ; N Otilde ; B 113 -15 803 876 ;
+C -1 ; WX 634 ; N uacute ; B 106 -12 678 740 ;
+C -1 ; WX 476 ; N eacute ; B 73 -12 566 740 ;
+C -1 ; WX 339 ; N iacute ; B 101 -12 443 740 ;
+C -1 ; WX 622 ; N Egrave ; B 40 0 722 904 ;
+C -1 ; WX 339 ; N icircumflex ; B 73 -12 451 747 ;
+C -1 ; WX 634 ; N mu ; B 32 -230 678 502 ;
+C -1 ; WX 265 ; N brokenbar ; B 152 -175 256 675 ;
+C -1 ; WX 600 ; N thorn ; B 29 -242 642 700 ;
+C -1 ; WX 634 ; N Aring ; B -24 0 674 879 ;
+C -1 ; WX 489 ; N yacute ; B -14 -242 567 740 ;
+C -1 ; WX 622 ; N Ydieresis ; B 127 0 800 881 ;
+C -1 ; WX 1100 ; N trademark ; B 138 277 1128 692 ;
+C -1 ; WX 824 ; N registered ; B 126 -15 854 707 ;
+C -1 ; WX 562 ; N ocircumflex ; B 77 -12 591 747 ;
+C -1 ; WX 634 ; N Agrave ; B -24 0 674 904 ;
+C -1 ; WX 551 ; N Scaron ; B 66 -15 647 916 ;
+C -1 ; WX 776 ; N Ugrave ; B 150 -15 902 904 ;
+C -1 ; WX 622 ; N Edieresis ; B 40 0 722 881 ;
+C -1 ; WX 776 ; N Uacute ; B 150 -15 902 904 ;
+C -1 ; WX 562 ; N otilde ; B 77 -12 618 697 ;
+C -1 ; WX 635 ; N ntilde ; B 80 -12 674 697 ;
+C -1 ; WX 489 ; N ydieresis ; B -14 -242 567 710 ;
+C -1 ; WX 634 ; N Aacute ; B -24 0 674 904 ;
+C -1 ; WX 562 ; N eth ; B 77 -12 593 742 ;
+C -1 ; WX 596 ; N acircumflex ; B 61 -12 647 747 ;
+C -1 ; WX 596 ; N aring ; B 61 -12 647 762 ;
+C -1 ; WX 761 ; N Ograve ; B 113 -15 803 904 ;
+C -1 ; WX 456 ; N ccedilla ; B 73 -246 533 502 ;
+C -1 ; WX 600 ; N multiply ; B 145 22 595 478 ;
+C -1 ; WX 600 ; N divide ; B 98 7 582 493 ;
+C -1 ; WX 402 ; N twosuperior ; B 64 272 458 680 ;
+C -1 ; WX 741 ; N Ntilde ; B 30 0 873 876 ;
+C -1 ; WX 634 ; N ugrave ; B 106 -12 678 740 ;
+C -1 ; WX 776 ; N Ucircumflex ; B 150 -15 902 905 ;
+C -1 ; WX 634 ; N Atilde ; B -24 0 697 876 ;
+C -1 ; WX 466 ; N zcaron ; B 18 -12 561 747 ;
+C -1 ; WX 339 ; N idieresis ; B 81 -12 479 710 ;
+C -1 ; WX 634 ; N Acircumflex ; B -24 0 674 905 ;
+C -1 ; WX 386 ; N Icircumflex ; B 40 0 541 905 ;
+C -1 ; WX 622 ; N Yacute ; B 127 0 800 904 ;
+C -1 ; WX 761 ; N Oacute ; B 113 -15 803 904 ;
+C -1 ; WX 634 ; N Adieresis ; B -24 0 687 881 ;
+C -1 ; WX 618 ; N Zcaron ; B 5 0 749 916 ;
+C -1 ; WX 596 ; N agrave ; B 61 -12 647 740 ;
+C -1 ; WX 402 ; N threesuperior ; B 94 265 456 680 ;
+C -1 ; WX 562 ; N ograve ; B 77 -12 591 740 ;
+C -1 ; WX 940 ; N threequarters ; B 130 -27 911 695 ;
+C -1 ; WX 780 ; N Eth ; B 46 0 825 692 ;
+C -1 ; WX 600 ; N plusminus ; B 118 0 602 549 ;
+C -1 ; WX 634 ; N udieresis ; B 106 -12 678 710 ;
+C -1 ; WX 476 ; N edieresis ; B 73 -12 577 710 ;
+C -1 ; WX 596 ; N aacute ; B 61 -12 647 740 ;
+C -1 ; WX 339 ; N igrave ; B 101 -12 378 740 ;
+C -1 ; WX 386 ; N Idieresis ; B 40 0 568 881 ;
+C -1 ; WX 596 ; N adieresis ; B 61 -12 647 710 ;
+C -1 ; WX 386 ; N Iacute ; B 40 0 534 904 ;
+C -1 ; WX 824 ; N copyright ; B 126 -15 854 707 ;
+C -1 ; WX 386 ; N Igrave ; B 40 0 501 904 ;
+C -1 ; WX 672 ; N Ccedilla ; B 111 -246 777 707 ;
+C -1 ; WX 417 ; N scaron ; B 45 -12 557 747 ;
+C -1 ; WX 476 ; N egrave ; B 73 -12 532 740 ;
+C -1 ; WX 761 ; N Ocircumflex ; B 113 -15 803 905 ;
+C -1 ; WX 629 ; N Thorn ; B 40 0 695 692 ;
+C -1 ; WX 596 ; N atilde ; B 61 -12 647 697 ;
+C -1 ; WX 776 ; N Udieresis ; B 150 -15 902 881 ;
+C -1 ; WX 476 ; N ecircumflex ; B 73 -12 559 747 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 697
+
+KPX A z 18
+KPX A y -40
+KPX A x 16
+KPX A w -30
+KPX A v -30
+KPX A u -18
+KPX A t -6
+KPX A s 6
+KPX A r -6
+KPX A quoteright -92
+KPX A quotedblright -92
+KPX A p -6
+KPX A o -18
+KPX A n -12
+KPX A m -12
+KPX A l -18
+KPX A h -6
+KPX A d 4
+KPX A c -6
+KPX A b -6
+KPX A a 10
+KPX A Y -56
+KPX A X -8
+KPX A W -46
+KPX A V -75
+KPX A U -50
+KPX A T -60
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B y -6
+KPX B u -12
+KPX B r -6
+KPX B quoteright -20
+KPX B quotedblright -32
+KPX B o 6
+KPX B l -20
+KPX B k -10
+KPX B i -12
+KPX B h -15
+KPX B e 4
+KPX B a 10
+KPX B W -30
+KPX B V -45
+KPX B U -30
+KPX B T -20
+
+KPX C z -6
+KPX C y -18
+KPX C u -12
+KPX C r -12
+KPX C quoteright 12
+KPX C quotedblright 20
+KPX C i -6
+KPX C e -6
+KPX C a -6
+KPX C Q -12
+KPX C O -12
+KPX C G -12
+KPX C C -12
+
+KPX D y 18
+KPX D quoteright -20
+KPX D quotedblright -20
+KPX D period -20
+KPX D o 6
+KPX D h -15
+KPX D e 6
+KPX D comma -20
+KPX D a 6
+KPX D Y -80
+KPX D W -40
+KPX D V -65
+
+KPX E z -6
+KPX E y -24
+KPX E x 15
+KPX E w -30
+KPX E v -18
+KPX E u -24
+KPX E t -18
+KPX E s -6
+KPX E r -6
+KPX E quoteright 10
+KPX E q 10
+KPX E period 15
+KPX E p -12
+KPX E n -12
+KPX E m -12
+KPX E l -6
+KPX E j -6
+KPX E i -12
+KPX E g -12
+KPX E d 10
+KPX E comma 15
+KPX E a 10
+
+KPX F y -12
+KPX F u -24
+KPX F r -12
+KPX F quoteright 40
+KPX F quotedblright 35
+KPX F period -120
+KPX F o -24
+KPX F i -6
+KPX F e -24
+KPX F comma -110
+KPX F a -30
+KPX F A -45
+
+KPX G y -25
+KPX G u -22
+KPX G r -22
+KPX G quoteright -30
+KPX G quotedblright -30
+KPX G n -22
+KPX G l -24
+KPX G i -12
+KPX G h -18
+KPX G e 5
+
+KPX H y -18
+KPX H u -30
+KPX H o -25
+KPX H i -25
+KPX H e -25
+KPX H a -25
+
+KPX I z -20
+KPX I y -6
+KPX I x -6
+KPX I w -30
+KPX I v -30
+KPX I u -30
+KPX I t -18
+KPX I s -18
+KPX I r -12
+KPX I p -18
+KPX I o -25
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I j -20
+KPX I i -10
+KPX I g -24
+KPX I f -6
+KPX I e -25
+KPX I d -15
+KPX I c -25
+KPX I b -6
+KPX I a -15
+
+KPX J y -12
+KPX J u -32
+KPX J quoteright 6
+KPX J quotedblright 6
+KPX J o -36
+KPX J i -30
+KPX J e -30
+KPX J braceright 15
+KPX J a -36
+
+KPX K y -70
+KPX K w -36
+KPX K v -30
+KPX K u -30
+KPX K r -24
+KPX K quoteright 36
+KPX K quotedblright 36
+KPX K o -30
+KPX K n -24
+KPX K l 10
+KPX K i -12
+KPX K h 15
+KPX K e -30
+KPX K a -12
+KPX K Q -50
+KPX K O -50
+KPX K G -50
+KPX K C -50
+KPX K A 15
+
+KPX L y -70
+KPX L w -30
+KPX L u -18
+KPX L quoteright -110
+KPX L quotedblright -110
+KPX L l -16
+KPX L j -18
+KPX L i -18
+KPX L Y -80
+KPX L W -78
+KPX L V -110
+KPX L U -42
+KPX L T -100
+KPX L Q -48
+KPX L O -48
+KPX L G -48
+KPX L C -48
+KPX L A 40
+
+KPX M y -18
+KPX M u -24
+KPX M quoteright 6
+KPX M quotedblright 6
+KPX M o -25
+KPX M n -20
+KPX M j -35
+KPX M i -20
+KPX M e -25
+KPX M d -20
+KPX M c -25
+KPX M a -20
+
+KPX N y -18
+KPX N u -24
+KPX N o -18
+KPX N i -12
+KPX N e -16
+KPX N a -22
+
+KPX O z -6
+KPX O y 12
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -6
+KPX O quoteright -20
+KPX O quotedblright -20
+KPX O q 6
+KPX O period -10
+KPX O p -6
+KPX O n -6
+KPX O m -6
+KPX O l -15
+KPX O k -10
+KPX O j -6
+KPX O h -10
+KPX O g -6
+KPX O e 6
+KPX O d 6
+KPX O comma -10
+KPX O a 6
+KPX O Y -70
+KPX O X -30
+KPX O W -35
+KPX O V -50
+KPX O T -42
+KPX O A -8
+
+KPX P y 6
+KPX P u -18
+KPX P t -6
+KPX P s -24
+KPX P r -6
+KPX P quoteright -12
+KPX P period -170
+KPX P o -24
+KPX P n -12
+KPX P l -20
+KPX P h -20
+KPX P e -24
+KPX P comma -170
+KPX P a -40
+KPX P I -45
+KPX P H -45
+KPX P E -45
+KPX P A -70
+
+KPX Q u -6
+KPX Q quoteright -20
+KPX Q quotedblright -38
+KPX Q a -6
+KPX Q Y -70
+KPX Q X -12
+KPX Q W -35
+KPX Q V -50
+KPX Q U -30
+KPX Q T -36
+KPX Q A -18
+
+KPX R y -6
+KPX R u -12
+KPX R quoteright -22
+KPX R quotedblright -22
+KPX R o -20
+KPX R e -12
+KPX R Y -45
+KPX R X 15
+KPX R W -25
+KPX R V -35
+KPX R U -40
+KPX R T -18
+KPX R Q -8
+KPX R O -8
+KPX R G -8
+KPX R C -8
+KPX R A 15
+
+KPX S y -30
+KPX S w -30
+KPX S v -20
+KPX S u -18
+KPX S t -18
+KPX S r -20
+KPX S quoteright -38
+KPX S quotedblright -50
+KPX S p -18
+KPX S n -24
+KPX S m -24
+KPX S l -20
+KPX S k -18
+KPX S j -25
+KPX S i -20
+KPX S h -12
+KPX S e -6
+
+KPX T z -48
+KPX T y -52
+KPX T w -54
+KPX T u -54
+KPX T semicolon -6
+KPX T s -60
+KPX T r -54
+KPX T quoteright 36
+KPX T quotedblright 36
+KPX T period -70
+KPX T parenright 25
+KPX T o -78
+KPX T m -54
+KPX T i -22
+KPX T hyphen -100
+KPX T h 6
+KPX T endash -40
+KPX T emdash -40
+KPX T e -78
+KPX T comma -90
+KPX T bracketright 20
+KPX T braceright 30
+KPX T a -78
+KPX T Y 12
+KPX T X 18
+KPX T W 30
+KPX T V 20
+KPX T T 40
+KPX T Q -6
+KPX T O -6
+KPX T G -6
+KPX T C -6
+KPX T A -40
+
+KPX U z -18
+KPX U x -30
+KPX U v -20
+KPX U t -24
+KPX U s -40
+KPX U r -30
+KPX U p -30
+KPX U n -30
+KPX U m -30
+KPX U l -12
+KPX U k -12
+KPX U i -24
+KPX U h -6
+KPX U g -30
+KPX U f -10
+KPX U d -30
+KPX U c -30
+KPX U b -6
+KPX U a -30
+KPX U A -40
+
+KPX V y -34
+KPX V u -42
+KPX V semicolon -45
+KPX V r -55
+KPX V quoteright 46
+KPX V quotedblright 60
+KPX V period -110
+KPX V parenright 64
+KPX V o -55
+KPX V i 15
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -55
+KPX V comma -110
+KPX V colon -18
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -80
+KPX V T 12
+KPX V A -70
+
+KPX W y -36
+KPX W u -30
+KPX W t -10
+KPX W semicolon -12
+KPX W r -30
+KPX W quoteright 42
+KPX W quotedblright 55
+KPX W period -80
+KPX W parenright 55
+KPX W o -55
+KPX W m -30
+KPX W i 5
+KPX W hyphen -40
+KPX W h 16
+KPX W e -55
+KPX W d -60
+KPX W comma -80
+KPX W colon -12
+KPX W bracketright 64
+KPX W braceright 64
+KPX W a -60
+KPX W T 30
+KPX W Q -5
+KPX W O -5
+KPX W G -5
+KPX W C -5
+KPX W A -45
+
+KPX X y -40
+KPX X u -30
+KPX X r -6
+KPX X quoteright 24
+KPX X quotedblright 40
+KPX X i -6
+KPX X e -18
+KPX X a -6
+KPX X Y -6
+KPX X W -6
+KPX X Q -45
+KPX X O -45
+KPX X G -45
+KPX X C -45
+
+KPX Y v -60
+KPX Y u -70
+KPX Y t -32
+KPX Y semicolon -20
+KPX Y quoteright 56
+KPX Y quotedblright 70
+KPX Y q -100
+KPX Y period -80
+KPX Y parenright 5
+KPX Y o -95
+KPX Y l 15
+KPX Y i 15
+KPX Y hyphen -110
+KPX Y endash -40
+KPX Y emdash -40
+KPX Y e -95
+KPX Y d -85
+KPX Y comma -80
+KPX Y colon -20
+KPX Y bracketright 64
+KPX Y braceright 64
+KPX Y a -85
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 6
+KPX Y T 30
+KPX Y Q -25
+KPX Y O -25
+KPX Y G -25
+KPX Y C -25
+KPX Y A -40
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -12
+KPX Z quoteright 18
+KPX Z quotedblright 18
+KPX Z o -6
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -20
+KPX Z O -20
+KPX Z G -20
+KPX Z C -20
+KPX Z A 30
+
+KPX a quoteright -54
+KPX a quotedblright -54
+
+KPX b y -6
+KPX b w -5
+KPX b v -5
+KPX b quoteright -30
+KPX b quotedblright -30
+KPX b period -15
+KPX b comma -15
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 40
+KPX braceleft J 60
+
+KPX bracketleft Y 60
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 35
+KPX bracketleft J 30
+
+KPX c quoteright 5
+KPX c quotedblright 5
+
+KPX colon space -30
+
+KPX comma space -40
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX d quoteright -12
+KPX d quotedblright -12
+KPX d period 15
+KPX d comma 15
+
+KPX e y 6
+KPX e x -10
+KPX e w -10
+KPX e v -10
+KPX e quoteright -25
+KPX e quotedblright -25
+
+KPX f quoteright 120
+KPX f quotedblright 120
+KPX f period -30
+KPX f parenright 100
+KPX f comma -30
+KPX f bracketright 110
+KPX f braceright 110
+
+KPX g y 50
+KPX g quotedblright -20
+KPX g p 30
+KPX g f 42
+KPX g comma 20
+
+KPX h quoteright -78
+KPX h quotedblright -78
+
+KPX i quoteright -20
+KPX i quotedblright -20
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -20
+KPX j comma -20
+
+KPX k quoteright -38
+KPX k quotedblright -38
+
+KPX l quoteright -12
+KPX l quotedblright -12
+
+KPX m quoteright -78
+KPX m quotedblright -78
+
+KPX n quoteright -88
+KPX n quotedblright -88
+
+KPX o y -12
+KPX o x -20
+KPX o w -25
+KPX o v -25
+KPX o quoteright -50
+KPX o quotedblright -50
+KPX o period -10
+KPX o comma -10
+
+KPX p w -6
+KPX p quoteright -30
+KPX p quotedblright -52
+KPX p period -15
+KPX p comma -15
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 30
+KPX parenleft J 50
+
+KPX period space -40
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX q quoteright -40
+KPX q quotedblright -40
+KPX q period -10
+KPX q comma -5
+
+KPX quotedblleft z -30
+KPX quotedblleft x -60
+KPX quotedblleft w -12
+KPX quotedblleft v -12
+KPX quotedblleft u -12
+KPX quotedblleft t 5
+KPX quotedblleft s -30
+KPX quotedblleft r -12
+KPX quotedblleft q -50
+KPX quotedblleft p -12
+KPX quotedblleft o -30
+KPX quotedblleft n -12
+KPX quotedblleft m -12
+KPX quotedblleft l 10
+KPX quotedblleft k 10
+KPX quotedblleft h 10
+KPX quotedblleft g -30
+KPX quotedblleft e -30
+KPX quotedblleft d -50
+KPX quotedblleft c -30
+KPX quotedblleft b 24
+KPX quotedblleft a -50
+KPX quotedblleft Y 30
+KPX quotedblleft X 45
+KPX quotedblleft W 55
+KPX quotedblleft V 40
+KPX quotedblleft T 36
+KPX quotedblleft A -100
+
+KPX quotedblright space -50
+KPX quotedblright period -200
+KPX quotedblright comma -200
+
+KPX quoteleft z -30
+KPX quoteleft y 30
+KPX quoteleft x -10
+KPX quoteleft w -12
+KPX quoteleft u -12
+KPX quoteleft t -30
+KPX quoteleft s -30
+KPX quoteleft r -12
+KPX quoteleft q -30
+KPX quoteleft p -12
+KPX quoteleft o -30
+KPX quoteleft n -12
+KPX quoteleft m -12
+KPX quoteleft l 10
+KPX quoteleft k 10
+KPX quoteleft h 10
+KPX quoteleft g -30
+KPX quoteleft e -30
+KPX quoteleft d -30
+KPX quoteleft c -30
+KPX quoteleft b 24
+KPX quoteleft a -30
+KPX quoteleft Y 12
+KPX quoteleft X 46
+KPX quoteleft W 46
+KPX quoteleft V 28
+KPX quoteleft T 36
+KPX quoteleft A -100
+
+KPX quoteright v -20
+KPX quoteright space -50
+KPX quoteright s -45
+KPX quoteright r -12
+KPX quoteright period -140
+KPX quoteright m -12
+KPX quoteright l -12
+KPX quoteright d -65
+KPX quoteright comma -140
+
+KPX r z 20
+KPX r y 18
+KPX r x 12
+KPX r w 6
+KPX r v 6
+KPX r t 8
+KPX r semicolon 20
+KPX r quoteright -6
+KPX r quotedblright -6
+KPX r q -24
+KPX r period -100
+KPX r o -6
+KPX r l -12
+KPX r k -12
+KPX r hyphen -40
+KPX r h -10
+KPX r f 8
+KPX r endash -20
+KPX r e -26
+KPX r d -25
+KPX r comma -100
+KPX r colon 20
+KPX r c -12
+KPX r a -25
+
+KPX s quoteright -25
+KPX s quotedblright -30
+
+KPX semicolon space -30
+
+KPX space quotesinglbase -60
+KPX space quoteleft -60
+KPX space quotedblleft -60
+KPX space quotedblbase -60
+KPX space Y -70
+KPX space W -50
+KPX space V -70
+KPX space T -50
+KPX space A -50
+
+KPX t quoteright 15
+KPX t quotedblright 15
+KPX t period 15
+KPX t comma 15
+
+KPX u quoteright -65
+KPX u quotedblright -78
+KPX u period 20
+KPX u comma 20
+
+KPX v quoteright -10
+KPX v quotedblright -10
+KPX v q -6
+KPX v period -62
+KPX v o -6
+KPX v e -6
+KPX v d -6
+KPX v comma -62
+KPX v c -6
+KPX v a -6
+
+KPX w quoteright -10
+KPX w quotedblright -10
+KPX w period -40
+KPX w comma -50
+
+KPX x y 12
+KPX x w -6
+KPX x quoteright -30
+KPX x quotedblright -30
+KPX x q -6
+KPX x o -6
+KPX x e -6
+KPX x d -6
+KPX x c -6
+
+KPX y quoteright -10
+KPX y quotedblright -10
+KPX y q -10
+KPX y period -56
+KPX y d -10
+KPX y comma -56
+
+KPX z quoteright -40
+KPX z quotedblright -40
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/UTBI____.pfa b/e2e-tests/cypress/fonts/Type1/UTBI____.pfa
new file mode 100644
index 00000000..a4fda2e9
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTBI____.pfa
@@ -0,0 +1,1172 @@
+%!PS-AdobeFont-1.0: Utopia-BoldItalic 001.001
+%%CreationDate: Wed Oct 2 18:45:57 1991
+%%VMusage: 34429 41321
+%% Utopia is a registered trademark of Adobe Systems Incorporated.
+11 dict begin
+/FontInfo 10 dict dup begin
+/version (001.001) readonly def
+/Notice (Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.) readonly def
+/FullName (Utopia Bold Italic) readonly def
+/FamilyName (Utopia) readonly def
+/Weight (Bold) readonly def
+/ItalicAngle -13 def
+/isFixedPitch false def
+/UnderlinePosition -100 def
+/UnderlineThickness 50 def
+end readonly def
+/FontName /Utopia-BoldItalic def
+/Encoding StandardEncoding def
+/PaintType 0 def
+/FontType 1 def
+/FontMatrix [0.001 0 0 0.001 0 0] readonly def
+/UniqueID 36546 def
+/FontBBox{-141 -250 1297 916}readonly def
+currentdict end
+currentfile eexec
+f9d13ed4c538ee56ca0c8979e615439db5863a5292e578086555752cf04323b5
+761364ec433e576f108b1bff6c56d0f56331bc1243c31feb9985de983b0b3300
+e35eaa63cb5ef5a522d2e2731f37eef0cc62a540af58c4780559b4a0499bd168
+7642677ef167669aa264d68a8ef6da7425b771d48260108ab87a077560b2ebc7
+dafcace10cee472c35746fc11d5304e441fa0678decc0652588a56cef7c28ba5
+5cbd94a7acac9a06942a82d6abc1ed45ef21f4f3f5f61d0002340725ba15b9b3
+0b9992741770f020ec318f514874b6abdb27c3f0dee979cce17199b9f3ecb0f0
+709736e55bc48c55ebc8f7259d9e0ba13b46c2983c6e8265633d11358057019a
+9bc40f9ef8830b5cf7022bc19c01160d17df4ab51d113cd49bde4d1e3b5f8ba2
+7d179464b8fa28e12092267d5c70cc341eb3ed0eec08b343175d5458311a7404
+a9463d728eda1642be73366ec57a705501aa054b60b04983e30b9987bdbd1c69
+95ef18d327beb2f2086a2245602495ccb7851bcc5eb302b980ada64a262c562f
+6b2748b8f7dc4a0a06c69a7231fd40568bb236b33f33ac04f1d2f91573f60470
+599ca51abce802711c7fbd95247ec33fba3a393195a5cd54622434e668d1e63a
+a39a51699962bfcc4774228ebdb0b01f29b430434d8023331bd1b6b2c76e43df
+b3c68753c55075856e9ea0f5c7d60f510bc115bb70e0a8eb9998d3f32a0b72d3
+ab524841ef36b198e3ae58129ca64e005f776a0676dcef57e7961dd2b948c2da
+f392cba950f1101a7bba99952251c82ab5d0eb5ecd8af07973eeafcb3e0b5f2a
+3b6c8852ea072b9787fb945cb7cc7a2acebe92a5458e7dab5ea2551a61a55068
+d7023b6193c750e32502fe1e1d8e8d497453fff493caaed83c410eef050c30c6
+e38339831517101b07fc15d5f63470a9863bbfee0c0002695669900aa70a18f7
+a87f1b0b83494c39c3c90a8313060204d19553e2d66419e7b002b25108ba00ff
+a7e60c68b73c53f306e112ecba3b64d35caf59f11a41ae76aa2fb4882ffffb96
+400c2b6b0051c90d7279ffd0df3a6ba63daf802888bb6b98db281b89734b6e2c
+9ebd473352b36e11b5c3f3c7ecb22095b7aae220fbc89cf6036fa963530b8a01
+1cb06875ceaa143eacdbf1015631e8858bfe5917e01737ec6c840fa7097a42f1
+15884772664b9665e5575dc3003914643d34f329b9cd12b1012e93133a27d139
+ebcf7f9b68dc21575f3ef070ef94d4f3f9ea0d3ab4a75e6ec849a4630f4f13f5
+0883dd86a9903613ecf36ed6ab7294afdc167e0eda4b1944870a2f99ae8a22c8
+1b711d1eeefd489009b86f08078f4510c6846b17ae72c87dc51d0433ed9cb40f
+197db7e3c2ef41f077481b3d194aa3284d5400681001e0531348b91cb957c396
+5611aab14b92dbb110db950073dcaf69d6454588bf63c477112c38ac578b0ddb
+18fbf7f0d44138c4721743fc05cbef7cc3c684328df7c4bbc17afd9084bafec9
+99b4b8304cfd2d604e31d96c466466cebb6ef3a2236498e31b3c8dd8f15c1ec1
+c67d2b6472c3dce8f613c8a206e5dbca88b03ea931fdbb7e2d19625d4aa0d800
+b2eb953de5ae22b2478432710ad8efc57558171c332f6b27a2cac9145badf751
+9cf4987b853ed30d3a726039a83158c03a0f457dfc9a81e78d909f070e8d47e0
+252b72d03d049ed6349c752a01b92072a2c8f6c50ae85d6a5469eedab2e0a5f2
+6e502cf72a76706ecaaf7a590105feb6f0c1f52a820369fe3b903b08ec602ab6
+882e1ea21007443e2460e29de7fc07fa72bf89c145b04be55df48c0ae5ba1bcc
+c84bbf0a2de650ded51dbb240dadc2faf3a2fd0fa8749f0b1e372c579f905148
+706e910cd5578d06c8018e3ff8b88af6002c549b87a9fe2b1f804e95921ef83a
+2810e015e315d55496eec2bd16e0b71e4d7004dfbaab8a5d651c4446e343e382
+8204f6e7b4cababa6aed624942902c65899615d858ca24fe6c91a41d087fd647
+e50c52850049c81a5dd9447761b3a308df7403fa4f8669df366d3e114628858d
+17ba68a92fed47c199d1f7d415c8315d30b394177b161fa487225c38e0b9d9c7
+ab236e90e926614f9fbdb3bc081e948319ec3871fd37193f0889ecf2c47841c0
+666b272328bbd281f719181b4e509c01ce80afe08f6244ecbfc136f8edaccf79
+36399e63e5982d27fbdaffab086e1e064c84cd97d4eef959aa3a452e065618f6
+b549e3fe7207bcdf85ec1917612c098236b9124d6f1c037bd0656777dfee995d
+5d125d678fa7ac5c995515e952194f9620579316eb0f0ad55930ac82fa773159
+c1ac7f4b072f06baef03ee07c23fcb4ad8072503469a8e4eda4cf143c7a68d27
+bbf01668c0abf4950277c195fa1cba7297a2e863299e195c6684f2a076427e5a
+a2f308fcfb1dc11d14bcc8a4a59611eb8bfcbfcd73e963d69c82fcd95e989ff5
+ed486f9809d3f9e1a0ddfb573b37d1d3d57e0fa4594f66ac56fe16168a472552
+eca179c7ad26be24e9afb1a759b73de53e9f7075b7ce0ab8bc8e30ec6d68aa42
+a84b36f2c4e4afa9e59c6dfa9af97a93b2cbd9ba786fff8d8b60305b608bfc36
+83c2c94dac72000fa51ff15739975e9ec0185f3b7cd805e67b5ff91fabfa648d
+81e0beddecb47dbbad28a99acc3074f1c248b2231be3fb01b331e90a1bc723e2
+8a0b2e99f2defe00a0b33c13f0a6a37717089fb28b7c58daf478e877e4bcb73d
+54b358c509a630d73938bca5126547c94f88c97cedd1c65a3d514b3ab86a489c
+317554f67e1aecc75e3c71cd37f268999f17fb649cc52ce4ada26d2d4ff1c248
+e9e4f6f5ccb85aa64e90f371cb94da1ecf5b995ded0fa6771e3f874ef449186b
+4b78c298d5c24e5127532b67c9479f70c3703ab7800ba8ecbca511703d6a1733
+7f2edbaa61171f00f01debc6eba2094465e248cc8262bd35cab622443a1be5c2
+8e6318e4f3b7131045b8f676e2086f0432f51983c74fea25cd5aad0797a4cb5c
+6503695292d798c755170f0ae137674d25759bbd89cf4fe0917e32999b4a04a3
+655ea871b30e22b0ee39f29f7915fa3dbac0d01c55a454728869501335f97e3d
+24b17ab7ea28784b6f8c9e0b72b38f64e0fdb578eecaa5590c20a1d17ebd4ec4
+1632b40553aa80fced6d88079eee8097a410a29b549a0bb3b6fec526caa745a7
+202eabbe2cf3f1262db3e6e309e0afc3d9c808fced17b05c320945fdf0a05459
+ea7ba64fc4112fa68eb4887bc845e63a598e2d31df0ab0d64155e631e3ab4621
+744da7c3da19a2e66ed2bf759505e0cfb47a76a81ed7242cc29c7383989afc30
+df86c4b38be0a5c8b360f586f463e53d6684584a3a5fc75495200fc707cd1981
+1a0fbcc0fa784b02140158ef0913170979c4fe96f3fdeb414cad01b38d2b5934
+0b21cf14a28c24a583472ae3cd667ab094e56744c6b35ab7870ee5b0aa22a70d
+dc68afd34b44e45275ca8d12747dc6fef76b826f1dcd70a5a30537e096935f7d
+ba37414aa1f0111cf7a1844c5e2b3f63764458ce9e113d06cc844bead4d22109
+069b5174e0ba5aabef78dea7e384369de2e4142bfe6521efadfe67aa41fd7bf5
+52ff016ad8d6314b4285e129ebbdbfd3c6e3383906248f0ce045ad370b0f90f2
+a832944cbd59bc7cf15a9c17ee0fa0bbe12f2304fd6a9f56cbd65e7cc91ed860
+7d09e58934079a74d02c1b25b958567133657dc028148e9f13a0ff6db812422b
+6c7cfe064a171b4fc5e1764000d4a761d1c476aeb59a45f7b64ba1c8623b1423
+91d79d124b8506eaf94ef8fd28b5fa6ca6aa5677ffe979dd3d75f540d577ea8f
+9cc95f5b41942294df812c4d4aa4ee701f654be2c00440ba575c5512bb5c196b
+3ad24366514f2b4ea4ab0c35927517bfe166c261c0c7f9b73ab0c52ed0da3696
+628a1f230e48cdaf2ae53000dbb2ee3fe17b9785faf74baec7caadb23fe6e329
+b2b908bae996af74097cdb6e280b5d5c2783b33ba106d6f458179822af89ee24
+f99514605cb4e232b62d92014636841a991b3790ae7478d020d6513e70244034
+dec5f2106a1820acac4b7e091dce521896f25dcfaf58a521ecb5d26ae3b8bed6
+c61c3c25d09d159d5ef58e253dc0f9817fbb469e44eed683ffecb08689e66108
+ce6f93327a5ade948640d0e9347b113d846846fae982687d95b66d6a75ffe876
+a2c50ce60e3f7953d9dd5a9f5d65b1105a7849d734336804de32576e81abd201
+4aff1a36aae15c725df295daaf26238b21c6d288450aa4d94e26ce6a1de7f096
+5838c195a97ddeb019500e8fb95e8b48190b020cbf0582a99df66b7192b67448
+c1695882808efb39a3c54fa42c183f65492f9d0cccd832b5590a6c4cf18e8d2a
+e9f50100f197818f192c39d4204b6dab711c2f8cb53e5ef002719755cfee11e4
+f1ca77783034901fa2da7f82254d1184f2f724e36be731fe923c99d444407539
+3000ceb19548d5c11d7aab1e13917ea4391ac626e02bd7a1960bcdf24e048f46
+76fc3cb4031811d94f83a0c23b309cc9a537b599c174dc862429bba8b94e7e56
+56682c13bc4deb402af2334993c66d6ef48cebcf6dcc924a2a1615ee92edb7bd
+221fd3204d9dfc6b2abe84d5b47a9d03227a4c015204af7dc5f937adc8c60de9
+feb1c1c722a911b2a5f186478ad7540ff28f7cbc82d85e461c740e8aa51c16d8
+8f939075b78f8a097f647de080dadd87dd5a4107ae7cbc25a7964e5e8e0aff10
+1f17185e95a7c432c76f5fba2138ee76b7ec76c691df228099c0de7107aa0f5b
+523209576c023ed23e9ea9047d1def8175e515bd1ee133167f0f92fc87732091
+4be8e6a04a796e5affd91f2e0bfb1b47d960228b970cae0824b32f602504cb12
+9f29f933e538a01fc231eedadd0bea5523ce4acd3ca5d512aeffd4bd91adc088
+47e520f07aa173dc6d1dcda5c7a20a1cbb399d7a11694bd2c959d2546d5cd1b9
+b7afba473e7c9f0492c2d4b4ed1204d6f633a431d83b813b5ec5ecbe41b6c8ac
+003fbe65fee1581fb80c2f4c691f9e8a33630940b198deba7491d83421017409
+6dd46e914bf82841e2377e57cf81c0f03db26133519f638d0b6422585af7f7f2
+589aed2dfd15c02f83bb6fbac42b4fe85e14256c5b5f6e7a287953a4f96a0c6c
+562b87ff8a8ddf922571aedf6898faf7f9327f92a6f736546c5699aefc4cd1ba
+b403a43f8464b215993ef53d2d9186ab1218f7e1f2ccf589ee0a52096a153ac5
+e328654465f3f734fb590ed232399f9f6dcfdfe7f702b4ed829e444ad685b1e0
+d0764217ae144e4af82554faf6d013be83816ab94a6b175905916bea3c053fdf
+8d033e48ce7e6a7338fdebdf9cd047624cfcf3be1aa133690eed083e28939707
+778274ae874e906f972080d9dd453ff5039cd351776a1ed52d3e4a8fbc749f9c
+588478826593e0e8625b013ff1469bf76aef8ae06a63ebbda3176d517a7cc32d
+ad93a096258b227102b2b748359ff2e70a438709eb5f1b60be92f7a6c8f92372
+c3b9a58ee0934083f2300c6423250c00ecc2cfc67a6be83450eeaeca993fd695
+939e47e7c7d28f9db30e2a82ad4895092a1a5a734f1727b4734a14743ee1c9dc
+851c9463451042f28a519e8c532e16c6a7f65a11286b85d48ee3bd6f09caf327
+cd87df6f40cc9ed5784744408da6a2e65cdbc6838812b2da414fcf7a33ebe676
+d381f7cb0bc1d84273b03240fd3eacfab2693d04abe2a89f6d884fc1ba2c0c6b
+183916096f98f062bbe990f87a9d74d275f18d5d97e9eff897b99fd5e5f2353e
+16fe417e644364017a1f014cd0ea62ad0a684fcb1f7a4969bb2d0119d3021ea6
+91ad90564b7cd59c03662d4809943ac3c0316c45b3e6576a9cac3f549f04a9cf
+b21aa641eeced2af9c0e9944bcd355444f1ef2dd626d94d65c1e00b208f06fd7
+526f73917bfebde0538984edc704fb56a92f429f2b3ccd2324926605ffb9e06c
+e3f9435778a9972bc1d9511b1c9e013c9232cfa8fa03b8076de78144e24583da
+ce13c107390a4b4a66fcf41d02c7ea3889fcf648bd9b202a77ee0debb98745c9
+b424aa89f8028129fea8293f44efdfea96ca75d1462715087baceb18506124ad
+843bf5b5eb3044665921c1e711ae61d7ea772773f99f7aee6de71fcd8c5c2c9a
+4e5d5a1f02ba9ad0e31ac0673594882b491d8dcc9b7c4d8bf806845cb7e7b054
+e073b9379f6f7b756786174fdf25b080adf0cb4645dd8fe1b4cb5377195bbbc8
+88e09bf98c82e523794bc350fa0fa12413dbffc006a66c47c402b78164fd9234
+b9fae67432f7a9423ee7569d3e6bb293cc0a095f3c5a0abb6a2c52a0c4dccddf
+54dccede5914afbb9bce717409060d5772bf6c19ec65700c6395884b3da5bd6c
+d81daba48977e3af9653875148fe7ddab905a0cf6c5352a468c36ad2bd09d897
+2d54f62bf9af9688c1454b67f24b5cbe39e9c069d6b5429ab9e8b2117c0b09eb
+7f877d667af2af4c06339230c2e2d082f389d7006f8bacf00bcef8b652a8f1a7
+e210e7facbe69f8b0cebc21b6cb559ce9f6d8e1b0546870d773f6335426b6978
+60bfe9e2c6cfd1206ff1fa27cadbdd58c4c2368e95e17bda712c4feb1e89c63c
+45c0cc72dac845d10847408dfc7dd390ea0c50fbf3012939372707be42c88a8b
+025a77150be73e08e73fa1e15fb39d985d5e6666d2a4f77f947eb1b2228ef0e4
+bdb7f767621b7edf5b863d44c09a02b08a41a3feeb239ec9e022d0d8c2fbe939
+fc242a923b082a3035a23c0ae48dae76b4c2147a7f8126ab66dfbb8c892d858a
+48f8b65eb85d3216b8c156ee4005045697a11f0f349d05620b1c0d41575c11bd
+e8165d443d4e836d4b4b2fc028e3ee7eb995115de8bb5b58d7575a260c8ebb93
+cc5d43766f56ce454dff93669f41be40953c42ffcc98a89e9f6ab320f7688083
+32a48d4ff8eb88c84c7e0cbb9e031ac71c53873da074cd4112dc30b4f27c163c
+0d6371b35d0571dbc1b80f5101c90d4745e978091c4c8b2bfe8aa23db4979a9f
+143dba6c041772bfdda2077985fe5da829d9de5d8c5c9d02175f65e23495756e
+2ba67a1f46d862bb76a4f330c66adc2df5303b34ca0e705f28a97e2782105359
+3ade715c3f7eea39e401af757229bd314b886dda55f418b3a67657d199cdc8b4
+d05a5b7aaf37b53e1c5707fe5454d512c954b6887731d2626499cccd6e80b1cf
+9b3116814c0f7705d53b402fc4e9b6307b22941daa12d9c66849165e8de01e0b
+20c33b1b503c6ac7995d4bda83da72dee3b177389d500061e16c6decd05bd391
+1e2d1727b5436aa550e1bd505e91ce0efcea62d9fcaebc098880180caa252f6a
+667c75a3185f62caa0c28763104f57e93fba3a396d7015b3a55161401970668c
+7e6ed168f6e0f1029216c27c33544cc77111b2ef2b1538f1e89397a2e78f0319
+28e948640198d7d2b5684f9566b5e70988c2516a81623a6fba82c1554f3a8a85
+98c1efd626eea0ea586cea3b1f6b62f97b1ea416332ae107f0f174ebde1f84b7
+4af56a9495913e0e84ed0d8702ebe1c699f8e8f056de6e7728023e6fe0dc68cc
+aa76c3e2aac6eb5f5ee502c1098db8033c4432fd0c6f62aadb989954d81e4247
+6efe3ddfef52cb066d3da1811e1bf13a9da68d77b3d88ae4b5e0ec252a992b2b
+dd6b487458694df4bb1f300f88f6891f2033dfd4ed6eb147315ea84efef20c16
+3b2acdbd1a6812ebfa51f69413d1750c60d374839d46400b9563daea410e4bb6
+4231cc49a6db6f9acad1bd930faa27bc25e636deef091d07ee8c405eacf00d37
+a67027a49443231fb38cf74b4afe723353838866c9d56c2218507344cfe23e85
+264ec426283982e0e299b8557f37010c921d4b1e92b1997498426d3690408685
+5e0ab86ffb4c74192269197dad437aafdfc98809cc865a72f397901a02e22f97
+f035deb4063864798cbbdca6adfdd2e1f644c299d5acaa8b0ebf0102cdc1e478
+77179fd1e5c69595485ce83dd18e3a154e8f6bf6aad3752f209ad3e561cb2400
+0959f964e853ec9cde9deea9278c5988db3f026a6120e0020438f44066d270f4
+af05c4d6568fa3b288c0443be3cc406b6ea555ba97b9fae56c2b7e75263397c4
+f247bee72066fc445c158297eab5151852dc2c32306794b7be0a5f23c22f76f8
+4c80909a303d6d29d808a606b8c0739e69d715778ae843c033bcc8065ce83795
+f3d5b1a403aecd1ce8db79b148946d9f96673b51840fe073065a7da8cf8a8e98
+0e70984d88ef691e21a7f3b52c2a455286d80e3831ef4b7119459970a77e2bfb
+1b2e12a6e2cd4e02d80cdf90fc00a1fbcb42817660487d5fadefde0278d4fcad
+98fe39b3827b681d4427fe992adae296946720129777c73d954e1f91dfe80130
+362fd97c8a7f693b73ebe45155434eb078fc66adde8f9217583680f5e0c9f80e
+c413226de65a1e06fd6bb4a4870e8702d2d77eeafc0ea5634272f19f31a6ea21
+b51de45449e89807b18588c9a43524713c31f27be41941c32da7c5cb636c5e1d
+0ef69e2ef2c18ab0746cb2d19980fbdcf9691c1d74606830522219310193b32e
+f886e8f200135b55e178d832f0416de4b86e2fb4eb8731337efccf833c044446
+1ab477118d9b6d53ebf5c42e46ed8e5d0181eef7a15145d5a51d7a1b8f82189a
+f6ccde81e2f1da5a0e419838b90adc945a7e74a2132aa57807fe6107caff8296
+aca45bdc7366cf3b738cf402674c2c2ef0954ccdf7c7df0377641e9ffa12a165
+725cc7bca1a0d38dbabd6982b2f1c432454919fb58876f7854ec713ebf05e0e7
+01d1135f89b31ffa94413f30f72527de872418559c07c8a87556fb558bbd7726
+aa9cfb99d41a7ddbc8d5e4f8aaf20619b2dfdc18ed319b958b5812867e5f4442
+fc670f55f78be8ec1c3ed00fa36cbd52c5c15d45a5c77746de5aaadbce70eb29
+05c3972ddd7e2c220ba4137a5a6aa178af65fd65f225393a08197057d5dd407e
+9c238702425991496f7c7c91754399ff3be95c95704f26de0cf99aabc5786994
+9b727f5cd72f4428908b95f1b879c2366ebc061ad4ef68ccd4f957cc76594d88
+dbea7f48b814a94be79e2cebf4de7d2f6b2fa680baab5196b132c5970d035801
+e021704e10823478c68b4f791e63cf7d9725088a6fc2279e1d09a68b04f31901
+7bfdf33caf58ef856a1aaa0793d327c91a2224e7239c211f1210ab11cab44b93
+0ac2b75868db9a0faca784a130fdf9f47cc09d44f9bc9f697f698d2da440441c
+1adf05c77b768535ec2dd30b9897c52b1e28d081f1dde26d5e8438c405277c2c
+2291b1911cbd914a714159a957e0b436583b1d90cd36c7ba1b6e1041c8eb3996
+dd2715fc01e9a82bd1590cb4ad8fbbdac9410beda818b81260a3a8742c137f6d
+175fa8bf26d648c991fe684e0e1469900ed0a53b7d44e35e63c7e1fd0b5aacbf
+5e9b854f302f3ee9c7620f5c9e2a5446f7653446f0bdc192a34e23654830e5d1
+52bf36fc02c220ac72569f80795ee9271fa89b438a8fa4de0a3fef6de60310e0
+77bb99f86ae3e0824c593cb2d0387faf97b3612131a2b0edbbc79a4444acd77a
+53ec23d447610ad92120f7702df3c0955e5870e1ede213b5edcfe5c0405538bd
+6d1a06aa55a8afa73fb82a29c6eba9adb721473d0756c31e77a4012d10dab6e5
+80c90caae60c3ea5a846f74536079bd790979059936c7e879eb79905e3b67b3f
+ace6bd0a20a8afbf1034dff455f130347f54b6f7862a772f0afd46f3601349e8
+9d1caf04d0598df4cdb164190614aaa872036855c02c3c92374a3baf5453baec
+0c0d2d89d79fd0452babb562e925422d17e888a9350fa50d9b34bd119618a1a3
+50f9059f36ffc5adb1f29923af100fadb5d32aa948ae738d53ba04c44c3afb82
+e935f0ccf3a7903ee5f939f10d3668c672b75e90777354d1429453010e4f72ac
+23bb5cd7ddb1712a6851c033bfc10470f8ae87308777dc1d69a1b95e0ab1a655
+e46f843d3c4bbf2714177d4c83747f918884d366dad7705e4668087eb0d13548
+2d3c35939ad5402b94d3fd7f2bdceb0ec55ec0fde7fe7a555ab921dac698917a
+fc4bd079834d26e0b1d4020bb50588e961658935079e8b542f927ec7a7e0ccfc
+d03a7bff24ddedd6d7df33c3507c449067e548b0db1a42baa9e3b1d0528298ce
+981f8e35b10b00f69c92390e4aa06d2b3374e868b4ee0cf0ee5f59f1c734550f
+640bd5eeeddf30a340558a831b9e39bff15b35649a22b23b8f39d0d473a07be8
+0c757d16f7738a0190178de0e5509ad9fe624499d7464c43833515c9a5841c1b
+e704c88e2df4508101dbb7330ab3dd863f544294887e6ad56bdf15800ae7a0d8
+e523cc1bd1f9424576492b11b41ccbdf5daaf31e3278b3d1c9160c9310f604be
+1e869ce18ea776bd3ad33ad41dbc5b3a0acce72033b37aaa5fd6854d0f2faf59
+d2e674cf97ed09c3ec2f7a02657bc036f78c29e25284c7caa6e480beec4772ff
+09b02feed2cf374a02b43f653c1d095d21cccefd30d2049c50327fc38bf44e61
+1e5afc9d29d37fc5c22f62b927fd2c5720a8025194734d8d361bd63745781d3c
+996f448dd51f345442f1738ef8915a69bced678b6dc75ce6d77c44464d4e3c23
+0f90600074b5d31a870ccb24ccca94e8597177d51f85f9079d3de3ea0978a8fe
+f94346f14cc5dca7c6476dd60ee8a7f402e498d91f4c3b8071f5238bf16e8909
+0c0d2b246fd6ac9bdff3ec3633c6d90dc5ae76e353293823b20abdaae12a66f5
+19637c1e69ddfd24df8a76807c441732f3bc7d334bdf5cd5eca5b3ef88798b59
+6f0eee2d147dd35f57c4a776a9c89eb781acc56491e6adf40b94df9e8953df54
+ebff8faa7bc3128a375ee91a17ef4e6ac5fbd17f84db3bcb2ac0468efcf8575f
+b51865ed827e1dae4a3b601239519fc552e9959875285d9f688b1f4da4a62ffb
+b166645fcc7e587fa82d02f8ea43049f9c589a3b570c16aa3d37f34f999c4f13
+ef852c19789834269ecdc91433b6351f1936b24fda2c6614a467855679c50162
+fb62daa72b9b971511e81c8dc7305e90cfad4950bc2dcfee8f1ad6f30c2133cf
+c117dacb41836efe6d5ddb09b6948475c21a7c7e0b843820564b7f4375b43c09
+a07632390ba4fb796e2ff6b45a0584f763873e6a36768ef958c306fdb23607ba
+73cfc5d53a282585b9c7c97e8b59ceeb43360107efd16c2d54f825fc77e4eeed
+50c040e210a2be8034648a0cd886e2720b8d939ef7c94df36bec7ceb983e7157
+5d8c7d688fdbea4c23b8bdca5e5719ad6bbd1384032235ab03d05af8e86b2e01
+2f6ba96703f6d2e4a014c3de80067fd3fd665ed1c64c3362f254e3c7fba06800
+389cbf12c7b229b66b2df82a823765b41235e31a1e348cf57da0e304fca82b44
+0b2af48040f04750d7df18ebf95da0101b418c3820e161be149a3459e1c39758
+e8063dd0fce331702e9f7be3d10da99033bf2c90a1a24ebc48cc276d78f61cc2
+a6721f3ec1d48d86b54f8eb88b9ef248ee50896901be472b8196f52289869d9a
+a656932d4872bab54cfde6eb67537c70078bb808bdd52f4d4b99f4d3a3d7fa4a
+ee72b2b5202983641496ab3cd44434d44062db39d6c3e8b60ac6f73ab9b101a3
+2d687ab24b84e27043126bf4eee162f2cbea040586f49fd91b5d4069704bb9d6
+9ea28a6c20643c3f92f648b38273abb8bd944e7edabfd4e3085056f4b29fbe1f
+ff5ec131de60fa61812a8ab0d3673272309e36f6a1bd1552a3c41af2a2228f85
+f3809d222aec8eedcb4baf373f37d2a5de8ecc62ea37b335b81005f8e92f27fa
+6b2397952bbbe44fe9a3213c33e368d9dc37465e84f538d969f97d55b31956ae
+7d8522a5ee6d6fdcef2cb6ecb636de6ee1b2e7b44530166f7b2eb278a0892504
+30cd9e39ad1d38748ccef3f201330fc43f12f6581162775064b6275c6959ad21
+b5486b450d8581de96291f870ed3768da5833520cc36c14b31a90232d1d1cb03
+de7f8f0a506c979b765205d82b518a8ddf6e3e494b4217e47c5dd770c51e47f8
+106f23e7e9c0c4c9b2bdbba64bef04c5acbcf437db53d1c47fcde3bf88d58f23
+fa8a8c482b5d45c78985e7e850b676d773a4ad42bea32e3090403c01c1532a7f
+ec6e008ca9911f15319bb2708a57499e19b3482537ac0bcfe8a2dd398b0ad1a3
+a4f9812a66e9b82b095da83a9925c4e9128ad159dfb674cef7090c06626853c9
+0f5d64f662ce69cb135a4ed6eab6f69df2f376f1e3ea47a1903d066441d157f8
+858848b89aa4634011799b26842adc6981686381638e9d7e2c6c8ca22e8cb9ba
+d86986aa0300a224d2d0bc6fd227fd557de7da66c301e4cac1aae538c62697b0
+b2258394e0af0333ef56007b98b436dde0d40937923c9c72ad3e3fc7b39d1c70
+2851b4dde84e697f7983d5f71089c3f94abe8ea47392800d7300f4576e9498a4
+20b537ff69231fe3643209c59c485471b2cfa89aec05e50b2ab081eefc80e1f2
+b08f772631c8c649497745610363246af135e2c3231b64be0d5db3789aa6a000
+f27ede24cbc19346557465818d4bf4c4286e08840f030dddf30bb175e81482a6
+bdbdd5c83a361a28b8ea5654a9066b5c8cfc8f5d4380cae506d6a61cf0ad481e
+832e08f17bd9bf87749ef53d858b2a9eeab8d7be7e259bf110c4e3313d6ea87c
+1ae3664089e4af8160d334fb3c3005de0d5037211f888ac0d959dad0a586b61c
+0dddb0be9c5110479f5b0674d00f0e54ca7f642b62b32593779591b4dc119a25
+1e8fb9c40b68ecc9e5a08e63bae86aa52235dea3902f3c355fc1ae6b8c79644c
+01a54c68fcbcf46ebfdf0a54eedeafd5d1120bf228ef499913b4426ba7dba017
+75d6e240dcb7d41352b5cb624bafd11fa37fde8b4d4fed2c106a34d8325fc31a
+b86701cc72fc1e8db68a1508337947352c46d46d4393384cfef2c9e9e9d6d877
+f13780441ed70e86aee02516ee87bf567857f6af21aeb625ac3e30fb1b873bb2
+862a5ba6c7a6f30dba14fea378a41d38116214c999ab4666e48ce091751ce700
+62bd9e47351e9458e355e08bdf9f38ce3f52993df8d6893bdfa29813fafcefd2
+217b481c1100b8db69b9f3a2169c2a66300af2099a9acecbc542f140ad5d3a64
+6a1d818b0cfad8cd8b4b8a3f8e8ffddc03ba62a624e72df170b65fafed325883
+feb1bd45e3865043bb008d3055c08a3e4c95734db50f983f3ce6b6fb9367104e
+c44b96c80b2fff43285967667a3edfd341ce587b212e12b2fcf156f03b0848b9
+3545bf50fe4ba75c13529ee16857d317ae98a934a369b83db5f6039b72bc09be
+6d14c88a6e2c77543796939789252b70f51fa37a69b08551be5416fe8c785110
+30264f52592909c8e7f81f54f1b37c2b488f088e85994336c7a075779b168464
+fb37e8f7085f8fbeee68fb68ca9fb2986a53878454d72a3ead0b5b2ed507528a
+ff6aa53b238380d6f0e59e991f4092d68e2001c0e6d945b810141d3d1bbb7405
+46d378af9ba3fefd4d44f6caf78b74450a0e230431a5c46e79c222c3767c4132
+90cb9f75baea5d1bbd25a718e8cbbe551441b642f10173e90aaa9b2cd6301332
+0ced79e66e38e8dd290bf36fe33244548216916fad18f20da7b73e31838930c7
+f4a9c44b3207eeb6c103f633f3fc8b2b74fb04a072fbc156089ee3f7351ef196
+f365dd43088f4d4a280be7e81fe73293912000e3e5f493dc4bfc275b5b8a8796
+bdbd266090733ba17e45239f7c0dfb4153be451a8560de6551202f700efafa6d
+2bdaac1ba9c8f6e084e4b273fb8c501f336b005cab01ffd38cb6d27941096f75
+db1721eeaec315842dc7938dfa1b9e530b285604ec2c6982389e6c22fbb126c1
+e6a21916e06ee778b51fd72db2dcc11d878f21276a00bae3cf3ad0999c64d007
+f909d0ee79251810c64c4d05a2a759ef0837cf5563a8765817003dac3aa1bd1e
+7e3b868307e2d1ccc93b1a5a0a4cc32b4eb6961f71750644fde26dc7b01a66dc
+667005b396bb597d1eaeaa55b46506d447449497d29872485b31207e460df2b8
+c5b181459bc68b686182f1b6e164a825199c3ee24c1bd6a1dca450ca1a3fde70
+2d2e89bf6d8b98e4c4f25284b667cec77677be9046ede444a1b93b3b03db6aea
+f89aa0759cc8aa7f10a2c4b36f63487f59bfd6e3a2025f26c346847fc257741b
+629a3f662e3bda8c3c010c5dc1838afd26f875a56fd6042aebe1dbf119cfd387
+477d713cdda669f61ec1070705382520548a94749711144d8311df5db319ec0a
+033f2d5c4c639b5af42549b1bcfd95afa55994389ca8a66b64c28520f821c808
+4ae9dcc9d6020aa6b1f93de064318e5e464e8808bdf0ac519653752e612c9149
+6e3c8e61434d012a5e4537465cb678b6e9a9005f5039e415dd2872ff0985439d
+12d524a4f34c520aeecdc4d96383e1edd0059943d59d47880f51729e54a68a98
+a03fd6ba4ecb567c47422e04966c4655f9bb6f74f2b82d4d3ded42a42e90157c
+6639f18ecae1cdc636bd616b60218d1b6f8294ecd09a88fc5928c37c9304d7b9
+5bc609286a3524fcfb9e406f636bf1b40573225e658543c29be863964556b622
+da308dcbb6516c88155f6d9b8ae4fe34b17374d46dc560809fccb19e87f7fede
+beaece1ec0f41102b99ba6c4de9dd105dc5d90b6d3b392040273e0703b204d26
+89d1a340d0d32d6e201b91244e07ebbaae8b4c877a213ceb2f1b1d3ca07880ec
+3f4817c00d48a06102352ef4de3440699cfd69ffaddfd1b90edb42034e5b8e3b
+c42fa37c38d563554f8d5b96956879b63866e552fb931c6462b934dd0454dd9f
+965055bb3e969b0063dfd2bc299df5552d8edb68b6530673fc08f3a0bb96557f
+13b068d62dd8b42060e67d2584b4e6fb97e9dc7439f5c1d23c6a324e72062395
+6305ab4be3a430141d8e615b9f59c3a3eb2b1267ede3987cefcc1419a9f6de7d
+d73ff35749b44018da9e79aadebafd0495ee9e29ae4fc7e420d86572f735134a
+f54e8f853b764c6e0319f720e8350110c1b300290c8bf8fa4cd3be47533482d1
+53553691ebcdfb46c6879347e3573c318195bc13ea1c732d8d0e7567eb655b22
+1960e8476188532af8af85f7ed7d399ce2ced204894be3a592ffbccbd831f027
+7ae604851702b50c4ef6482c78c77c7959869e83001f7f8f93bb3b8be70d587d
+9e5cae3aeccfb455463adef4038ccb6eb8447a7e1992929f5ff7f8e6f37d4a11
+d5815c2ae35f90d3708f3e2a166953d9823846d87d2e7a7419abc47f4044c8bb
+a9129cbda3c7fef7725b481fbfab1ec2b3700b3b3780ce5e711663557e873531
+72c3bf0ea8ba03a66c5033602f12cd8aeb3c5cd0e0abd65c936cd7d973e8d3d7
+c5d09bec259f41005187115317afe5f6ef895eac769459c0ad7ab039febed7c1
+dadaf14d015a7a3356982c242713e27eccc7c4d7237052b004e8e5f15008a826
+ed515dd699e6bf60e3a2aba08c07b8c8d9df393094069e781c7d75b090445c7b
+1741949aeddd01691102b001781598b87f493ed956b500b2d813d50ba749ea1e
+bdceede30f45e0fac0aae683ae087a635ca344411c26a2dcd04a084b5d1774fe
+c1f77079af1f9d8c75f383b3755f3ea120e2bef4375dccdd221e1811b4b775a4
+f45b142ae17f1654a53a938eab4c2b2e71293cdc8e2d18ebb8b502af594de4f5
+00795c3712a58bb65b8287aadf183e508d907290cdb9ad20e578ee485ca6aedd
+432f01a550980bf7ffdd0a61834748fa4ddd4d2f561b9aabab279e8c131f4d32
+68d89b5f8f542d881286b3c8913a0a71036058755e2f47994157f645ff086729
+67ae4c8461191607b1cd8c4c83298a4bcb397fa5eeef83c5955b301fa270c293
+88d0fa7d58b00f5f79cdb0326172364a0f4f91e189101a2bbbbd448b576c275e
+ae6b320f61d6536649b0b0f9340d25ed5ef2746f385bec9526a2d989271e9a38
+302c84169518891d336882d85871ab751c147d1c8563e2d7bf5c3a660c0b0ae4
+9c0981e96dcd4ec45c166f7a34270018a852d5108219486b706f84ebbe9ede96
+6dabe61fcaa62658db716919af23f384948b4ed54ed369dd3e6e9a4f0422f35c
+afac114819e7b1b9a09bfd1b985e18e6dde975e610f48d752eebea00ede16f3a
+0b090103100000d0c213c4425a5b0ff3193941e27eeb978c7a42dc6927bf301d
+1f0f366a5d0ed75b8a969ba68c8106466db17cdf5b516a0ab63f3772dc7ffcf0
+4ff5a952d0798b68273dcefc30c3324ab824690821875bb8580543ada7b15b50
+aa04cefb22aedd548ca17e0c334493245c2eb4fa7549fbf0833f142967753111
+96dfd986a2536834b4dbee2945c2ed76205a05b9fdef35c9b4e41a808220e6d6
+e0349659c3ac8c3f86462866807ee5179d6994dca75b8d162763e8af6107b272
+bb10c72d297aa93c0055125a426ca0ef9a2c79a4b73060ff6b45719c3dfe2924
+8ee4430acc1c9c28f8b93f554f668cf14f39a728d7b2cf00523248d5877c718d
+28aa6d2b25e1b0ef7fbca7bd87a1bba4ea760b7db91c7d8f0811840654860fcb
+ef679163d03dab048f05194924b68433b76330a6ac806034e954b80b5a7c8652
+719aa83b70fd434ede624963a0ffe25bb1f72b71f3351164d1124712d58a79f2
+5c60a3fdecccb25a5658ad50961a756880b81698c2f2915d13f02ec6cbd28edf
+587457d825544af042a800e4387e004d0701b12b8281bdc76197de64309c5a33
+463bb7d317e7646ea2e7bdf7c0a38e82b0142d51a055bb90689f11bfb8cc5065
+b98cb98bcac7c740d9b1676a103af5a3501e6949ebd9705131b7f30f10323ac8
+0e69159b8da52acc1bbcccb8096d378536ecb498900fcf1d21a92dfcadcca90f
+cceeb56d38916c2fb64a9c9df5ceada68553e18d5c5d184c8c23ec78dd5fbc1e
+f685737e77767afdf33fc585e5d4e9fa1d85446b1b209ed0f18e529dc1487bef
+57c45e10759705297476b4d9b323b45c9d7b364df726b9108f4efdf1c080c151
+5d379adb369724746185abfb9567978b090f9159317be68fefd735a62d686ff5
+0b08e19c87f668aebde88f66fbba5c614415c7ba4085b35b3b086662064ad401
+abe40b7c4d0541d69b745f7e4a9ff3657e74c797ee656e721088409226aeee8a
+be944ef94ccc9d0d7f4f059eeba35ad008e961a252119950277e7f1028305b7c
+a09a5f4c99a3620001d53d8df889589c9aca04f525a39a67fe2f776fc650b3f2
+ec5a895911171356df2bef1fb6302d4e9ec698af4e2c4320821f37533e1cae94
+a5e14eb18f98a127c498ea7e70b1c8599d4ca425272bca3aad3f499130c386a3
+ec3632c8ce2d714d309b52a90ae662ffd310e335a3690bdd831f52ac88279a31
+c3441d8e0c6a741f615c78ef08502af34d81cb1ebd03af02a3fcc76b6648efa4
+d8e550d995bbfa02d5a1d08d0625432267ed55aa34fecd3078c75e1d8e4185f0
+390abfacbf6dc83e8e6bd75094a54a28ac5c44ba886c3b586759e192beaa88ad
+b1f1e7b59703ccc55d0c4383875f8d9fc1e24d7574828f8db01ed10919e3abdf
+f062f0350da3414719c765b600a281ac245f4f7a33efdaf6ea3cefcf081158e3
+1451b8bef0e8ac74d0ecd0ea1958e171fe2a1f23b8557556927b4db67a09a077
+fca48bfd110420bd160c931c07799fddc47df1d5a7050bf1221bf3c5335f994e
+c002a6621068cf4630fc508cfbb7a89ed86593370fe36d559e4fad3853aa596f
+50a4d4fa7c28ddb348986fd5e41c12aa6612e1b8ae69723decfecf062e7108d9
+f0b6f92c3d9251d7d061911fa6d35ed466763f82f669b3ff5dc1cdc5ecd21b23
+6424453dc9fd542578983cecdccd279cd52a2461499ff3709ca9b52e7d8ca177
+b59ebb7aac2e5a65dad9f3d07694e4f161b6c78c37f85cb9d21f2a4908c523e6
+5faf1a391066e68344a9f15467991f809d421af2cbdb9692e4ad9e64d0175f59
+729db92384af1d1c9d058b4c5c9808404e72db14c434929a2ee56772a623811c
+4bd21e254fc99e23ed1f8b2997ab725f809b5b4a6a9019d61e82b151a3a03c24
+4189ad819e0c0852ece01cdb4829fdb205a3f704480c57235cef0b9960134ab7
+4b3bdc8ed7376980e75d48829dfa6b1f6b3300e2ed170771b9c450ff104ea759
+666461d62a16674950d54d804ae25dbe2540896e7a9fe57006efa21a6efc9630
+4836db559319fe389cf960f041a4d9a0366121a1ead35032afd4b699db7e6252
+abc9a67d9bbd956f5e99de22a0ca48ae978d2c8e018bff87406a9f0c437eb552
+e19d2455b56d4ef37b47577d009d14b2ce56b87966b8d2de7a7c423f89a22ca2
+60c837d76b2ba0d5c4817e9f67ea9348d65975fa356851a9e56c19bce0ef8aaf
+3905b73c16eae90cbcd8c1eb4c8e9d840cb7158f145120e3dd6398e3a2034fd9
+95f34006745ccac381cf8c36a13bad6cc4a5420ebe75ee9aa608437fa831d5d3
+3113083cad048dc0730667388bdbed24b33d2c39f67f501f948fd810b8111903
+63e2fe20f823845f9a876e679120f93ba686cf408bd7d69237538e3751c20e84
+4b48e15f86188b8b1002be79cbbc75f226bdf458193c024629aa680f650cc8c8
+71f4a6983ae5a1598821f3f4d8a53aeca3fb8b3bb4266c4aca496110962a8de4
+bf4d133bef58ce2dda36222249d0532cb935804e1ab3fa15066bd90ce643ef8f
+4446bf23d64a9212f5386f4cec60ac632c0e9d7404eb70303f35c50b3ec8dd5a
+99d309b6b7364cdf5fbc1ef653b661442b260764c902e8f4cf6f2ff2252472b7
+53fdea2300d5d6e1f77e46494d6dc0533a77fc007e723591769fea95906c9a0a
+c65f04a8fab8fae134b1227406bb3e44fa20a8379c91318d21315f730b208763
+654d279af7d123b98f2ae67bd5b2f7920187ca936b45b7e3d1da116953f1416c
+46a41d922df8d5941e1a860879c052020ee0ebb78eab5e0936b1c8d02eb4061c
+11c2cd1098e566a180e63cbc8ccf70a0298f45adbfd0db942abb2470bc1b9c3c
+98f61b3ca787d0f823c90231439ac4c5f8ffc7057469b2955b9bea13dcd9835d
+8d020f593e21b87cc8becab62522a1b1dcde669760e830be5c3bb7b12decca2d
+785e1eafc5d6bb3ddb447c2e69215f7647d4e725a2c647746b7bc7f5df9d3189
+d6239ee2a2bfded2745418befb8def463433405f4b4c278295c8c2ad9ee43917
+11353ecb9ae9685f17550f208bb3eb7179a1fa69882ee2691d280ead58e93f37
+70ca26ac429ee5b8e0dbbb7dc1876f11ebe6f1a0dc330cdbb24b9cd1439b3044
+8cb62e50b33ff18099602c620af45674a3ec0c93fde12ebde8b1543c1f9c5d0e
+0a12916ed048ea3244f07060235145cd2a366581ea658b85272d59cd429fc509
+8f62c9937dfa11d29ef22c74e5115f6383d5aea6b2de0ca4c5366800a2bd802f
+162c3e3774b12b016914e4c4e008d81b0278c336d764dad0cbbda4652abd4a8c
+213c7da9c9fd6ff7270575c6c15bf622b6f6f4802ff6f7991d371a7f06755ff3
+cc7b344749f239036a68803d367d121a24241a559e8d64b6658a8363371cd9e9
+b79231afa2f6284a1ba5b99f18bded77db058469932ebe7d405cc7fad3224536
+fa5fa3f7c643637960e88b0e50014a391c65f22424f7edada3a7dd72a3bbe5cf
+47a7dbc05a7955d77fd115db666f4858df46260425575b8ad8ad44912540ff7d
+1cc336c3f9056df50f5a0a2c34f12830f81a332b982db204d218593b804661df
+5adf14c6ff71504cf9a6faf948601e3e1319c806301ba2915fcb59208ca378e7
+afb5adee01e4167cac25e8a1da13034162c5b1d648bf608174a38205ef838067
+c4943118642ac1d5723eccb0d5e365226ff4c34ab97cec6c9ccb7b73c01ca8c5
+85ec906334753fe353bdee090a79bd56905deeb8e4314620e509a83867ea1c19
+a16599dcf0f2d2f3eae58d8bcd8da926a1af99b4057ce29d6ffdec24a7db1a14
+4ed9feaedaa325b6eb583ed87901c43f9f31bb97563afe8630df00acced9d95d
+4b8c1d0f47e02a76730b960e95fb78a4a11284923e9c91a2a5804c14ffb3e352
+943cefbcb1dc6ac888c3a332528a89ffe36fd2824c48eaca69919838665a3367
+7d528ce020fbf68a32d10f0e0708e16611e734a0b20d18aa6dcbf8c450e2b5b5
+f09d32c94c115e9ea5e1a634a163cec01c3a2f3c7826ef7e9d5e8d19d4c4866c
+84666db73ec62d9c5b2508b05145522c3da50a31871ef2b6ea0f48187c191e06
+e0d8160390c05376f0eb931bc07007fb313af86bc0934bcab824073d6b30aed6
+5bcb843f80cfd4c3ca11926c604512bb9c14ddc64bed2eeb5522a4ff3120fa47
+d454352891efe9fac5fa88660011a8c0ec0570e0430b19b698160d4ddebf7051
+079809d76d8aecd01ead398d4e2fb15423fb38a6dbec48fbad2098a9ed738f81
+f6330165622f646fdb5b148ae89168c3a1fbadedd5d316246ae37e45bc896d9b
+aee9dff0f5de45b6121e9a092ac8b4c69eb61138505ec66b367f59b0c6f18fec
+c0010864ccc7a0dfb650115242f1664ec78b1861b93a82a793ad55f1d27d646e
+90cb1534ffc6b9f5e982db9070fc65c9559b67285696acb8c7d680e7f109890f
+4df338c15cc2359fdc3d52bf64c5f1f04d2507e80b4f3c51e68c2fc099e56fcb
+89d0d3bc15657fce79a2633352e4b6ef022f23f17c9b99006de8f42b6c09f5c2
+c82d88b0bf013d7500e10a838d34098b657ce411d22916acfa09121a3a84fce2
+a6ebf229b12763e2f5777c99234ec8a966a7858330ef3330a3e3d0fed0c8969c
+1d5099164cc9f1dc0e398cd9d30bcaf6a4b02b148705de166306633ae538f9cc
+e791641864e73441382828e68f406a673cae2853224428921ffec808630e495c
+64ab4c0754cd85327bfd7fc9ef957642a6d30258b4b9d29bc059392b7e3fd339
+5a6e5a0eb060f9dbd09d110fadbd8a061b1ee8880c61e01fce52c661e1d1b5f7
+cc45b9efab2880a9bea2bd64e8ec183632b1a7a996baa7eff23b18ccaac6c88b
+1835eb751974d2930c1f354f5173c9266918872eb7c70d90f64113f12b86e6f2
+eca0e29c4d4c20fa26df487b9be3323ef6de8dfad3f81c7a23a4930fd3a1c6ff
+ae0a952f8e074f0f034d4dc16e0f3c11463a57ce0c14fba845733568ae0e8f46
+1a00d55508e00a80be53d818d38acc4f7212c628cb2047f18bcaef075e7fb3d9
+6d090ea9a95a21ca3e7f797aa2934653176bd7ae0e72c52dc19196142e571338
+572a939790b6fab6592f56f8a876c99bc0f9d04afb37da897e2dd8a3818f9134
+ff6dfc66c7142738167ac5688b4da11dbbf343aeab2fddc82631fc7f5e3e5bae
+c857ff3757de3c01f097db34436e6d9ce0736734acc91c447277baf1df07c5ab
+ae7bed411bb203043f0c16155e38f62da67dff78f94bff4e8ee86e0261628487
+0290e8b302f9941c60a04973aaebd7ff2c4fd0c5fad6e85625921e641b73bd91
+8ab85ecc505552442e1d1802460024a9d3f523fde2d1139733fe54e74dc1322c
+0d67261490354ed262e2601098689d583f0feabd9b90fe030812442be5645817
+5fcb9cc603f45146004f4f06d3b6325adb329b6c0686403678ee2901165cd44a
+f85d7977c6e3c84a0c5f5d7ac40be097e91259eb0928f84166b2bea549d82ffb
+e2187555d160a406a8f273d18778b42f37ec7045734e579b9a7141f48559d8a1
+2a491e774eb885449bf57ed3737c42ebfeff262bff74eed2d0a2a8915006950d
+24b1f2ed4bccaaa7a37f126759e52034dc7264eedad951a69dfaa509af59549a
+f2de1eb4a74f18abd83d7e8bf00ebc4755ca342836ecfdd0c11221aa26c187ea
+95700568592036fa3a03b7378633984b7340e8080f82355c2a572579c06d2c86
+b3602943102236aa99d971cd3fab42db95dcedf44d04c8f05a2a1381c6e9c58a
+a526db2ab99b4033ba0d339ab2e1d93b57e691e66efbb79e627f2d0eba005080
+7ed4a60011be87ceae68fade61f969daec01accbaa640cca88a550c7de3b13c0
+ee86bcc1b3d11cb069bb2c205c205b35da97c554e0eac66dd8585bbfcb18c8a0
+cb3941d9eef3a493a2f7dc187a480d21a7b4e5285de82b81e04d6e239f7807a6
+6a334b6c7bf6fafe27c1a81c3957ac1590de2f5d74a0c9b8a990e2dfbde91298
+30c494a28e29557f3cb2ae8a6637fa3d83eb11331f8a3554a1419d8c5e84c1e3
+35f4325506a08bc8ae49a113a5c3a10f6d4b5446fca9a287d02d8db0bd53d706
+00137e83ed703f3e72e6f5f3f2fb14b6dc3e61c1bf7d910f03610666f5579a81
+644c5eca8ba56df6790561ecc91db4fbb7872fa49d2b7f10ba2aa28fb9ac85a3
+4d571ad54e98993f94b7c551e237b314536681cf8e42a26144ea39d9f9a9b295
+9aff80e5b965d2b3127d89e27ab1c8804c68ff4a1ab678ad2f1bfd5b2df38985
+bce719099c9c7a128e29ca4ba9187ef9462d4059034d7379a04aba778756f48a
+2373c32c4b29b769c6e9fb458bacc4bf10bf25320d757b76bacf1745c0593e2c
+fd20432e32bade5a213d53c77d56d34d67f436c1566395e6143c36c16796cb27
+bdbdad5cfe3dd3eae67b9474edd492c85879169324cd9fdef636d5b9c3abaf36
+edbb4ac14a98893e36dfbe7316c3b9dd3c072327b0bee8bf09fed0a0262f0e5c
+08bb6dc0474ac2b428a4c1ee5b624bb1833be8b2f4b14c254d7129a2162308e6
+48035a579d1a7c236ec5bf6255630be94bf6129b96989b933f9ce2745f69140a
+515da1fdf975588afbd4c39201ab3ae8b016635c2bbbe6aa27bdca6215617e2e
+b9d4c0e1fdcffa313d847929c37ac7b07f96b0180fbbb67f46e9b6cfcae1a66d
+4be0afa99fa7787e7f8be5a04e723d4f30abd5a25f080f51de36130b7862b025
+eb56872f2744686fe5959fd51b40e6d11876f61bd9e3eaecd57b40441b5bf86b
+378c83c4deaddddb6ea17f6997549eccdcee9a7a48a24466d9c55ed17d7733fb
+f323871fd7bcb8414609729c27575ba9fd429553e3d63ecdf78851b72dabe4eb
+9ac67e961a4dc4cabf70946cfecc144a87dbd9c73259455199412faeeaa3644c
+2022489a3136253e1497bb3458be497afd4c9049c4a615a102829c85e9a4576e
+350e14d837372b4b1663d415cbae6a3a954880114b13a1c214b61ea6594127e5
+742b98849778893631b67469aef59b15919872a7cfd255910f17c53576f03571
+0819d59e2c1a46d0dea64fa7e03a63256a8e65047a1ffe4fe36497d2281b6aa8
+9ce1ec831cce61de63c1da6c8a5672c25e5a5a7bf20a7f933bfdc45cf7bbe921
+42d8625b3aac773bafdcc614425fadc62699de088714b72691a1447506e9a37a
+093a960287e418f2e530c537c08a3e19ec7becfa4ca9320b05f27974d4c22ddf
+61a0ccb6b866d8961f04f9f9b85809bc57d358cd918dfdc9603b32fcbea81d3b
+e193034bc458941b7a413947a1594e46b4ac9bdfe9416c275807a4302694f7b9
+2a226b8c96361a389a9bf92a47b28e501a06d91d6cd54fd2d2707ce52af2c279
+24c6e95948a936b4e0cea35c6bc62edb1f545a28ab762ebc3c2ab4c4c3a670fd
+14cd631fb5fbc26cb51ded32461d69a2b82c586c40cd1841fa3ba780dbad789a
+cca61d286389dae51295141ed27217db047eea3247388390c153c22f949642b8
+5f87f9193421abdcb6d4d3882578f9e70b28ad6238d1fc25877f42e3899ed0eb
+4cd9f13eb898e67a053d41ec485931655c9575370aa8f4ff30e8d3e7a26bf27e
+f32f2ad0bd244272f84af019bcb2bc6332142b4d0bb50fcfe13b9c4e9883f0cd
+c4773686b3ebc08b1dbd2ee12fc3f18a28be0610c4bea2df1543ae37dabd861e
+2c1a65515d8bddd314f8949b5941aca77220073b541c9ee457e24c862781f8b7
+56fa69719cfcea364a0d983989ced39ba2c910fcc25a00601b0340d8d6083f54
+73d54c577012ff8895d006922198cbd4718e32d5fdfc56143818901a670d67e1
+0971b6754390848db25f059764a99801ecb89fd0f38d46c8da9e01011bcd32ef
+c3c445769122d22409c8492bd716782f411aef4b4e0f1234a936e71b9e8a6237
+9b18088464e5e2934c748c64d69b3f2a92d81a1e0ff81a3150013c3764ca0194
+110f7691c3a2b8809900ab262825cef744812fd2f5566894ba1f8d2250d98b89
+a8683f736d4b5a59f51f2e332c8ed0751089e9c51b91b1c54d9b260e312959f0
+c692d4a760932264214ed92c3ded8f45ed1f40dbde9f20fa7a658b6a36e121dd
+cd1bde6e0b8942715b5080fc04a7e6db224faedd8c5778219966177e476de4a5
+a22be484107a0c11d6eaded4ebb020651777add24537d83b6d61b407df0f60cd
+34ee532a9343f5b4231937839a5b330df795293ffb2b27521d142065edeeefc4
+8fc9f90558cae326dc18fa6b759e4d0db1decbe33bf3c1fae3e3ab33c9392d62
+429506196d1050a0e2da447161f2e323103da806d05c40618288fbeff828d053
+d3c6e0b0718f5c9afd1581f0098eb9b97c35aad2cd590e97f4a139733cfcc7dc
+acaa5814d3b63efee7c764c0d0a8ef392e3dc3f02c8e89f64fae8ee4ca05f115
+4314eb98e6a0e032886fdf29692dfed62802e84b48e4705cc0c158eaccf4d7db
+dc47b91aa3c5a49ab600679c13a2177c145759d8bdd337e35c77717a15c55caa
+45339aadbda46e71a8a018d7782b9299a2681c927888408328699da5b8cbc45a
+b2705038b818fc0bdca4d52896057e105c8b859a52676735022208a76abe3f53
+3b012a22daadacfdf29aefd86f67ca840a1473dcd650f03c315f23bc6ed8285a
+9d1796862a88051a4ec70fab8697b54c53260eb12edcb9e533eba6f9c1668f5c
+9709dc308f35b927c974fb1d0c58757086770e1774485e757f1b777db15a6857
+1c084f48b3da6102cc7b38dc89f7a8e33411d7e35434750ebc98dab8439fce99
+37f7b35ebdef734e2b44faf3dc68fd5d36e8fa82face91146ca6bfe1b1710cfa
+60a6178c88ef1137074956c67cf81e0052d7109debf10409d752e3f770933111
+a069a639005c0e043d42f622e96fab8a4a203857a788b32d3b2b5867c7fcc075
+9236e5dea77a574f4cf964d0cc2cc786c6f0068049f20015dda844a3f409b4dd
+bc95f22266529f58e88eb9568ba44cc414535075b383937d1b33135f77c87a92
+9e77f95cdb0d71a1807f1193ef20ecc1c497df8e08732505288fd2ccc73b77dc
+cd88fa4db43b9410af1524b03864642f44ded44466b5caf39429e2c18e634834
+11bda54bfb6787cdf3810a3f2e15141d7dbf09aec5b80675d65aec2c66e80177
+4bd3e428b6d111e50d6f2e6b5d4d9140689598d147473b57a71ab08080d37d2a
+7f2b89ae2f5948eb11f3808542b8af7ca2173bd33eb513c67f28bbeb87627ad0
+09fff3b7ab0c2deb1668fb922649bec4bde78448bf7070e07b101055584d002e
+93826a781916f1e2ac7d272f1a31611835737d54671d9fe6e5b9460b15e39fa9
+af198819f2073d6dae3b3763abc240732d719b0b1c8a37e5f9f76103e770c0fc
+9343289d8cf3cb969bb4053cd377413a610084e68c255a6faed405bed1e4437f
+f93b34eae37559b78cac42761c985e280b92c57c0641c244f3c480bf57fdbf6f
+8749fa7a45d44a40f690d7cbf8b6d9d014a533b8fc62717eac7e069cbe6e3a2d
+62e0472bdd23f0edf4202a643c8fe7dcf37f4401d94abf44c749ccd26c2d8fd7
+1ebf9ecfcf534280e759959c243f3592622d799747efa7d5b75964090e0a3dbe
+dd08b6106631bdfcc890bd5eac60e6fe316e1780b0073eb4a25dd38ea62793c2
+e3e73f9c4ec3d85693dfe038e322c2c383193b789d13c4be802852b7c877cf29
+4b58bf6ab55f9d6669bae41771d926ca28aa6a58c2601f13fd1aa9b0d7b481f6
+2be12d72c3ee24851851bbaca53045b24823e02581988e4432a8ec843866efbe
+5f4511418efec41146309c7a57d665d8ff4d97dd85578dece104a6589ab5de28
+c7d3e653f01ca8492ae98a2dc7381be07abdbb5ca3ef9241ad0e74561f9f5a15
+2ae8cd680fe804aeb1f73eba506da5aab780d3fb24f617957f7835c406c2f017
+1bcedf43715e7f01450f3bdd30c91d91c64e55ea52a8fe5423d54965aebf4574
+b1e9c8e46c78b76fb87df3491440a7bedc74af6a963a2d34486b81daabdf4ff0
+37529951e2bac3dc85ae2aa8a2a0543a0093d2ad7a872369458db38188a346bf
+43d572f9e2bc2f780ec70a5b65d94aad411f888b5592894da1e4486a0d800895
+c08accb0c9b8e0f18c0ded81be332ce12989c95f451c661496fae73ed7f0eced
+8a9a8754bf7778ff0b4cb4ba16c67c9a905e781ddbe16bfa57462ccb6044d891
+83d761ce58941024a403ab718764a1855d0673d39975a286ac8b32f043176f85
+f2efab1f0a0fa4e32ff159be4f53292d5e393262e9f6404b6ed6349ee4ebb62a
+53dfe5dbd2f6b63960cb2bbbfa89d7c66a44b2cf5969ee98854cea3e04b27c19
+f4c79157311318bc975bdcac70548ace6dcce4d71e506e4fcf2eb115309a4444
+f76b6c271017a02953d0ea0a2448aa832e89ab0895bcccc314fb02968cf754ed
+3307f1c59306eb773d7f63f7b853f0ae71093da716b6c0b8ab469c2371c26c85
+193ef171b6c1f48c559146d1989af1700d9d08e3d60ffdf548d5cf7a8712c75d
+24ad4dc445f310b625c4a00e0879b95a2699189cbf88fa7d2c3fd191c963164b
+d363998c215e8121fc3c44a6fd4e428b244a8f2c08b3b058220bd3a83ecc4b12
+24b192d8972430693d9692a045df6f8cc8e04829a1ad2a690cb876c8c72ef8b7
+d04a119430d9882aa7b4994581ea9d846970e80f29c4e4386e95cc74e60d1aa0
+e0eb8d5a9b018dcb3bdb9fca1605efebb39bab3abdeed7585fb989f8149db2b1
+22bff8090404c44bf2da1492a697454804b85f7f282f78ed0df62d59d911107d
+524f6db56e00f897c58015f399bc82d4340a62025c7867c38f7063dc4012a795
+016ee4d90f14c9568d5e324e73f6b7c57ee5f5375907bc403584eb44a4e33c4a
+88435a399d72307c46d232ae24b3ffd31514ad891b7af711263d5778f1412db7
+4b8302b179fa8810340f07fc9b0acc27c06c915f1b45dd1d4d238e00796d937b
+4539a8386a42d6d73f66bea6d8719d057d54cfcaa282c4f06336c05285f53667
+8feeae08253eb094aa8290c40ad00bcb7e1ec20cc99a0ceb80f08f8f356657da
+ba56829d869fde7c4645559f9349d3371efa44f58bc36aa493d9a0624b5ed4fa
+935703ee695de66e687f27e1f88228261eb8c5a42a6c9b30eb45227874f42fa6
+aeb66ec01d1b140af535b1e28d625b1849d2914cba6545727125f739bcd1da1b
+7d1fa4b2b0e6db67c549e0e2e52953cd3a240d3eacbf623043d28b119193923c
+94a15f4773e0c3c974325f608d0ac0015cd450c02118fef94cd39341b8b1144d
+9ed9f3764f3c7796bae60881f6d9d84e76fc18ed060cc82a1beba6123f05ea92
+9a3f3eb82e2ac1eb99e5c6ec65a9cf2a4e0b3c9d55816779edf45c429b49f553
+f0ef1f1c49966a822920230d1a4dd6c05e2c676780ad7a6cd325422d3fb97cfb
+1594f9a31f0730c1c811cd0533671940b3dca7a70fe1c1a79cb4d22194a7eb83
+20323cea558fa1ab48573b4d56fadc9ffc911c8e94680985b0f527be4d218233
+890a06787f21b60b459b8a712eda9797f9dfefea8bdadd077e56ae6035fd5ce1
+a405b543538c790b0bac9c7586d9677feb4518aa590e4b4499902f8afba48386
+908d301b8ed6b87ee1f1ddfe80c37eb0affdc1406617d37613b9a3e82694f7b1
+60b2d43483a09811ea5f50cb559022034d9726c0f9f80b5efbbae8d1dff34e34
+ad6a03f4fe2396b50560fb8a9e9edb2c33f359728f2ff393da760f9a50a04320
+916a583228be053cf17a11246d97e2adf68891b73cf607a1c2705f9066408137
+39c09fe101bbf9d6f0be4fea0b469c47ce6bc2ab1df3c5123f3693ecd1e29ce4
+20f76b77f99d428254f8bc54f0cb95ec02872f83677bdb3aa42e6ac247df1a65
+9c53f63c779b4a2d8a720818033395d47798594ae67a5d84c4505697991830a1
+b244d86dbdcb1b57499332ce0c7c2b69275d043e5be094d835ed3058fef037d1
+2771c26dcbb2da45f3cbf68f219f102b5926200726ae55a214c5267b0ea374de
+41c46376cfb6bc7c005fc5cc803822982133c604261f3a53c7f100fb43638195
+5f4d544d99e3296fec2d65b9f3c40636cbf6f6225e62498320a0148dea171546
+6ec4a4a6626c2d69ade49abc9856a5df5d5854aeec16070750dd7de29b8b6a30
+63aeb9bd772f232e80080f9eec514e8578ff7a51f2d7c083695c50c679130a83
+9708e9e7111c6d8abeb055502ab14e0e4310676140a79bc88a0cd652eaf497df
+e92d43ac0bc724892d7b44926852030ba229527177d42f648f829a77942f1181
+2aa4688d4953c18b231b0e016441022450b6a4efa08a10b550734c2e7794bf06
+567fec60c62d0c159025dec3805ee2ff0e85280442f9cb14b5e6d2cbf2035153
+b49ba7836a372a2c4a4173e2b63fa839a368ac7896cfe0e3c8b131cc4385377c
+b4ce47bb4fc50e84a24af35342cd3356b1299d421ef7d172679816d7bc8ec23e
+e778c76e59cff801a01bacea63f46568ce5fdb81e5fd21f9f43661a9f8815df2
+58e2e9de4b22fb8b2588cdb3037b5725ebdd76a50ebf6afe36eaaa072aabbebd
+0c6adb74468395b34b84567cfa423b19deb4bc67dcfc86345cda9a9f511ee834
+a17ea4349584ece87b4fb9ea12718c600a1122c1096326e06e193da1b32f9945
+3a40f5f8b37245c99e5225c6b75e1bf9af0fb5d0b522fad58eeb2d6dd9d63ec3
+d9dc272a5a80ee7626651f9181ce39fd8717c870dd9e84ce9e942e1713c11c6c
+87acd1b3415c15ac0b34c24dcdfc59acdfa56d5980efbeba111a4e5693c341f8
+613d83b3a0828d48ea00487e55a27ee4be7b93c30bee530ebd2bd0c41e280f60
+eaf6ea444894e24af55e04c7892dc8860d19dbd60f157537e2808d2f561bf869
+143efcd8a7e3e52911c58f2148fc2485e3b37ddb23e933b074638c6459c09e2c
+29244a49f8e7fc86546210e7bd68b966e67333b2b527ae20180f8af858a1e2d9
+8f21bda6e98e02f310ad7960411eb89bdd97e79751ce5cad665ecd7e7995cc6b
+18969d13e902252b163eb346857d6b091c1f8e5f7ac76284be6eec4748e92c68
+4e31a333726ff7d5c1fe6d9867814781ccbdcbd1d270cc2bf014d8d1cce6a8dc
+b184f18651a2cc94975385906e22128aadf419672cc862aafe5110fd9cb4a028
+4f7413e4e244638660a20968a81ad923eaf27bcbb2aae3106ebc5050a69a92d3
+483e3ce8e5ce69f06edfe2c6a1ead8522184f4beff28313a8109731fda4228ff
+e3ca429f4cfd68035d2f73667bf0510135d968fff85cf8c516a09cf60bddecea
+8f889b3e63bbb6ebd2ef7ccb0db00ef5a92794b162c82846f7fcf3e69bc6956c
+5e63776482c6043bf73c986f1f18c6abe32c71f1a4a74f29e9fddada315f5ec7
+e4b38e1225ae92ba3dfd4746ce74c1004a7c2007bc3a95d0bca6eedaacc05078
+ec3a6e32fe37b6b3edc1ba46da5eb8bb981867b64404805dcc91b4b5e0b527b0
+be53e79596a7db573edd7f8f913082e0ba15e74d44ba59f89b0c9d46fb34c2f1
+25ce2d967c903debbdf67040af8bdae68ee0b1e0e89d79e9bec185394ce531ec
+8ff639e1ad00caefc9fab3880fccf55dce1e3937860b25a47aab8fbe4376f854
+f0c983ae950bfa5b53ac225557d28dbef67a89ea7571ddc8a710756833bdb5c4
+30ce1ddcb86623a82cb4e1be4ffe6b777a0a8409813580b75a89f6a8c65e81c0
+168dc486916be4f40cafa4cc6b1e22d553963b2449e00796e459c57b94e34ed9
+5bd4f1eb9798dac04521d031bb8eef565c6b0740dbe50f0b23fb1d799fdc2f61
+3f46ccdd6ff1ae778909d8998598fb796e9921069e4766ed35a4422782f62338
+a03acbc0a8f42c0dbba5917d7fd0bd2481e1835c660ceb5f2334acd57d4427d1
+97a82e44b0b172ee769b69666776597e018d05db7161e3662e42e0a1d5906c96
+607ae2ed6447cf857ce0e58984d8c363a3a6516bf4713c3b23246232b5e512d3
+9d2c3e32c33408c03262cbf5c517772ace92982b59db582ff69a3ff6c0497405
+8dac147089c03c44bff3ac70e685939ed4c1097a0cb6bb9fb399c5c0950f6689
+5dd9f05c7d262061813eca80e87e2c15a266f5be420796d1493ff3547db70f66
+26543b1ae2d6fa431c5623191327118c5eb8b331ccaa0f198f062c51cfbb981a
+dbc442937f63c8a0c179c2c42d572564bb872a2e6dde8e7d680372b157e840f6
+6fef984b009c671bbec94d3446e85579806b11586cdfefd7e2bffce117f15e11
+f9f47bcc9e390595b06f7207f6457cf1f775f781af79b5b825dbbc98e3858056
+aec61f1d9210769e2761f216f01038b24422e67b5c2386182a4e9382d1f345c2
+c6ee6299e4e3a784a4166183be9c2628afcc27c45ca0391975591961cdd718a4
+3c3d1b85b793b5f3a8b6d7590daa27eb6e73ed7726d9719b43aa13e05ea7ade5
+ccef5bf1333eb7880311869e055391412a9f09b676ec67c994783d0be74cca70
+76424bad7f2c80f031c2dcfefb6f2c8e352d2f41e010dff7d37dd6b03cfd207c
+6308d43432d33a1ffc94b74248b5db43f044764a865cf2a6671bb1f99a7e0df9
+5ac614da6c4a8d2f8fafef2fb5358abeb8cf307e0a710a4d3670de75e0f19410
+3062037291a143c03375e6c1fcca2711de12aafacdd5c071adf22c86dc240ce2
+d5182701ade04a8a1c73144012e2b88d4d5621e76a26dd5418f171f8edcd1dee
+3bbe3d6a357b75a217a40e79545113fa2ee15ac0ea2a94b8abdc82e7645596b0
+d8de0966693b23183fc34324b6b7c3d58dcd3b9a6fee2df7b599ec03fddc0aac
+632305776c092c7d45c8e64d9f4c4d9342fa55f9777ebe941ac71abcbaa94a0f
+91ecd9ba159dbaa6ab418c1bdc3580728705e782b6259478a036df71d56cd481
+1e5679ec3fe59432d6491749a9653ea2f22a16cb93a0762e7f2aae8fcfdfb43c
+fee053fd488746b2af285ac480e7830a0707adc87161595a5cc5a81feeaae5e7
+64a73af7c1d7e8c1dcca93374c6dcd57d0d31c479b3e89ff4135e66352290dac
+86d663c53d9b2f4479662c8a335b00ff194e0a29931552a641510bb9b08dc991
+78a63331f41b7a813ac7640ba77d7a82d9f9823f75c01175d49d2d357b2be420
+b8137c3821f260f3dac79818a7d0b4d86d257af7bec5fed35f157024f89795fb
+6723d13bdc0f41a1d16cb2a232d457376acc0e332fbe5b36344fbdb22ca8d7e5
+660a86f3902f04ad8e1685998888baa9b4e0a2c064d2b63cbbde79914f3d8f52
+059ba32a5f541fed91ab69b69ad53d045755270345d3ce9916488f75bc83e7ce
+041b96eded251aaf6abc9b68b16da135f86c74556394cf9042b54cb38784c548
+6491c83043598564ca6c64ac7e11fa09a3c3887fe20d9314c21ad36136e6bc05
+561c0c1e9be70079846939b84eafbb0030f4a9b0cb303cbcfcf16ddbf44cf977
+7f8cfd027bad28fb461b29ec10abf2655341f0dd3950a508026ad4031f3b139c
+1e16220521c36ed2f7a0e11780dd8982747af44a62bf13bba9eec406077238b6
+a03acfc88709d33589cbf6341eb2d1f3b096e515efbca9b6c1328d97cd840dec
+e341741bac9d0ac07f5608652af7caec833adff0e09eb728588a6da3e3fa8038
+b8c1f4105a5fd50dabc7609007ed1ae39a0b75f11772a452ca7d0bf856e0c9f4
+fa0e98d21df2f47924528b6edaf372b937d3fc9e418b6e5c18058f93d5bb0e28
+a2b59dfb5b5924cb20ba6aa4a5e5a1239e864c9de629aa43543ac2eacf89f6fc
+cac316215a3295ff05f42bc41cd8504f33326a3f1596c63f15272c2484386544
+9ce9e479d5df3214c91b9ee0fa94da29036912a6b1f2b8e240a9123a6854d84a
+08f647bad14829aeb65a3a6fc8b7cc53723b992db9a94d8057c33c369b401ad7
+1ddbf2078bd8ab303b399002600d5eea4dc469535562e1e6e50366728441fd2d
+35cd771495ffa169ed4cdac01f988e7ce5c7d28f9dd0093bb95aa97ffb30bc6e
+dbe2f71fd3e70aa1e2de7cdbe96181e92326d26a7558b1c1fd0edb58c63c51f8
+b1d741513d98e06d3769d64e1a11fae44088583ee7584e867b9492e66e9c7114
+3e165b672204d444aad59893d4212860f074d1c6079a261f6b3e7383c57ef17a
+bb600552eb518698dff54a48ef83ac82ad43534c07dce6e9e32078b5d60240f8
+b10d07d496f7b7de76fda6a20db434f88428dda2c5320a38836ab84de94645f1
+c511080a50d8254c0cfb67bc3ac2101c401541a34959107898bfacc611bb2962
+f4b3d1ba8006c28103c2a55286bcdba75a3537f81e7cd0d02a9a3f574cb6389f
+332983f4cce98c6d53089588532402a4b8a1d1427d550c7015e5ef584272160f
+a044a92664299ddfc990ab0c885666c09b6f2d6240db9f7dd3f1a53a6e312a80
+963080a78e62169cfc1a1a7a329ea2998568d9be427b7c69a504e56a7920246b
+1d12fc87e7f797798b01aa76fb34594749fca31041373c2a3e1bdf066c839925
+fd89b479bc991372dedcd1c4588f1edc25ba46b7f48c93d13ee470727c9f9f27
+dc942e55051d4f73af8ff183bcd232980fa907def8f9192c3397552070d0792a
+0e13f3dce6f14c904992940dc66abcf4de5d31be037a8d913fbf6f0a71c8a9b3
+ae5962d0e70397aef6d42d2959bf2f4058f4aa2226afdaf9a24f326e37d3757c
+56d590dfc41a5a2313ef3a460932e71c16229a152a36bfda828d9d68a411d64b
+24e4854cf677d412d22db866c7f70ce1514684a9a09203572a0b5088ce3d2652
+b014a181389279bc373f982b2db5d6cd9e6f17af6dc1ffa3b05787c50ff2b9c5
+21509b9ff68384217c6c8027b93478e31083e71b91ac4e8de22352ac08aff22f
+e7821600af0d1a269bc56a658bf7650187545a1ff2b85d73c969199277a76dbc
+5f1246a6a7066720b62310321411be865fabd80cba515d52a7767fd70d3bd0c3
+7512150200b4d5e8d92548bd3f392c1f16a55ae1a110fa2bc6ebacc5276e6983
+7dedca629ff9d85218522e59566991ac22a584af50b6155873012e623e64c0dd
+9b029546c73368bd1a91a1365a3082b715738d2d470b4c9cc0798a9d09756157
+74b8b5fe0fd88864260e3af1b2cb01f3cb6123834a468fcf1de1f1ecf945b6fb
+6e2eac258b0e67cbae19250eb2ec927bacde291d4424d8a5537bafdc2bea300e
+3fb2eb614a952717fa35749dc90a683359dc426d901b4f407710ca529f6d100a
+143cc3264586dcf7aeb6ae1e0a38ce7efa0bcc2eaba937bc1d924c61fc4e1214
+f421cab4a51d223cb75fb13258fe0cf3001760df8081e5d8796e329b2219c773
+4ba75cd625e873f19d07dca484886174c089c1a50482edce0b33e297980eca19
+7e80f95612562f655958510bee41fc43eb6f3a6dcfac20af5492449994016664
+9f17f649844b0061a2c7bdfdc3d2d8bbc0decf248382e29b34ec934c3be95aa4
+4d7ad210d2f47c986bb74b162a8b646246e7399dbf42e744dda2aa4814f184be
+9e5b40762dc25102e036833f22f02ac701bcd27adfd7233ecf8178c4406372a9
+44b2afe64739bec4f3fb782643245a7c76a541997da6ab9273d73b506775db1c
+d1009aab2289e1bec86bbb3079e20b0814a0dc046c557d8d5d9a04fcd8deb4ac
+76bc3daaeaa95012770da4d96120e153595c2cfde91499ec1d3371e4f662cd2b
+4b3becdaf3d3cef3c93e786bead46fc8b7dcafddf067e43571cfac08e2c2a94b
+33c824176159bd0612cbad0ab9c87e86ecc7e6fce0e09ac6b42aa81cf1d216ca
+5fa78b5e5df6ea279a8466822c15e9c570c782dbedfd2a2b472dd555551d7e06
+6c582844169e4ff992f0632f3a5d84d0ad97e5e0c87471714f87fd02f7470af0
+f64820292af14a29e600293cd53e9a0d3c2e4a10386913a08d3aeb81830745be
+be651d33c4dc3f9a362b295a9feeaed0a2691659f86846dbf96fa73d14c69fb6
+2934e3e881ed6ac09db98bc4d485ce9b190d1aab30e1bb2c6fac242505c68e89
+d796cafde8195ca0922fe315295e9644d4aed6b4f2a0219784b3933b7814ef25
+5e37a309edb100a6a9484778f195f1b89620e7a46a719953fbf11ea504575842
+9deed1c4d9d9eeffc4988e1db72107ae09873586256168e00f74efc803245f90
+7191058b70b823f06a5728d4bb2cc329b16b1622efe45898da9c3eeb82a0faaa
+ceb67820fc60ed2f8673e089ede97024fd7e7a4c34fe53882f09aaa8fa14d5a8
+ae172b4a20774dba7e98bcd626b6f45ff15742fbb2586c581a44ec7197019e2a
+2a53038e15af2acd5e33fea4f76617d0bdfa83fb7a8ce070abbf357a394c166b
+7991ccfb463e4178d4d4e92629e993639b9f2e80d90d0e040e12c60b563426cf
+0c5444224aee42c44f7dedc06aa93cee86fe7407f2f8cb11d1ecd71852eabc6c
+bf25ce66d4776da60f4daeebfcafa240620e9b261b922ffb670d53007ad659ad
+f25e33c2635201b15afa6ccd81b225d9d59ef78e6f809d03f39b6b968deaf8e3
+b52bcc1c9dc6ff4de50d039d98d0b36773cc230153fa438f8fb4de8fe1f0a3d9
+548ef3078abac9aa74bb499fb06fecc78db02bfe48dc441510a393f6805613f5
+a762368142cc9b1de1163bee652ff1427f0b81836d42bc0d50e70d29846115f7
+3299bec648eaa5afa960b13740716767c92e4217eeabf4f42f7773cf08eede70
+c092e17c4be3e18a0a18bed80c2538f53dbc7410e9cb346df72070dceea70d68
+fcf91f821da4fac781a50ace3f2fa9a4d1f843e07d24b906ac9eee025ba98f5c
+29de05050c88a0c5e645bb6abb68ac9b6e1b376e32d0e6e312bf8115479bdc62
+c34a9d7dbd3b7232e96e326352c6957e316c4b68befaaec0d8c72835a4833ee4
+e745985eacb33c712ac6639f86959c11d6d22c562de124dc83613e86e9359c07
+a1517830eadd1286aa48f699258e9d85678cfeb425680f630c5090d9528ee401
+26b02bac723e01b5613ef12dc9dc22f5c3061f5c39a3948bbb46c20fe86f48c2
+408bfc9d8b8d6bbe00b14bb86070df73dd49d24db3e54a11c87aae6741ea6419
+bd9a370f71f23a42d2ab67d8a7b01967416dfec9220c40ec6203e0739034e29d
+ea00e150ad58da630bb057036216c811a26fd1dc945babf9af676f1bc6f67a7a
+f4ff3f67e34677c5c5012de714c8c0fb6ad2cebbccc73f82b0cc4b52eb992756
+51dbf27622056839bb491674f7632c55a7ef67b249e3d695a883d6fc0cdde6b3
+79cdecea565d186d798bbf65b2a420896dd075df38bd54236a93e968b5d9fd75
+cb178a7da86634231332d0382a0e58c800f453e1fc1a83e19d17115037466395
+71987f6a57092bb46e0ae10110f84d1d959c567b67b9603d22b4e5cacdc827e1
+85e5559fed3b702e3f79577c4b4b3ad26fc8e14a326a94e776f4ada16ad544e9
+0b3a19012f9d6e0d3583f2e814648e86c01561fed1bddcf9b3cde825d0a7eec9
+bc7aeb705c5d1476ecb00ae4b8b8a021bf03aed4e7c9c5c6753f04fd87bb1322
+350e6db3ed8da517dd938dec6befce20a854567d0fcf1a339149369235889a1d
+70f31bad6c47c4e52f6b2e785a0eb141359d76a05a37bd1c566e36d262780140
+65a4fd366723c4841bec798e4ce07f542183a01ef9d7f8f1979539e84ec241d6
+6aa2339275f588352fbe3b05bbae7b7bef3485b05641ebb8c8e3a619c48f93a6
+dd301f0b108ae77b36302af00e580cae0174eb6d30813e9ffad6058e7e850338
+1e53adb9f1f03d8524c4aad05c98ab3862d6d7cbbdcd7d2d7bd921a4069392b0
+bc8a531ae000b67c7e8bebd6b94c2c79f5303c53b045a47be451a3f10afafc76
+a28360374d5fda8895b79d1f94555c30966e5ad8a5f58d54b2dc4c4c39e47b1e
+b3b2a57c8f7aed2ae776fdf4f839cb77f1b2be2d23aa4cb8b8d205152fe3f9b0
+d9002b58fdda551caac65569880995267003437b20cd3a72c3ab3166e593f3e2
+c5399da8ee17843e224b01cbe2c1548d1054a8c21304085b93b95caed462679a
+fa228b98a458e5f08435bcc981e58e4cdc310f57c85478e56b186a65cb8ed185
+4e8a1b30ac414152cabcf2773a2a6c296f89fda5f0cb3634288291022eb1e608
+d7d0a8a46f84aaf58929f8dd7097ca83443ce71f56455092d27b22816379eb04
+4930c56449c419e37978806f3b42e97e1279e9e897a3a709dacdd5d87d0d774b
+e14672a4ef12ba4b49915c76d4d574d44f3e94a20d911db728d973356e91604b
+45e6eb25bc0dd3d285311bd2b01a22559f1e14387c996d531898d5fafceba4ba
+0c6b87aaf5f8e82afc71c04d634c782ae7288f397e50ca0cace18eaa1c17cea0
+cf388e326c7ea4ba925331fefbda0606aec1defbee547666002d478a69538c99
+28106f509a53bdc2167903c9e1d54c41b3696e25106a6062c10d945602e341c9
+b50f1df6630a886c8c6f27383f29817e1b2aa326cc85e43afb136fa58ec24dd7
+03c43b4694b403a1471053cff14fbf9b34567b4eaa0b96adea3a5d428bdf8b9f
+c0159e4acb57deac8d87597b4ee450e93f682bca0712f2136f8216945ea482ec
+8ca99a112e447aab44850a576b69a254a9cfaf86d7cd1c130dc6c1181515b79a
+d703f0903faea4c8e76375951b03b924c1079df5ac55802b53cb34113ffdc513
+8201c1fb93767ede80e76cfbea2f07ac0f95f250697ff0267c206bb7048a862b
+7715741877e4c27b7a88bae1414be335d97ddc235deb4b95a5c51089f50996d6
+a812c4e9cefbcbdfe18388e75df88d04df66eb53cf3e7eac5fea15ff041ac3c8
+834356278f31517ef1d30d53e0dbadab04bd70f2ac190c35e7273d45a0259363
+40dc1e0b0017a7ef36c4c8669f29f21591e43541f149c4a3b1412a5057c5348d
+33b8aee4f452db49b54d4faac7ec9c4eff03bc3c783d901a896fb7533b51097b
+81783a016b20dacb75f52bf78dde107172803db8d3a24300aaff61863ac87c27
+f62e44bafd260a687a8cb47daddc2bdb6570669fd3b63b9609c57caad4fc29b2
+01082b752a679423dde1a8c86836c44dba5d1e3ab96676707df07bb47f730363
+cf78ceff36ff2ca976c969cb74abb3f72f693396518e9f1ec0fab6b53fa334ec
+741b043045e31ace728698c097d458dc733fa74a63d61f53fd4f45b9ac1880d0
+ade4bad2e774af7ee6549e2824168f0aadebcc069efea4a90d4f2d327a242042
+9557284971dc1f1ecef59b92550929dcc42a3168d7f5df5d18e53837ca6bd01e
+003a02c3700842fac98946f6d516ef503be66d4897cf2f138286ee3aa79f771b
+4af8541855c21d583f747c294bf1d1016d3e4313b5df8fd2c3172820bdf19792
+428072fae237ea94b4e28daf257d41cbcbbf9c56dc2d964ad26aab3df4f912db
+71546ec1fb9dbaef67a4a8771f859e0e78a9f6972724df27f79ded61b92ff3df
+4de0f497c50f569891279b968d0567d17c99cacf6c07da376b986299423adc90
+84d4f8e93dead2970857b2c562244f898a59995b5ba464087cb3e2318eff4975
+5079a9ec897cc4dd5bed02e820619bc1edcb56385e0fde4c2ba27fb51a3e80be
+9a2116bf27a5a42e2a70487177d1ab878bfd89347513c54eb92abbc87d50ce70
+3e2d237187cc8abeda00f686bca95cfb404053b6261d4795e558f3886c523282
+06adbea3cb36212dd839d26755ac16632a723754890ac8d9a6599d6f2663f2c9
+b920d92da7c773aecbe0cc7b950e619ba5eb1abc667cd354d417995d6eee1b0e
+e43f5bc9061b897e0779b023eaf9aed24ea5cec2082050c92e0f30e23ba713f6
+2fe14d9957dace391f05e8370b9551423dd76bb50dc218d7fddbbf235753e1fd
+f35e73d4b81e2f8bce061451a2c53e95015a7be71df27847b628bf8d3b682b4f
+0ea381f5635fdd2b8e49e534a9c4d50e2e3991a7d5d418c9922774246f8e9e62
+9096c4c4bbbdb0b7fa78877883fe2f47f4570b4188d540163bfbd5d10975a80e
+c4a21c1ecd43e211ecfbcdc391869a27b06a47bc1540a708a4e58c2fce67bdde
+94fbf0310a60fc2baab83cd16b1c0c3b05a6fe84367b3e46f93a409633ce202b
+fef120b0c791a48d376e3707689a186c73135f034ec6bc28870ff0f9ca5c2dab
+2412d8292fdca20113abfff496ca34eb7829f43d6e1b51eb79ec9d331d6aad28
+2046c1eac346953e6db62225770dda2d02150601fc66f53f94e9d58b4d3499ff
+22494b4ce45de3739f3ed286e421febeaf9e125168babd9b705b51ea733e31f1
+ce295f4d144d86a26bc8aabcd042a4953ac9d61b90e2d4feabab18648ba16ea3
+ae99f53b971f48ce1985c0a02703f1c609b7cc77d551fca1a6dbae9ad7d2dc7b
+a61a5e6f8b729bd7623d9c73d33fe9ae249a3b776634e10dd49222cbc4705af6
+4e262a75bf5e60b249acf6d40358f5ead7fc3096342ea25de703909be907e364
+0a1315a1589df358b7eadf394d4b355571809b1e860d96f4d1386b9d8276b85d
+6ff051e2883e2fb05c809d189e66bce5465fbe830282a23f1bdf0fe3bbf77780
+8b2c93fe7b69d6156f6ec93cae83bbe9c54638bba8d6df08da9bafcb3d080d3c
+775c0347a472a23da6a22ca855ace9385893fa7d851cace8768fda1b2716e6cb
+fe875530ed94c49e8c67922ee003911c08f2165d97d8a18f62d040e64b35b544
+d740bd40d8715005dd5469d106a4bd8040fa53cef750287560433965c6656508
+9cdc19875262c227b8ca384cd97047d279224e13bc8cb280f18e163d1aceaadf
+d4986a6db5a6bdad04d188a927b6b7a8c21bd67bdd4210eca16dfdef107c8228
+0d722d8a3bb5102c0dceba5e75a5d8834e8f253bc5b9938d5099873e1848b0d2
+76040a020d677b2b5b4f7e029df54650b0d1408bb81824f003232c5e8fa4cc52
+8da2f15b4cfa2fd8770e0ef75a7a82d92fe20af3d5132d00c136a8740d88b7bd
+5004d69fc459d373672b2b72f3ca1fbe58a3c2dbe5708b57bb3c2489f28a9e3a
+81d61c0a661b8b039b1b7677438906399a6b8fabedf9b25dc024a038574d7719
+cc4686a43725c624d07a0760aac63f477123f7f4af8caf41874046fb963eaf4d
+eb027b0651dfe68f224a5631eec6342258f71bae4053cb491c6c37bd9c4c0915
+2a643ca05150b84e2342205415be8556ce7b2f22ebceea680873da60f1a1efe6
+97e3e9e43c1756a243d3a9757f1219ae739579d393d46d747bf36bbea11dc549
+df94f5b12386c770d6927ca29b80eb728d68b7c20438ba3932429e5e7cb190e0
+3213cfe74fe392ba93dedd3b1779fc44c85d9ad678d92bc25827807b607aacc2
+414b2d0d59147ab8e0be0d32f76b95b27fddb2ff275c870a5c441612faa47330
+5ff3ea6caa6cfd284c2349ac9e5d185beb1eedbbe07b8c2a3ad4766142c02434
+042568172b0d3781bfe703b149eaa84fd09e8d488b99aa38eb577f7b21c740e4
+6088dbc4e17d9333a3e387531f97b4ca1ea179952eb33878bcaa465333e1de7c
+83616e413ee6e4c5bcc1fde1987a0cc78d279468b9b1511081f6e3bc802e8d6d
+78b19dc59990579eddcfaad310e0f360632da25ed5f6f16d560ae7d0175e7b2b
+01c3dbd8c98f55f494bfc52b49f3227ce12dc4f458de34b2ad36b3b00168ac80
+85e35caa9ffb7ff60098e5027e32be43790a0dbdeeb8223b692aedb3d6c6130b
+b6089c764fa7897b9a36207c9fbf50ac0c481a37096a57da8beadd05bd16deeb
+d93fd56fe54a6097c61c1d647f2f52553609c3f936831f4f981862526139cb29
+48a02146d1815d3d078bc3dbd7c32f2167a70edc28b30025ef761cd43bcecf7a
+a8ea6619cecdeb83147467c3970c772db2989301cc8337350be1f55c8566d14d
+8d963234133bc7ad37397be014950d325d2b67d70232a50fc6a4a064dab0a970
+e7d4e1949f7a0e50ea970934a621330fd477107843bcc7713dd08075127c19d1
+5497f2a13f917c3b7a8917a5e7be2009dba70ea513b405b8926631562a0a8ffb
+4bee56250fd7c147f5b515a8f1032564b6baf2569e913e7660098d72eaf586a0
+078af231d4595d67cc0c73faaf4463fe6ca8f94a8aa8f9132106da58d45bbfb8
+3e574f76201cd5ef05d47daa02001a89452b5a8bcef2cdb714b0bf5c36c76557
+7e2eb7aa71d085de5a4f5fefd4b22e25854c21a8e9e8460a2686da7f4dd2926d
+3533f3fd3ef0d7cf276fabccf0c1dd01eaf32d788aa6e01d810713bf2d236a83
+4c67f4c69fa9281451c98307d7e69afafe851f8502421c4276061bd6c3790029
+5f72f1ac5d78564cc221302cd97889400b3238c65a2cb820bf23006208c65d23
+9b428ba1a5f9696665bfdf9057d645615993d7f572c6646d710482b9ed62f0cd
+4dd6515b31ddc3a154beac3d89c81c8628daf456ac62abbe9de7a5b6062cf650
+4060c23ae920cc4967798b6eefbc752e3baf2492d8327d295f710814eb11b167
+629c496c555117f76309ea311c03ed7fc7f248f7d1492587d0cd5c5ae5e037a9
+57e68d48f4b8128c31dd014ad5230ea69ce33ba6c0c4e6aa1e1a7b3eb2245445
+979a59d7796a059f54c3a1332e06906cd565d21c0acef799d9d8aadfa6f5aa2a
+309b296c1bd84c1fe08bae310f5f22e60c9ec7096490bbee14ca80cafee768f5
+7ca0d7a51894b2d52ffe875b3762d20f0a1500adda6a401dfc1adace27677890
+ec2794c07529ee08ff575ebcce496c16e6b36f9a9beb924f95c4566ac092be59
+1694ca2a76062112ac62b1360cccda1c5020fcb4f400de7e87b26391d4177341
+c91c4633b878ff121715bdf6eaef905841cb096f66547d03aea8d8c66dc7b603
+9b354bf14f3bdb74184393f00dd5840e23b08653d045f18155098fcaefaaf8b6
+2c548f4a756994f8427598ac8f68d7304ee37107738c74c0b1c911396c7de26a
+3f33dc50809c971c394d2846208999bdb9bbe252ce67cd7df343a4f0e108623a
+837a18e3f19a9689b823ede76f782b46d3635d60e4fb66d7a672b973d3d14262
+dc6e05092fc79544b2d82b03a089e8314c4ad8a64498f1bc699f58983c1c5087
+98aa563860b833bcc7c490b0eb852d2ec7ee9d9a02c91ae1e94a4f5a94688492
+05bc1df3e49c6368463303c81ebd9ec0742a8374ddbc39da1ba8e51d1e074e05
+1b3311fab5daab09de62bc9580ba9dd337f8c72fe557ae91caf5bb3c1932b544
+04eb6a55d79892f70331e72acb7093b38e7327c336fb6b4a6e6cfaf17ea978f0
+04b58b0c8a04b0c8806741abd1684835fd5a1a7c0345d5a2387f22ddd0b93269
+8559f7a4b1170e98ca957fc9c903cffd18fe9b92b4b32c508e53efb68883e042
+7f8b646afc1d1c597942e79111d571be9cec5221383f5f6b03193e0c8eeedd50
+261b9f57e594b795c3c7f493296e9a8ba1f4506f868b8502649d7784503fd253
+c5e99f3041f2b4cd9db715728f1e692ae507e234e0e8a3a8f6c732413b90176d
+48e910abc918ccfd30a33c872f9efe34a8944f692f5f770c027da9bad3f65866
+2abeeb55a57dc13c71bfb71cef2886453b43d9961172723556639642ae281980
+50fb7ee850651738a68be6420eefae78d1a20817ed96dff413656bb4a968a70c
+b9fdd1e9bdb2c0e1dd5d32ea1b50cc487de1f55407eadbf2b3fb9c03b64c15c9
+5aeb67cbb5ecba0889ea4708141e82d2ace7a841896c77ab00dfdbccfafa1f64
+61abe7803872d15961c1c31a6b1ad50f120d555471579bf02399f815988958ff
+97d0132e6429769ecfe39b270a07ffd1469246229cbc1450251e71b1beb00886
+35399527233576ead74ef01d7bd7bad02d951e93b2e71dd1a82c523abba753e0
+95af0c249ad1f5d59f5c95da00d4eafeede3a093e4cb6e108469c6cc80fdb8b4
+5116c29b0a9a02a48f6f77115649b583ba4474900f69e9472ff178e41a33842e
+78b476a924c14814191a61620e398ee3b48ebaff1b3635edb48ad16d497b3362
+ca9fe38284e1261344b34dfa8d46e43324b3d3b21f31146ef95c2c7c08029e20
+776474fa622d5f145ee66aef74e4d42b40e981fd5ca36c6697c2655b2183c655
+9901a8f632beaee841515da5956eb82b341d4683c37050ce998df19480662d7e
+2d09b430b0d79441295b2d599bcb59a3ccbbf3333745e509903400c74b30dfc7
+7da9616b21b268b07f65a4a32d878feca9852a78d43e341e893238a2b2c0cf1e
+3dfd75592bb9f15c4e8653dd6e8aee0b772961a9e54696a266506ae7146947f0
+ea378bd1ed22b391795bbe5737994bf9756ca3019f42a032435f9429eedf2d41
+5ff27dfa5aac62a418667843ad3995684018c1b1de4ea68848e3762818e5b80e
+0d5bbd9dd866753537ed7ba8ec145d49df30fc8d293d69e3083209754a4b425b
+99e39da47bf27152049231f60dc3a849ad6a21b86771e945877cb2ecf7451261
+b5943f02252618cc734cd0ddcdb312ab729f35826c89fa9420fb1ad70647a74b
+70474ddd8ae200fca9317e1c50f9cb68de8379399bfa7424e66311c9d7574746
+e19d44413a0c6149f28a340062946288f9f78b1c2b6f5233885e6a19cacb78f2
+31534202b24aa659211adc6420b95b17b20226e74e9ebe01948582b8ea2d3ab1
+78d5b4ed4c778041fc17d11157d5262c90571ff706099c5b06130412490833b9
+c5267a7163ff7e1d45c89050d6497d097826f5b26be150ddf1ee486d232e687e
+453116f0b6b7c93064207213540cbbd88d549b74dbea59176bb6d7f7712dd598
+5256a9273aa32d1701a6b2faa0da6d21356be95968dfc03221b0ac65b2b22287
+90c3cb64ab2a65909824dbee9b5399b0c651bf5efd603621b94feae2fde41963
+c266e40755ca4266496ece2fe93c4397d907cf8d413a230701e7045cb9da1d17
+f1a00e00ea64b45dbca755a454a9fb232a8b4b80d6032650231b9fde78ad9664
+71232d9076520306940852e529fc46d83fee4d6fea15056cf42fafba1e8482db
+6870f41187c0906484e8996738b52a946aa34dae017d19faa08c86845a85a543
+20dad4f4002f6625b84a1b4b80467d102c678823b676df24c9ca802d568ccccd
+615858fe6517ae56d631eba96da6b71e0e0e7b71ad9a9483c61574e23c47bfc9
+a0394b4bd85fe41c3a8bb60ee8c6f788017ea90ede0a70cf82f14056b24a6c3f
+58849d613f08ec3ee21d990dc4fe661dcf4b3afc8ed34ec2ddd279cfc2cee1b7
+f1929ecb72d10752ea012342923450110ec1868292babcf755d0b393768de27f
+702fb68daf1154e8855a89bc32bc378580ad405a05cd12b35826a9d88a3cfb65
+987f364b89c30340a2b1e25ca29cec4988afd940c1321d12cbd50eb411a29a65
+657b2e7fe642bd8c90cce146b9d216b7f87aa135ffb9ccf0fc8da1146bb6f03c
+1cea3f7d532fb572bae2db81b7f15c14de973f6bacaadde5cd0333e71a9a539a
+8356038f876ece85f7e6b2d057ee8d48626483ba054f8a662991cd52fc317738
+2d029b04dc44da269de8be9b025f7929a34ccb6efeef271232894ba8c837698a
+fe6cefeb4c1ca0180215c2adb029986953af902ba21c7531850ca7ef798ce22b
+3496988dd07347e36f26cd55db9caaa73c5ac260256432c9e0f6e3bdcebd81a6
+59e822ba4188ef395f10c448c4c09270daa19a856c34f5cf0f91a74ea962b4a9
+5459408633966d6730b85c54e2de6ae317409287414079072d9422ff38db6c93
+d37994e7ef1a0e65c88019e729528b51ad23bffad9ec9954de8678f03a307c43
+0c498a7c16b3b348947e25a896bdab2168bf3119b296a327c2378904045e322b
+9cb321d23d74f470fafc828404612288252f1701df6c743de571e2e876cc2672
+23f2aff00d26f1155d6d41a3b08322cf8eab002fe47f5265bfb67a405d4ed77e
+89cbb4a61b54e796b8f3cdf388d5418b96615c1312ab7d4e7deb1aba8984e3a6
+76dccad1bb953569931f8c26f7af460c5689412648cdb7803f1c93dd452a8ec1
+6c8e3e6190d558919b855b7b5db34bc42b992ea155615cab202009a96fe13363
+4f649e77e4268cf651a74b8c8c645aae6dff800ad17b194c0b883867151c6230
+dddfaf596c2639a46e6746fc3ccd1593d432063ba27bf2476bb3ee610fd7d92c
+c09b70c657a8255cbe2d8ac281892280d5f19fc27b5f0e912eed9c38007e748b
+079ce05131c3956dd078c0846963268fb4dfb5eda5ee8894a89920088f721d1e
+b725870aa43e92c565d10d5d796cc04a114b5c32e90c9c7f5a9b5d03d894f96b
+9ef0b123b9f90d1692be30515757e54c2f9e073c16c04587363c1e9bd4f834df
+4134a10fffc0b5960a83c4f75979a14bbbbd52d4d4b5eb75901fd20a36613826
+0d39c035f3144381fb6d4eaa99953736614d1a46fc05f800ee66ca63b1c6ef10
+673e4faec2288afbf5cae62b7f6ab8c80e96c8cbc7b666ebe78e68d184d85c4c
+d8b92adb253aef17ada1dcb3eb1895047b93163050a6f3c1a69a7935a12244ba
+cc164a780f44dba4c432f913f5b649529721b123f4d6816ac8f1029bba12f442
+935e1b03a434297d33e03357e0a4a56a10f0fc2c16dcd060cf858b58f3cdb5b3
+4f571ca4ef8a7a22bf7b54c35b38aeaa6548be8a70027994834ba2d55b4893a3
+c70d9d44cdf6cb9d1d6b92af027a0fa802f207d34c10f8c4aa3a2cb64f8817b5
+78408cd8f98466905ec58a8df6cf060d6ba17235dc6591e2ebf061f5a7f1bf9a
+7690df303dffc583b98e8765977c887e776814899b45feb0f0cac642912d4b81
+e51c5ae48c0cdf9024cb96f421d1b7ae45b5ce9d41d9d35c2b00416c8b9c032f
+b27680c1f8c1e55d9973a698180a786b92fe97c1b0a9017e37b51cab3f388b2f
+42329ec38e452fceaef2152d36471573caa830a5c80eb0ea73c1c7161f7b21b4
+cb7d3e55f29e36dfaa09c8b3592fd8afbb64adde44d8665143ebf1f3afa1a5eb
+a16860b8e8f52eda1a6793be62ee8a682a1dd30b6482a3188b5e6b8dcad4c688
+98e39892691434d85c42922a035c752827474e01cfa507d0c434f746993ed746
+3d32f530ea324bbe0ff4a38581296788fa2dde0db5dec9666d8af2deb947f200
+b617a83e84114e684442fe3360e0a66880f3a87f1030dead88eba805cdceb8fa
+4b110dcd71f720d93de7c0e8122a09081b9cd1152dbba5f0411728ed87028a21
+9cc36c72bccd73e3cd517b9a7210cf1f3c719e7e23511b0f0c272de69951e0cc
+c264387948b95b3fd7d7d849947117e1f14bec10d2d718374eb09734709ca60e
+c7037c7c3c5d1b9797b9e4c539733fb69fe61cc569f76b269780fe9abd497f54
+4d728bea738a4ec3e17aae0ffc36c2bce74cd24bb8a2758b9c8554d92a64413b
+ad5f35aafdb387b28f1ca329a32d8d99db75121207109e6fba304fc86e027bab
+b631f93526be28022f06af5126cf0a69c7de8aec8043aff5a47f6397097b8dd8
+8538bb33987fbdd064558e7634612b8b2c22390f35b8096fb0674fd62084d7fd
+15d98b22fab5dd19f9d222a2c4d2573badb76db8383c74b4996cb068ad4856b3
+a01a8f42bda0501aa7c317c275e050a133df090b6274f9beb1af87f8b48f7b03
+64bb4e937369972839448dc459dbb463e8df91e0fc7fb909f5a423ea9aeb2f23
+0d24b27dbe955c4b6c3a9d22603b42c6c5ebcfd67c74f915a47ed1b762e10738
+b7f181feb8e6a3b2b764336966c6acff5a2e2fe237a4dcd64b7dc7b7dd33669d
+8dea06bc3c1ad351e2305335036b8f109fece9e30cde9ae2f472907780dc1667
+e34cc399da1ae24fb728529cf610c6592df901ec9a7a1d0c52ef3840a99861da
+b28412b3751b07206391aa61c33ca87d4164876b92bd6d27de24d6abe4af0119
+aed63c73e43a696bebc21252a6c39d5ecdf45042d230a583e177b1e41167ff25
+1e633941dd1808f8a4436e663197fd2442e085c596d3fc12b654f94f05453599
+e7957e70a965c87132bdebab6eb857a450c23d36538da886be9785a15ed6a2d1
+64dcce81504e271f58101d36257f8cb1d146c5dca3d00963d245634ab074b95d
+d490b8a999b83ad4e4fba3573858fda34ef4735ece350868c4ab772298e22b6e
+d78e9faa6af70f400df8c6777d27a205fc1b40b26e9ebd72e37a18948d447dd6
+021aefcbc2ab7939928daa51eb03d66172d0d4df7e01b032de40c3ff156e46be
+28fad368241ae811d6914a3cc4ecb640531adc026496999ffa429093a40615db
+81b63de704aeb0ae6363559a0b70d55940bfa22415edb9239b27bc5ef0e4fdf6
+3a91764ca5dbc491df84e07875da4814bcae9b715d3e4dccc55cd83fcfd63696
+c30648600c986bdf4b1f00c1334b1403670a56d0cc8bae15187e1f4dc8082eb6
+7d85d9413fa5bd3215c6af814cdb8c1b7dc579c6f8f2bb11836a5760021c0fe5
+e715080339b24aa017ce4aae8bafa417f889c5d27a285004429383bf4cb8f65a
+6bb72bcb975d903020bc27752c7e40a2d00ea67609a04b125da0ae443cba553e
+d3d7bed8408f3cf9d22176f96382fe832a85fcdc995663c777ba80b9750000a7
+921d3fb174ffc454547a90a74289c2a5b49029b3e7798f5587e9c646ac23f2b9
+254b151cfbab3472eff67ded7ce08db90c0a9a858b3d58e7b185212a0c45c1d2
+ba5d5527e23860ff4a9c985f74efa6c70a749206addbd93278965d3b6ade68ca
+8e2e9ad4974a836fb78836fb95e4fb7dd7d6c2b5423334aed2644b09259367ab
+8a06c57a99803b547e3becb7ccdbba446a7061d624a8ed0ab1f86740351ffa61
+2f55ad009fe1486418119f3b9932014cc92ed1f13ec433c62c3a004f9fa207a8
+da0dc1d5adc59d2268866b143868feabd83a7a27aa68c1d2d31b5a3c5b2dc1b5
+6f65f46b5a3e33c9d2202d901c9eb2b100667903493999f0a23bff58c6dafedb
+7f2914f65e1654579b4a7df69ee950a223b679161f4455cde3d275edb6e8977b
+5067a80d040e349655f4a3a60adffa2a9e56a4c0a3b1d4475c29dbcb177b18bc
+2c755214570988ebdcce1d8f54c59fca0b0e19116e6a2bf951ee7d00b3feca8c
+bbfe7c9914ff0eca51ac6273ce6e8f5915eae72bb2e3b8c67635da114e783e91
+57f1ea86d828fc1aa506e0fd7843bb036aea75714c5638793076f0b87de3f6d8
+8527a8b3adfb5171c076ad8fc47b68f186af94a0de3b7badfa167cf09c7909dc
+d9649484238f6be5c2cea6b58f37e55e6aa1e331d2bfd64e5103841ebd324f3a
+f9b9d7ff7dd9934a1eca605ba4a38c926c654d0c9ed2ef59506fd93f25ee09f9
+ef6383481ff4f29d33098d0b1c3675a256d924c8a344590fde46880cf9c12128
+dbe09c32735b56de14dbab2957fae3ba143a07de4b48c25b399b0e77d537da00
+f0fe4d154f1e9a09b9c4bb74f62ae698fbfe43bd06f038089e8b1876eb07e249
+533f9aca9fe1eba0d89e0bc4a0f98488462a11f95f6d77ee6ff20508293d8e71
+cc81d9bc80450c2c6e6a0fd46b8a24aa263acd703217ff2e19e7c61d7d1200aa
+b8c6234e397ae8f474f7afd9dabb65cb6de25212987a8530dd5ee78aeea4226b
+56fbc58fc736c7fe3eaa7a842907cb4f4589814479ad2a09bd1e3bb0bf575d15
+f0aa886a56e30b5991f7064d4b298c4eeb2c6a33a7307014315e3dae2ff72581
+283e0a607a7d8c62766a23f42c8722590a784b34f5e97b9707bd45fbe3b08630
+1df111d0aba815c03369f7caf6aa950498bcd6bd02736bf70970525b70c023eb
+11ef2901a01abc672153ddeb66264d59a7eca681c602e1b4a362bd4fe09ec788
+2e739d4c3aa9ac9446b2cb7da376c4617cdf9fe436aab026d2e23c755be7ba9c
+fb61013c90dd986c2f90ab7444661c16ac4db3a8402b03450173aba644a61e98
+99d1cafa06a27e428ecd6307ae89b1747de0609ba95f4d882986a904f1c1f3bb
+da550327d637a29fb1b4b23f5e08772889d27bf3cb461144d0f01db6699b2cc4
+8c2a7cb91090432d70fa673b7ade79f862e9b79a94f06405c44c2b0de8f233b4
+8080e440921cfabc9133916a0419613528f04b00275b140d691f4057c5420d49
+a1e53fde0e926a60613d1168d886ba0e2d5de629e212cb9b8b4e2253a7c7b531
+8287a0fa5438528bd414e679d20b92d0ae2ed58d2c6874354ece38b933a1da35
+508e5c4e63e62437a77a443a2a9ad7044d63f36c23b5f3a2ab443c717219e02f
+dcb6a4286f765477eaae1a18bb63c3375fcd109a953a644e168f2ac0e1d85f91
+3d62b34dc51e3a134aa30bfccfd6178cbe7dfde088a435ecd7b310f5f10a5b8d
+9387ad196d7e1b37047670c8ef0397cd1f380415de77419a20ea5349ce908027
+a8f8d1498f5bffa712690e4ccca077a452c902fc4a9b7c7882ad7b9db0d626c6
+7def29713c25d7ca466958350d56aeb8dcd0987ac05fce2205081d301cd3e601
+f386f99eef9857fabc6bf49a32ec3d4a921ed57063e5366cfe51adbcc48b3fc1
+ea1e40205c7fccec4fbe6dc0bcc7af55984f6e49a8a466e144b7e1bbf63506c5
+f60caa2320f9ec1732e9767ff4c9effc5b2f9ba6a9dad549dd30771c53f9a4dd
+24adfd39cc5be74851d51b4fb038b22fb65eca6cecf6c7e902e9391c89b1587c
+509bf28a987cb1c8ad74ebe7557792a9ddad7dbdb1a367c633a8eacb91e3131f
+02000b5d06213700a011011cc1cfc3abe1bac54878cf32145d9b18eea7b59b9e
+19f551170b92472f8922c0838f5d7ab5421d59a325c05372fe2c4c37184361a3
+71572d378c3d0de308e2c1cf3dd7f2c3dfe703de9df84e569dbe2e8cf00c54f5
+15db602bb2d44d1f99cf2aaaf78953b4e838fc1532e2ef4de4f0268b2e6e2c2b
+cdd46138aac78f9f34d6911ae387008d594291c7a57d693386170e7e6d377cf8
+11966b59bfa3cf47fae49324348a91f80399e29991d659b32e0fb0af79763ad0
+93630b21d061cd935351fad564039a2fac55b025a04e9668771118e86f9216bc
+55c4cc8ac588333bbc1b8fbf6b76a08bc14e1f83a683aefed0e20c7bacd2ee51
+96d723daf15279d8e2ebb993847b43d63e4960ad2f4150b07fd081517de48a9f
+6f4ab09802181a2e4e44cb0204200fc112fd99ee2e09cbaa3c6d3cbf97f95bdc
+a4f6d06ad415ebe00d6a78c3ac0ce6cd4dbc06d2829749b7e410e6fbd6f8390a
+b958ce73414a8766576c3b642916778c22cfcaa498afd93aaa19cd506e231a81
+f9994006f5e948a2b20912d70132e6a0382666f35fcf21b6edd947d1e497f66e
+da9cb07d716cabfdb6535e5ebffff6196b495327fa4e5c60778f76b40d46d4f2
+769ea09961f24b8110be2c59f426f16928866233066135ff9e1f15379ed5b11e
+de442dae3895e438ac05889c683687242a4758f1a234ef144510bbcc89e20954
+93272fa2dd7373c6b7cf9170287f4dbe3022b20bfc08e27c3232514c813e93b1
+cb37193b2eb17a832c1833ece9666aa24c12424d6c157d4f279e1950d45b4940
+2c54e2bee3f3eb017af95881e32f009764f5e67b93eeb2172023b510a874a159
+4975ed54f35268d3df9c2cfb65c6316b8178d1945abae3c5566b9da3c8681b4d
+acee486d5084d66e88620af120a44b5e35ab55591ea5283b4a69a31a9f5b212f
+96456ccee559dd42c58b6da34dc181a0ec0a5be41a9b3e0958f399d083fba072
+686a38da9b05355fd47c3f59708c7b7d4c31de833a87a4d8da6abb4b9d2ef519
+7575de9dc5b012e2e3aa51c407afe407001a0714d7ef1184b33afe085754edeb
+3ae4472f53011049af4ce4e0ef299720cf8c10911dc0bcb0f9f81a8caec00855
+91b750dcae4e3b0e0f507c25c1ca59cc38e366f27ea5f13427f2bf6809fcff29
+5d513e03c98272a0a822592dc9682a49d9324dc1c07f5b8b933ea9232ada0108
+27eba8837797d8782f46dadf53cb7404cb6612e71820478bf37f88615ada4989
+00f10078a2385cb83c8e41aa5464148bd90f7f2a18fd0b3f41a78b10471bc33e
+0b2815ed757515a331a6dbf861844ffcbf6b37ef7f348d7d918fefa2248d82d5
+56580cbbc3ff9c079d04b9ab4fccf1682c0b66358cff6840a9808d91374319b3
+09fb2f795569eab9c00e850a50afeb42248a251473156bf4d220f220e8acd814
+2554128f83693005694e19707d0630ddf56bb55f47f21f87c37286015677685d
+d6763e4a3e28de9d9f7292f659ac307ef3e83d84529389f7eb0639c02239861b
+86df711f11038176d9a70b04aad94f14285bd7ba4b264696c383bdb6cde76ea2
+c3870ada2dbd7fa7c70e3fbf92ee9baebf07e97da553aec0c90ea3feb38ff428
+7625706fd41f54aa6d85124920c33c48228cdfb0e426eefbc3732e12813bd368
+01698802e58db4eed2aa7fda4c1e439ce2012457c55115d69eedb9f587e46b5d
+35abf7e7372203b01ffecff24226de4e428217c5313a2e8bb58efd0f465cc612
+c143ccae5d8b6dfe04583b1168259f15c363bd4a7db272642b0816c7e0e5ba0c
+b2bb4690aed8e0455b878e139c15ffa403828e30c079b3c3a26278050ba66ba6
+fce91bad29dc2d980c06678131d961e47363f11856b72f5ca039fc991ba248fc
+f838d935789da19681db4d9119cf55bd29847d2536ef4f69327163c3e262cf63
+695d42a83ab0553b4c72c5eebb9e71c358c5fdbc209ada006f885f262e3d077f
+b9966e8f53ce61e144d5a2d096a2ba15af75790609218bcbf7097a0342e1dacd
+4eecf33fe3c4c7dff8b4c1f501007957f8aaba2e7176a707064c266cb723ba18
+6139e0bd697b37528fbd3a4bc8be5f7c6a50aa6fc65a0a52fd310140e750176c
+698a919512867a1ae998b32043c0e11106f81154aad48fa3f6acc98bb2b38493
+863b2ab05aa77dc16e5b01b328d10dd7af79ec3a5fd38eb7712e9624cfa2a620
+74988bbd4014a7816a4826319490594c0abba1643e85e961870e7125de720ed9
+db31febdcb739b3f8f0a5a2803819f9f7f3fb3ac983e3b6743c7cb3feeebe578
+0b0456f15047a76ade4d764e56093b19ae91d52cd93ca61bab22095711e2dd56
+cdb2db4cb0a1120dba8939720c84f2faadbda3b0cab8ffd83bd77580e0dd59da
+67cebf38ac126e54f6b70b55e7940c6b44d459757958c13f1c6f248eefb5af86
+48d752e91788945fda3da668a887f657de389b123260e3ef4f1b93393c1f484f
+7268e84e78585c0a3695ce84bf217637f8326c98407c1060a00ef8950e498d0b
+79ce31264ab755493d4ec00babe341e431d34c8b7228d0c5297bb776992586c5
+ae9ceaed8020f3ed8c0ff4bb963b9c488f8ca6c667c851861e8410973373fbf7
+56b0355b1a0dea5adf037ccf59bd1d4957c116187c99c558d5827d609dd27cd3
+b2fcede2d281ad3c33c5f9d55cce22bb1b8e9b14d9b6dc140620733a10389574
+eed9fa2caffd5591b8091c8533cc29d600b4a75b738f8dd2951983f06da5cb54
+26c1b1039bdb3550ec6cba9be971034a125f58e74152bf38e59b6365e7c6be2a
+8d5decd0397e0edeb638238e8320f0425c5ba6f18ed9bb32693dfe1b079ffaa0
+3bef302dad880b1b4625865d2e8794793e1e63fdd5b0acc91d1f83d12238427b
+47dc31e3b218ddef4813ba2e15d872b0e6b640905e261405d148de380b062b9b
+a73f115b4f5ef94d4023bbad60fe61939b5c6cfcfacbd85110cc027a194d92b6
+f64b2b2bd0c5648e744da3048486e5d9b77b45f0360aed982351fadce42a42fe
+f2151ec97e2e546940a7d9154cef3b2b8dc94aa1076369d9b1fc54e22d65b6c3
+bdbbc16f315b52e31933acb14929e23f0a1806d7bef12578a6
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+cleartomark
diff --git a/e2e-tests/cypress/fonts/Type1/UTB_____.afm b/e2e-tests/cypress/fonts/Type1/UTB_____.afm
new file mode 100644
index 00000000..c41755d1
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTB_____.afm
@@ -0,0 +1,1005 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Wed Oct 2 18:25:02 1991
+Comment UniqueID 36543
+Comment VMusage 33079 39971
+FontName Utopia-Bold
+FullName Utopia Bold
+FamilyName Utopia
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -155 -250 1249 916
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.001
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 490
+Ascender 742
+Descender -230
+StartCharMetrics 228
+C 32 ; WX 210 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 47 -12 231 707 ;
+C 34 ; WX 473 ; N quotedbl ; B 71 407 402 707 ;
+C 35 ; WX 560 ; N numbersign ; B 14 0 547 668 ;
+C 36 ; WX 560 ; N dollar ; B 38 -104 524 748 ;
+C 37 ; WX 887 ; N percent ; B 40 -31 847 701 ;
+C 38 ; WX 748 ; N ampersand ; B 45 -12 734 680 ;
+C 39 ; WX 252 ; N quoteright ; B 40 387 212 707 ;
+C 40 ; WX 365 ; N parenleft ; B 99 -135 344 699 ;
+C 41 ; WX 365 ; N parenright ; B 21 -135 266 699 ;
+C 42 ; WX 442 ; N asterisk ; B 40 315 402 707 ;
+C 43 ; WX 600 ; N plus ; B 58 0 542 490 ;
+C 44 ; WX 280 ; N comma ; B 40 -167 226 180 ;
+C 45 ; WX 392 ; N hyphen ; B 65 203 328 298 ;
+C 46 ; WX 280 ; N period ; B 48 -12 232 174 ;
+C 47 ; WX 378 ; N slash ; B 34 -15 344 707 ;
+C 48 ; WX 560 ; N zero ; B 31 -12 530 680 ;
+C 49 ; WX 560 ; N one ; B 102 0 459 680 ;
+C 50 ; WX 560 ; N two ; B 30 0 539 680 ;
+C 51 ; WX 560 ; N three ; B 27 -12 519 680 ;
+C 52 ; WX 560 ; N four ; B 19 0 533 668 ;
+C 53 ; WX 560 ; N five ; B 43 -12 519 668 ;
+C 54 ; WX 560 ; N six ; B 30 -12 537 680 ;
+C 55 ; WX 560 ; N seven ; B 34 -12 530 668 ;
+C 56 ; WX 560 ; N eight ; B 27 -12 533 680 ;
+C 57 ; WX 560 ; N nine ; B 34 -12 523 680 ;
+C 58 ; WX 280 ; N colon ; B 48 -12 232 490 ;
+C 59 ; WX 280 ; N semicolon ; B 40 -167 232 490 ;
+C 60 ; WX 600 ; N less ; B 61 5 539 493 ;
+C 61 ; WX 600 ; N equal ; B 58 103 542 397 ;
+C 62 ; WX 600 ; N greater ; B 61 5 539 493 ;
+C 63 ; WX 456 ; N question ; B 20 -12 433 707 ;
+C 64 ; WX 833 ; N at ; B 45 -15 797 707 ;
+C 65 ; WX 644 ; N A ; B -28 0 663 692 ;
+C 66 ; WX 683 ; N B ; B 33 0 645 692 ;
+C 67 ; WX 689 ; N C ; B 42 -15 654 707 ;
+C 68 ; WX 777 ; N D ; B 33 0 735 692 ;
+C 69 ; WX 629 ; N E ; B 33 0 604 692 ;
+C 70 ; WX 593 ; N F ; B 37 0 568 692 ;
+C 71 ; WX 726 ; N G ; B 42 -15 709 707 ;
+C 72 ; WX 807 ; N H ; B 33 0 774 692 ;
+C 73 ; WX 384 ; N I ; B 33 0 351 692 ;
+C 74 ; WX 386 ; N J ; B 6 -114 361 692 ;
+C 75 ; WX 707 ; N K ; B 33 -6 719 692 ;
+C 76 ; WX 585 ; N L ; B 33 0 584 692 ;
+C 77 ; WX 918 ; N M ; B 23 0 885 692 ;
+C 78 ; WX 739 ; N N ; B 25 0 719 692 ;
+C 79 ; WX 768 ; N O ; B 42 -15 726 707 ;
+C 80 ; WX 650 ; N P ; B 33 0 623 692 ;
+C 81 ; WX 768 ; N Q ; B 42 -193 726 707 ;
+C 82 ; WX 684 ; N R ; B 33 0 686 692 ;
+C 83 ; WX 561 ; N S ; B 42 -15 533 707 ;
+C 84 ; WX 624 ; N T ; B 15 0 609 692 ;
+C 85 ; WX 786 ; N U ; B 29 -15 757 692 ;
+C 86 ; WX 645 ; N V ; B -16 0 679 692 ;
+C 87 ; WX 933 ; N W ; B -10 0 960 692 ;
+C 88 ; WX 634 ; N X ; B -19 0 671 692 ;
+C 89 ; WX 617 ; N Y ; B -12 0 655 692 ;
+C 90 ; WX 614 ; N Z ; B 0 0 606 692 ;
+C 91 ; WX 335 ; N bracketleft ; B 123 -128 308 692 ;
+C 92 ; WX 379 ; N backslash ; B 34 -15 345 707 ;
+C 93 ; WX 335 ; N bracketright ; B 27 -128 212 692 ;
+C 94 ; WX 600 ; N asciicircum ; B 56 215 544 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 252 ; N quoteleft ; B 40 399 212 719 ;
+C 97 ; WX 544 ; N a ; B 41 -12 561 502 ;
+C 98 ; WX 605 ; N b ; B 15 -12 571 742 ;
+C 99 ; WX 494 ; N c ; B 34 -12 484 502 ;
+C 100 ; WX 605 ; N d ; B 34 -12 596 742 ;
+C 101 ; WX 519 ; N e ; B 34 -12 505 502 ;
+C 102 ; WX 342 ; N f ; B 27 0 421 742 ; L i fi ; L l fl ;
+C 103 ; WX 533 ; N g ; B 25 -242 546 512 ;
+C 104 ; WX 631 ; N h ; B 19 0 622 742 ;
+C 105 ; WX 316 ; N i ; B 26 0 307 720 ;
+C 106 ; WX 316 ; N j ; B -12 -232 260 720 ;
+C 107 ; WX 582 ; N k ; B 19 0 595 742 ;
+C 108 ; WX 309 ; N l ; B 19 0 300 742 ;
+C 109 ; WX 948 ; N m ; B 26 0 939 502 ;
+C 110 ; WX 638 ; N n ; B 26 0 629 502 ;
+C 111 ; WX 585 ; N o ; B 34 -12 551 502 ;
+C 112 ; WX 615 ; N p ; B 19 -230 581 502 ;
+C 113 ; WX 597 ; N q ; B 34 -230 596 502 ;
+C 114 ; WX 440 ; N r ; B 26 0 442 502 ;
+C 115 ; WX 446 ; N s ; B 38 -12 425 502 ;
+C 116 ; WX 370 ; N t ; B 32 -12 373 616 ;
+C 117 ; WX 629 ; N u ; B 23 -12 620 502 ;
+C 118 ; WX 520 ; N v ; B -8 0 546 490 ;
+C 119 ; WX 774 ; N w ; B -10 0 802 490 ;
+C 120 ; WX 522 ; N x ; B -15 0 550 490 ;
+C 121 ; WX 524 ; N y ; B -12 -242 557 490 ;
+C 122 ; WX 483 ; N z ; B -1 0 480 490 ;
+C 123 ; WX 365 ; N braceleft ; B 74 -128 325 692 ;
+C 124 ; WX 284 ; N bar ; B 94 -250 190 750 ;
+C 125 ; WX 365 ; N braceright ; B 40 -128 291 692 ;
+C 126 ; WX 600 ; N asciitilde ; B 50 158 551 339 ;
+C 161 ; WX 278 ; N exclamdown ; B 47 -217 231 502 ;
+C 162 ; WX 560 ; N cent ; B 39 -15 546 678 ;
+C 163 ; WX 560 ; N sterling ; B 21 0 555 679 ;
+C 164 ; WX 100 ; N fraction ; B -155 -27 255 695 ;
+C 165 ; WX 560 ; N yen ; B 3 0 562 668 ;
+C 166 ; WX 560 ; N florin ; B -40 -135 562 691 ;
+C 167 ; WX 566 ; N section ; B 35 -115 531 707 ;
+C 168 ; WX 560 ; N currency ; B 21 73 539 596 ;
+C 169 ; WX 252 ; N quotesingle ; B 57 407 196 707 ;
+C 170 ; WX 473 ; N quotedblleft ; B 40 399 433 719 ;
+C 171 ; WX 487 ; N guillemotleft ; B 40 37 452 464 ;
+C 172 ; WX 287 ; N guilsinglleft ; B 40 37 252 464 ;
+C 173 ; WX 287 ; N guilsinglright ; B 35 37 247 464 ;
+C 174 ; WX 639 ; N fi ; B 27 0 630 742 ;
+C 175 ; WX 639 ; N fl ; B 27 0 630 742 ;
+C 177 ; WX 500 ; N endash ; B 0 209 500 292 ;
+C 178 ; WX 510 ; N dagger ; B 35 -125 475 707 ;
+C 179 ; WX 486 ; N daggerdbl ; B 35 -119 451 707 ;
+C 180 ; WX 280 ; N periodcentered ; B 48 156 232 342 ;
+C 182 ; WX 552 ; N paragraph ; B 35 -101 527 692 ;
+C 183 ; WX 455 ; N bullet ; B 50 174 405 529 ;
+C 184 ; WX 252 ; N quotesinglbase ; B 40 -153 212 167 ;
+C 185 ; WX 473 ; N quotedblbase ; B 40 -153 433 167 ;
+C 186 ; WX 473 ; N quotedblright ; B 40 387 433 707 ;
+C 187 ; WX 487 ; N guillemotright ; B 35 37 447 464 ;
+C 188 ; WX 1000 ; N ellipsis ; B 75 -12 925 174 ;
+C 189 ; WX 1289 ; N perthousand ; B 40 -31 1249 701 ;
+C 191 ; WX 456 ; N questiondown ; B 23 -217 436 502 ;
+C 193 ; WX 430 ; N grave ; B 40 511 312 740 ;
+C 194 ; WX 430 ; N acute ; B 119 511 391 740 ;
+C 195 ; WX 430 ; N circumflex ; B 28 520 402 747 ;
+C 196 ; WX 430 ; N tilde ; B 2 553 427 706 ;
+C 197 ; WX 430 ; N macron ; B 60 587 371 674 ;
+C 198 ; WX 430 ; N breve ; B 56 556 375 716 ;
+C 199 ; WX 430 ; N dotaccent ; B 136 561 294 710 ;
+C 200 ; WX 430 ; N dieresis ; B 16 561 414 710 ;
+C 202 ; WX 430 ; N ring ; B 96 540 334 762 ;
+C 203 ; WX 430 ; N cedilla ; B 136 -246 335 0 ;
+C 205 ; WX 430 ; N hungarumlaut ; B 64 521 446 751 ;
+C 206 ; WX 430 ; N ogonek ; B 105 -246 325 0 ;
+C 207 ; WX 430 ; N caron ; B 28 520 402 747 ;
+C 208 ; WX 1000 ; N emdash ; B 0 209 1000 292 ;
+C 225 ; WX 879 ; N AE ; B -77 0 854 692 ;
+C 227 ; WX 405 ; N ordfeminine ; B 28 265 395 590 ;
+C 232 ; WX 591 ; N Lslash ; B 30 0 590 692 ;
+C 233 ; WX 768 ; N Oslash ; B 42 -61 726 747 ;
+C 234 ; WX 1049 ; N OE ; B 42 0 1024 692 ;
+C 235 ; WX 427 ; N ordmasculine ; B 28 265 399 590 ;
+C 241 ; WX 806 ; N ae ; B 41 -12 792 502 ;
+C 245 ; WX 316 ; N dotlessi ; B 26 0 307 502 ;
+C 248 ; WX 321 ; N lslash ; B 16 0 332 742 ;
+C 249 ; WX 585 ; N oslash ; B 34 -51 551 535 ;
+C 250 ; WX 866 ; N oe ; B 34 -12 852 502 ;
+C 251 ; WX 662 ; N germandbls ; B 29 -12 647 742 ;
+C -1 ; WX 402 ; N onesuperior ; B 71 272 324 680 ;
+C -1 ; WX 600 ; N minus ; B 58 210 542 290 ;
+C -1 ; WX 396 ; N degree ; B 35 360 361 680 ;
+C -1 ; WX 585 ; N oacute ; B 34 -12 551 740 ;
+C -1 ; WX 768 ; N Odieresis ; B 42 -15 726 881 ;
+C -1 ; WX 585 ; N odieresis ; B 34 -12 551 710 ;
+C -1 ; WX 629 ; N Eacute ; B 33 0 604 904 ;
+C -1 ; WX 629 ; N ucircumflex ; B 23 -12 620 747 ;
+C -1 ; WX 900 ; N onequarter ; B 73 -27 814 695 ;
+C -1 ; WX 600 ; N logicalnot ; B 58 95 542 397 ;
+C -1 ; WX 629 ; N Ecircumflex ; B 33 0 604 905 ;
+C -1 ; WX 900 ; N onehalf ; B 53 -27 849 695 ;
+C -1 ; WX 768 ; N Otilde ; B 42 -15 726 876 ;
+C -1 ; WX 629 ; N uacute ; B 23 -12 620 740 ;
+C -1 ; WX 519 ; N eacute ; B 34 -12 505 740 ;
+C -1 ; WX 316 ; N iacute ; B 26 0 329 740 ;
+C -1 ; WX 629 ; N Egrave ; B 33 0 604 904 ;
+C -1 ; WX 316 ; N icircumflex ; B -28 0 346 747 ;
+C -1 ; WX 629 ; N mu ; B 23 -242 620 502 ;
+C -1 ; WX 284 ; N brokenbar ; B 94 -175 190 675 ;
+C -1 ; WX 609 ; N thorn ; B 13 -230 575 722 ;
+C -1 ; WX 644 ; N Aring ; B -28 0 663 872 ;
+C -1 ; WX 524 ; N yacute ; B -12 -242 557 740 ;
+C -1 ; WX 617 ; N Ydieresis ; B -12 0 655 881 ;
+C -1 ; WX 1090 ; N trademark ; B 38 277 1028 692 ;
+C -1 ; WX 800 ; N registered ; B 36 -15 764 707 ;
+C -1 ; WX 585 ; N ocircumflex ; B 34 -12 551 747 ;
+C -1 ; WX 644 ; N Agrave ; B -28 0 663 904 ;
+C -1 ; WX 561 ; N Scaron ; B 42 -15 533 916 ;
+C -1 ; WX 786 ; N Ugrave ; B 29 -15 757 904 ;
+C -1 ; WX 629 ; N Edieresis ; B 33 0 604 881 ;
+C -1 ; WX 786 ; N Uacute ; B 29 -15 757 904 ;
+C -1 ; WX 585 ; N otilde ; B 34 -12 551 706 ;
+C -1 ; WX 638 ; N ntilde ; B 26 0 629 706 ;
+C -1 ; WX 524 ; N ydieresis ; B -12 -242 557 710 ;
+C -1 ; WX 644 ; N Aacute ; B -28 0 663 904 ;
+C -1 ; WX 585 ; N eth ; B 34 -12 551 742 ;
+C -1 ; WX 544 ; N acircumflex ; B 41 -12 561 747 ;
+C -1 ; WX 544 ; N aring ; B 41 -12 561 762 ;
+C -1 ; WX 768 ; N Ograve ; B 42 -15 726 904 ;
+C -1 ; WX 494 ; N ccedilla ; B 34 -246 484 502 ;
+C -1 ; WX 600 ; N multiply ; B 75 20 525 476 ;
+C -1 ; WX 600 ; N divide ; B 58 6 542 494 ;
+C -1 ; WX 402 ; N twosuperior ; B 29 272 382 680 ;
+C -1 ; WX 739 ; N Ntilde ; B 25 0 719 876 ;
+C -1 ; WX 629 ; N ugrave ; B 23 -12 620 740 ;
+C -1 ; WX 786 ; N Ucircumflex ; B 29 -15 757 905 ;
+C -1 ; WX 644 ; N Atilde ; B -28 0 663 876 ;
+C -1 ; WX 483 ; N zcaron ; B -1 0 480 747 ;
+C -1 ; WX 316 ; N idieresis ; B -37 0 361 710 ;
+C -1 ; WX 644 ; N Acircumflex ; B -28 0 663 905 ;
+C -1 ; WX 384 ; N Icircumflex ; B 4 0 380 905 ;
+C -1 ; WX 617 ; N Yacute ; B -12 0 655 904 ;
+C -1 ; WX 768 ; N Oacute ; B 42 -15 726 904 ;
+C -1 ; WX 644 ; N Adieresis ; B -28 0 663 881 ;
+C -1 ; WX 614 ; N Zcaron ; B 0 0 606 916 ;
+C -1 ; WX 544 ; N agrave ; B 41 -12 561 740 ;
+C -1 ; WX 402 ; N threesuperior ; B 30 265 368 680 ;
+C -1 ; WX 585 ; N ograve ; B 34 -12 551 740 ;
+C -1 ; WX 900 ; N threequarters ; B 40 -27 842 695 ;
+C -1 ; WX 783 ; N Eth ; B 35 0 741 692 ;
+C -1 ; WX 600 ; N plusminus ; B 58 0 542 549 ;
+C -1 ; WX 629 ; N udieresis ; B 23 -12 620 710 ;
+C -1 ; WX 519 ; N edieresis ; B 34 -12 505 710 ;
+C -1 ; WX 544 ; N aacute ; B 41 -12 561 740 ;
+C -1 ; WX 316 ; N igrave ; B -17 0 307 740 ;
+C -1 ; WX 384 ; N Idieresis ; B -13 0 397 881 ;
+C -1 ; WX 544 ; N adieresis ; B 41 -12 561 710 ;
+C -1 ; WX 384 ; N Iacute ; B 33 0 373 904 ;
+C -1 ; WX 800 ; N copyright ; B 36 -15 764 707 ;
+C -1 ; WX 384 ; N Igrave ; B 9 0 351 904 ;
+C -1 ; WX 689 ; N Ccedilla ; B 42 -246 654 707 ;
+C -1 ; WX 446 ; N scaron ; B 38 -12 425 747 ;
+C -1 ; WX 519 ; N egrave ; B 34 -12 505 740 ;
+C -1 ; WX 768 ; N Ocircumflex ; B 42 -15 726 905 ;
+C -1 ; WX 640 ; N Thorn ; B 33 0 622 692 ;
+C -1 ; WX 544 ; N atilde ; B 41 -12 561 706 ;
+C -1 ; WX 786 ; N Udieresis ; B 29 -15 757 881 ;
+C -1 ; WX 519 ; N ecircumflex ; B 34 -12 505 747 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 685
+
+KPX A z 25
+KPX A y -40
+KPX A w -42
+KPX A v -48
+KPX A u -18
+KPX A t -12
+KPX A s 6
+KPX A quoteright -110
+KPX A quotedblright -80
+KPX A q -6
+KPX A p -18
+KPX A o -12
+KPX A e -6
+KPX A d -12
+KPX A c -12
+KPX A b -12
+KPX A a -6
+KPX A Y -64
+KPX A X -18
+KPX A W -54
+KPX A V -70
+KPX A U -40
+KPX A T -58
+KPX A Q -18
+KPX A O -18
+KPX A G -18
+KPX A C -18
+
+KPX B y -18
+KPX B u -12
+KPX B r -12
+KPX B o -6
+KPX B l -15
+KPX B k -15
+KPX B i -12
+KPX B h -15
+KPX B e -6
+KPX B b -10
+KPX B a -12
+KPX B W -20
+KPX B V -20
+KPX B U -25
+KPX B T -20
+
+KPX C z -5
+KPX C y -24
+KPX C u -18
+KPX C r -6
+KPX C o -12
+KPX C e -12
+KPX C a -16
+KPX C Q -6
+KPX C O -6
+KPX C G -6
+KPX C C -6
+
+KPX D u -12
+KPX D r -12
+KPX D period -40
+KPX D o -5
+KPX D i -12
+KPX D h -18
+KPX D e -5
+KPX D comma -40
+KPX D a -15
+KPX D Y -60
+KPX D W -40
+KPX D V -40
+
+KPX E y -30
+KPX E w -24
+KPX E v -24
+KPX E u -12
+KPX E t -18
+KPX E s -12
+KPX E r -4
+KPX E q -6
+KPX E period 10
+KPX E p -18
+KPX E o -6
+KPX E n -4
+KPX E m -4
+KPX E j -6
+KPX E i -6
+KPX E g -6
+KPX E e -6
+KPX E d -6
+KPX E comma 10
+KPX E c -6
+KPX E b -5
+KPX E a -4
+KPX E Y -6
+KPX E W -6
+KPX E V -6
+
+KPX F y -18
+KPX F u -12
+KPX F r -36
+KPX F quoteright 20
+KPX F quotedblright 20
+KPX F period -150
+KPX F o -36
+KPX F l -12
+KPX F i -22
+KPX F e -36
+KPX F comma -150
+KPX F a -48
+KPX F A -60
+
+KPX G y -12
+KPX G u -12
+KPX G r -18
+KPX G quotedblright -20
+KPX G n -18
+KPX G l -6
+KPX G i -12
+KPX G h -12
+KPX G a -12
+
+KPX H y -24
+KPX H u -26
+KPX H o -30
+KPX H i -18
+KPX H e -30
+KPX H a -25
+
+KPX I z -6
+KPX I y -6
+KPX I x -6
+KPX I w -18
+KPX I v -24
+KPX I u -26
+KPX I t -24
+KPX I s -18
+KPX I r -12
+KPX I p -26
+KPX I o -30
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I h -6
+KPX I g -6
+KPX I f -6
+KPX I e -30
+KPX I d -30
+KPX I c -30
+KPX I b -6
+KPX I a -24
+
+KPX J y -20
+KPX J u -36
+KPX J o -35
+KPX J i -20
+KPX J e -35
+KPX J bracketright 15
+KPX J braceright 15
+KPX J a -36
+
+KPX K y -70
+KPX K w -60
+KPX K v -80
+KPX K u -42
+KPX K o -30
+KPX K l 10
+KPX K i 6
+KPX K h 10
+KPX K e -18
+KPX K a -6
+KPX K Q -36
+KPX K O -36
+KPX K G -36
+KPX K C -36
+KPX K A 20
+
+KPX L y -52
+KPX L w -58
+KPX L u -12
+KPX L quoteright -130
+KPX L quotedblright -130
+KPX L l 6
+KPX L j -6
+KPX L Y -70
+KPX L W -78
+KPX L V -95
+KPX L U -32
+KPX L T -80
+KPX L Q -12
+KPX L O -12
+KPX L G -12
+KPX L C -12
+KPX L A 30
+
+KPX M y -24
+KPX M u -36
+KPX M o -30
+KPX M n -6
+KPX M j -12
+KPX M i -12
+KPX M e -30
+KPX M d -30
+KPX M c -30
+KPX M a -25
+
+KPX N y -24
+KPX N u -30
+KPX N o -30
+KPX N i -24
+KPX N e -30
+KPX N a -30
+
+KPX O z -6
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -10
+KPX O q -6
+KPX O period -40
+KPX O p -10
+KPX O o -6
+KPX O n -10
+KPX O m -10
+KPX O l -15
+KPX O k -15
+KPX O i -6
+KPX O h -15
+KPX O g -6
+KPX O e -6
+KPX O d -6
+KPX O comma -40
+KPX O c -6
+KPX O b -15
+KPX O a -12
+KPX O Y -50
+KPX O X -40
+KPX O W -35
+KPX O V -35
+KPX O T -40
+KPX O A -30
+
+KPX P y 10
+KPX P u -18
+KPX P t -6
+KPX P s -30
+KPX P r -12
+KPX P quoteright 20
+KPX P quotedblright 20
+KPX P period -200
+KPX P o -36
+KPX P n -12
+KPX P l -15
+KPX P i -6
+KPX P hyphen -30
+KPX P h -15
+KPX P e -36
+KPX P comma -200
+KPX P a -36
+KPX P I -20
+KPX P H -20
+KPX P E -20
+KPX P A -85
+
+KPX Q u -6
+KPX Q a -18
+KPX Q Y -50
+KPX Q X -40
+KPX Q W -35
+KPX Q V -35
+KPX Q U -25
+KPX Q T -40
+KPX Q A -30
+
+KPX R y -20
+KPX R u -12
+KPX R t -25
+KPX R quoteright -10
+KPX R quotedblright -10
+KPX R o -12
+KPX R e -18
+KPX R a -6
+KPX R Y -32
+KPX R X 20
+KPX R W -18
+KPX R V -26
+KPX R U -30
+KPX R T -20
+KPX R Q -10
+KPX R O -10
+KPX R G -10
+KPX R C -10
+
+KPX S y -35
+KPX S w -30
+KPX S v -40
+KPX S u -24
+KPX S t -24
+KPX S r -10
+KPX S quoteright -15
+KPX S quotedblright -15
+KPX S p -24
+KPX S n -24
+KPX S m -24
+KPX S l -18
+KPX S k -24
+KPX S j -30
+KPX S i -12
+KPX S h -12
+KPX S a -18
+
+KPX T z -64
+KPX T y -74
+KPX T w -72
+KPX T u -74
+KPX T semicolon -50
+KPX T s -82
+KPX T r -74
+KPX T quoteright 24
+KPX T quotedblright 24
+KPX T period -95
+KPX T parenright 40
+KPX T o -90
+KPX T m -72
+KPX T i -28
+KPX T hyphen -110
+KPX T endash -40
+KPX T emdash -60
+KPX T e -80
+KPX T comma -95
+KPX T bracketright 40
+KPX T braceright 30
+KPX T a -90
+KPX T Y 12
+KPX T X 10
+KPX T W 15
+KPX T V 6
+KPX T T 30
+KPX T S -12
+KPX T Q -25
+KPX T O -25
+KPX T G -25
+KPX T C -25
+KPX T A -52
+
+KPX U z -35
+KPX U y -30
+KPX U x -30
+KPX U v -30
+KPX U t -36
+KPX U s -45
+KPX U r -50
+KPX U p -50
+KPX U n -50
+KPX U m -50
+KPX U l -12
+KPX U k -12
+KPX U i -22
+KPX U h -6
+KPX U g -40
+KPX U f -20
+KPX U d -40
+KPX U c -40
+KPX U b -12
+KPX U a -50
+KPX U A -50
+
+KPX V y -36
+KPX V u -50
+KPX V semicolon -45
+KPX V r -75
+KPX V quoteright 50
+KPX V quotedblright 36
+KPX V period -135
+KPX V parenright 80
+KPX V o -70
+KPX V i 20
+KPX V hyphen -90
+KPX V emdash -20
+KPX V e -70
+KPX V comma -135
+KPX V colon -45
+KPX V bracketright 80
+KPX V braceright 80
+KPX V a -70
+KPX V Q -20
+KPX V O -20
+KPX V G -20
+KPX V C -20
+KPX V A -60
+
+KPX W y -50
+KPX W u -46
+KPX W t -30
+KPX W semicolon -40
+KPX W r -50
+KPX W quoteright 40
+KPX W quotedblright 24
+KPX W period -100
+KPX W parenright 80
+KPX W o -60
+KPX W m -50
+KPX W i 5
+KPX W hyphen -70
+KPX W h 20
+KPX W e -60
+KPX W d -60
+KPX W comma -100
+KPX W colon -40
+KPX W bracketright 80
+KPX W braceright 70
+KPX W a -75
+KPX W T 30
+KPX W Q -20
+KPX W O -20
+KPX W G -20
+KPX W C -20
+KPX W A -58
+
+KPX X y -40
+KPX X u -24
+KPX X quoteright 15
+KPX X e -6
+KPX X a -6
+KPX X Q -24
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A 20
+
+KPX Y v -50
+KPX Y u -65
+KPX Y t -46
+KPX Y semicolon -37
+KPX Y quoteright 50
+KPX Y quotedblright 36
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -90
+KPX Y l 25
+KPX Y i 15
+KPX Y hyphen -100
+KPX Y endash -30
+KPX Y emdash -50
+KPX Y e -90
+KPX Y d -90
+KPX Y comma -90
+KPX Y colon -60
+KPX Y bracketright 80
+KPX Y braceright 64
+KPX Y a -80
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 12
+KPX Y T 30
+KPX Y Q -40
+KPX Y O -40
+KPX Y G -40
+KPX Y C -40
+KPX Y A -55
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -6
+KPX Z o -12
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -18
+KPX Z O -18
+KPX Z G -18
+KPX Z C -18
+KPX Z A 25
+
+KPX a quoteright -45
+KPX a quotedblright -40
+
+KPX b y -15
+KPX b w -20
+KPX b v -20
+KPX b quoteright -45
+KPX b quotedblright -40
+KPX b period -10
+KPX b comma -10
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 25
+KPX braceleft J 50
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 35
+KPX bracketleft J 60
+
+KPX c quoteright -5
+
+KPX colon space -20
+
+KPX comma space -40
+KPX comma quoteright -100
+KPX comma quotedblright -100
+
+KPX d quoteright -24
+KPX d quotedblright -24
+
+KPX e z -4
+KPX e quoteright -25
+KPX e quotedblright -20
+
+KPX f quotesingle 70
+KPX f quoteright 68
+KPX f quotedblright 68
+KPX f period -10
+KPX f parenright 110
+KPX f comma -20
+KPX f bracketright 100
+KPX f braceright 80
+
+KPX g y 20
+KPX g p 20
+KPX g f 20
+KPX g comma 10
+
+KPX h quoteright -60
+KPX h quotedblright -60
+
+KPX i quoteright -20
+KPX i quotedblright -20
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -10
+KPX j comma -10
+
+KPX k quoteright -30
+KPX k quotedblright -30
+
+KPX l quoteright -24
+KPX l quotedblright -24
+
+KPX m quoteright -60
+KPX m quotedblright -60
+
+KPX n quoteright -60
+KPX n quotedblright -60
+
+KPX o z -12
+KPX o y -25
+KPX o x -18
+KPX o w -30
+KPX o v -30
+KPX o quoteright -45
+KPX o quotedblright -40
+KPX o period -10
+KPX o comma -10
+
+KPX p z -10
+KPX p y -15
+KPX p w -15
+KPX p quoteright -45
+KPX p quotedblright -60
+KPX p period -10
+KPX p comma -10
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 50
+KPX parenleft J 50
+
+KPX period space -40
+KPX period quoteright -100
+KPX period quotedblright -100
+
+KPX q quoteright -50
+KPX q quotedblright -50
+KPX q period -10
+KPX q comma -10
+
+KPX quotedblleft z -26
+KPX quotedblleft w 10
+KPX quotedblleft u -40
+KPX quotedblleft t -40
+KPX quotedblleft s -32
+KPX quotedblleft r -40
+KPX quotedblleft q -70
+KPX quotedblleft p -40
+KPX quotedblleft o -70
+KPX quotedblleft n -40
+KPX quotedblleft m -40
+KPX quotedblleft g -50
+KPX quotedblleft f -30
+KPX quotedblleft e -70
+KPX quotedblleft d -70
+KPX quotedblleft c -70
+KPX quotedblleft a -60
+KPX quotedblleft Y 30
+KPX quotedblleft X 20
+KPX quotedblleft W 40
+KPX quotedblleft V 40
+KPX quotedblleft T 18
+KPX quotedblleft J -24
+KPX quotedblleft A -122
+
+KPX quotedblright space -40
+KPX quotedblright period -100
+KPX quotedblright comma -100
+
+KPX quoteleft z -26
+KPX quoteleft y -5
+KPX quoteleft x -5
+KPX quoteleft w 5
+KPX quoteleft v -5
+KPX quoteleft u -25
+KPX quoteleft t -25
+KPX quoteleft s -40
+KPX quoteleft r -40
+KPX quoteleft quoteleft -30
+KPX quoteleft q -70
+KPX quoteleft p -40
+KPX quoteleft o -70
+KPX quoteleft n -40
+KPX quoteleft m -40
+KPX quoteleft g -50
+KPX quoteleft f -10
+KPX quoteleft e -70
+KPX quoteleft d -70
+KPX quoteleft c -70
+KPX quoteleft a -60
+KPX quoteleft Y 35
+KPX quoteleft X 30
+KPX quoteleft W 35
+KPX quoteleft V 35
+KPX quoteleft T 35
+KPX quoteleft J -24
+KPX quoteleft A -122
+
+KPX quoteright v -20
+KPX quoteright t -50
+KPX quoteright space -40
+KPX quoteright s -70
+KPX quoteright r -42
+KPX quoteright quoteright -30
+KPX quoteright period -100
+KPX quoteright m -42
+KPX quoteright l -6
+KPX quoteright d -100
+KPX quoteright comma -100
+
+KPX r z 20
+KPX r y 18
+KPX r x 12
+KPX r w 30
+KPX r v 30
+KPX r u 8
+KPX r t 8
+KPX r semicolon 20
+KPX r quoteright -20
+KPX r quotedblright -10
+KPX r q -6
+KPX r period -60
+KPX r o -6
+KPX r n 8
+KPX r m 8
+KPX r l -10
+KPX r k -10
+KPX r i 8
+KPX r hyphen -60
+KPX r h -10
+KPX r g 5
+KPX r f 8
+KPX r emdash -20
+KPX r e -20
+KPX r d -20
+KPX r comma -80
+KPX r colon 20
+KPX r c -20
+
+KPX s quoteright -40
+KPX s quotedblright -40
+
+KPX semicolon space -20
+
+KPX space quotesinglbase -100
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -100
+KPX space Y -60
+KPX space W -60
+KPX space V -60
+KPX space T -40
+
+KPX t period 15
+KPX t comma 10
+
+KPX u quoteright -60
+KPX u quotedblright -60
+
+KPX v semicolon 20
+KPX v quoteright 5
+KPX v quotedblright 10
+KPX v q -15
+KPX v period -75
+KPX v o -15
+KPX v e -15
+KPX v d -15
+KPX v comma -90
+KPX v colon 20
+KPX v c -15
+KPX v a -15
+
+KPX w semicolon 20
+KPX w quoteright 15
+KPX w quotedblright 20
+KPX w q -10
+KPX w period -60
+KPX w o -10
+KPX w e -10
+KPX w d -10
+KPX w comma -68
+KPX w colon 20
+KPX w c -10
+
+KPX x quoteright -25
+KPX x quotedblright -20
+KPX x q -6
+KPX x o -6
+KPX x e -12
+KPX x d -12
+KPX x c -12
+
+KPX y semicolon 20
+KPX y quoteright 5
+KPX y quotedblright 10
+KPX y period -72
+KPX y hyphen -20
+KPX y comma -72
+KPX y colon 20
+
+KPX z quoteright -20
+KPX z quotedblright -20
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/UTB_____.pfa b/e2e-tests/cypress/fonts/Type1/UTB_____.pfa
new file mode 100644
index 00000000..36ef3395
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTB_____.pfa
@@ -0,0 +1,1134 @@
+%!PS-AdobeFont-1.0: Utopia-Bold 001.001
+%%CreationDate: Wed Oct 2 18:24:56 1991
+%%VMusage: 33079 39971
+%% Utopia is a registered trademark of Adobe Systems Incorporated.
+11 dict begin
+/FontInfo 10 dict dup begin
+/version (001.001) readonly def
+/Notice (Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.) readonly def
+/FullName (Utopia Bold) readonly def
+/FamilyName (Utopia) readonly def
+/Weight (Bold) readonly def
+/ItalicAngle 0 def
+/isFixedPitch false def
+/UnderlinePosition -100 def
+/UnderlineThickness 50 def
+end readonly def
+/FontName /Utopia-Bold def
+/Encoding StandardEncoding def
+/PaintType 0 def
+/FontType 1 def
+/FontMatrix [0.001 0 0 0.001 0 0] readonly def
+/UniqueID 36543 def
+/FontBBox{-155 -250 1249 916}readonly def
+currentdict end
+currentfile eexec
+f9cd86fd4821715265f16a614e3770ef80561e77ba6ecbb4ef4232445eef2839
+94e93e3c8c9b09c09d71542241821e07888ceb54e8cacc8a45802f50c0afeeca
+5e3d9114c1860bcb2ba5fd2a879a0d79953e30c90d8347513f4ca5f05b2e231b
+4973bd1e9db66a39d846a8b3d9e48da72e2de8743ba2e104893167c235719245
+87b43ed3b6552cd85e4bbdeb9f46bd813298d531c74be81995a52ceaad4112c7
+f65773b088bffbc9874f615371c5e7b50ae99d5b6acf9c80d87057bf3a424377
+f4284b7232096b6ea623dacd1c925ce09cfa6515f675aacf815c38e984b4ba40
+0d409bb62cd4ebac75201c8e782e68fd73e8622c46b696b1ba37ca9621400041
+d95b948a6bbc089fe6b230b39bd358228af9b3e68418166a9adc4e7665341088
+c47c074aa4b21b0949fef9929e1a32ffddd2d02145cdb256863cf6067d27ed9a
+f0aba85b1e0cfcbab7e74880e9693b5626ad31e6b5c2d208087d86c116513212
+710c9d6ca50e1503f8d4c2863fa378f8184189af0cf109f4affe7cb74563d513
+2104fb9dc6f3f5992e075633a0baebda8ebe9a07c3ec4b25fef015d9915a26b1
+d401a6722015090672714181580350560caf79bccd6040c0c1651d917c73107c
+2d46aad5d4a370b55e896ce1eed127b6f0aabaf7c4ca62e89003032abb2c95ba
+58c4a0eb0bb1a59a439a6a0c2082e50725b9e8139324af06ff03b4269a697342
+8fab0c1fabd320f1452d9c435e48037eb8cf6df4233cd9c05691604146cc813f
+7ea131f21e9863b7bd1b087a583af14675b9bd31fd364d5dbddbc9b533a6ac38
+c4bdeb28a1ae3d2b374da72405acf67b1b3a83d80726301b3677c067c748d857
+ae1da4f723d4365647de10f90e96dd7e9ab9ef5c7e6866a13b807fd3f78136d5
+72950a10bb0165e61f12c60a813c7e35eeb9a0500725dd912ca2deebb238538e
+790af851b14643f40e5eea13660e57b5c407735079a292a7c162dd11d0a3d342
+67290f1c9261fa5de6b92ca1e7daeb7cb970c766b3e0396ec0ce55dffbef0bdc
+b437fbb4d63634b9abcf5844e76f9313ab2182e1fb3c47a705a1d97b79e93dbc
+5e48ac19b45f9be4b9225e5a7e1d3679dd21001eca3c08d6febd35fa5453290e
+1269c1d195e45338c10aa60fb8ab9e5a5aaf08c82e737f082bb6216d7cc1d5d8
+8906c8b05c4432d04561377438447eb5fade652af84c1ced99551365cf9e6f99
+03d1593de0e450003fb0d5b5588330d6527fdd0c8193fc0cd2293012310a425e
+0ea0f8b901b991648ebbc894cfb7d95da6d35fb7c947e887bd3b6599e673b921
+c96f2a033e30b341a14066076ff0abd5fe77f393b548a15ea2185f99a3545b01
+dada854fce1ddd3448a9aa42ba260a4f85a0d1bdcf08b3de2ff8b6411adff446
+4a2929193ade35f4e26006cfdc45929250bb76ef007043d739d798a4973f350f
+52fa1690423381539e3a4cc458600fed91fa8e16b5ae0aca710dbf318d16e3d9
+16a082a093b59f35ff92a1b235d5621e87ab471310ca26a07a9ecdd3cbdc9bdc
+7506ff13b886b26eda6d4ccc52a312593a0f2845a4a30aef829109e6b76bfcbd
+2f2fe267d9376acc0686c9b1df3a25fc41d9e5390a90249cc26c502e4bcf582e
+ca5054d96b6ebb2bec79ae23125b4c2a13a97570153b83dfcc4d9dec9717679a
+ba8c37c02697d8f47269b9ddb983bd4dc029968d0d1729bcb9f94c899906af40
+0df490eaad13acd5a893663fc90a850533263cf6657632e8e4df31e6e6636521
+78dac783feb9616172d7651e3e4092b409383ccfcf8aa04b330119c334d6c8ab
+bdf97d97fb475c25331044413a117dee0f15d6560cac5ca6c01c1c98b56d2971
+724c135de3c624fb7a1994a8267ead0628af72d5af1dc0ae3177c3b678dc15cf
+9dcf100dd9de9bb71f568661293515002220681b0afd9775464be2f66df08c5c
+7d3995a57ffd2af299564dd57614e3273d8555f705ce2a2cb095cd662acb8163
+dc5c587747e27fbfab667dd449228e7b2f40cea584c997d2b98e78a063edd327
+9e23947ba2577035ff349019293b13953ceea11b49b230e2a29d295b032ebbe1
+dacf823e3587184182d0b910794a1d784f0bdc21c74a65d12b5875cdf0c1cddd
+5995a6e56c9a95dfff55167f82b3de41a2eb653b0d1eb61e9c48654424ce13ec
+8671211b2b8a78839a4569b3cefae520e91c538f09d96f98ebbe905b01ddb080
+f7f6ff00d2f662e02bbe7dc3044b66723956559669000810f706f4ad544b9a29
+b384f554816696eb7b59540b407d438efb6c8bfdcf05324cdb65e9ffb8864a9a
+05adaa3678a341983aac1f6bed28040df2f0e18d27300cab0930958704d900fc
+5e194b149740b089814d1050e11a9d885bb1d835a7cc092ada9f08858e1ac5b1
+84b6fdfdea41d4b1241ae64fa3c47f125f48cc05c5dd8e2c896bb88b0a846379
+4477252f0d7abd31d4bd5703b9f280b09a59bfaec0663dcb2ccce2821e0e382f
+837f4a4fdf0e57d531383155f4fd633bc4b7315e54425a0395bd91f050783685
+eb0b85e15e331166795acc5dcb2ea1239209f8296c2c4cd1eb15b37e3ddfd2ba
+33a25c3761da66c0c08635101534607ca02990447c210689d31e49376d152161
+7696160aa2da75f993535e6cfbce229c657578620d7c8de8cf720f21ccc22ed1
+2be5c92bf7e26cb750f707fa7e2348a04a642ff319cda45fb9884caacb562df1
+365992416084a4d40da9831865b02246bda402f4354e8882e98215c37d3eb3c4
+d2f9ba2b93f190005da1bbb506a5d39e2a24be0ddc50e1bc5235d221a151698b
+803f0f0414bc085a395fbada62a73a9a3d4c66415ff1704226f8437e9bc470cc
+63011be0e1f3dbe6e6ad3ff35541961f6dfaae1425b2020349486880fe7fae78
+5a2db27a586f0daddab8ca62887db0a18462a9a3d821f31923c583bb731d7299
+f051b8db5f33fcf53c37c1e4ac7fa0275dd39ab9abc97cedd1c65a3d514d96df
+8b7a49217fe1af15b5c738e927028d1ec523e8e8575f018e7bb3f7e9afc81ff9
+3de7a4969ba59f7093772e5696bc4f2d6616419b43ff26f2a9da0e183f7bd542
+b31f175724d2e8f18a61ccd010d8c18394f640f905b6832fcfe7d3e9c02e3e03
+911dff14d0a1c73129ba85e2179372aff3c028388f30621d1f8aac757660079c
+63c22cfec96ad9ee4846c0854ec912abbf3f934e293e0f1211786455019a7f7f
+38f9e877f4186a9440c75a4babf3a9fbad1c5fc484082e1be4084ca3018b676f
+0e037cfaa4195a18948d8bb46a6d00b68213c09d5e69673ed4e00155fba9d840
+9e08ca2dffafc93110751b59ebc2b6ed4bf5f535ac9d73c9399d9081eb6b2ba0
+a13aabde56a7f9200749c9e866cac735bc6e2f7859cbda912f0c26e85d3f7b25
+3185ec64546cfeb89d3992b12815e70a548299615c6889379218c0e7dd1cffaa
+ff4ccaf3c35c77b09513b5f9b0d501322a862af4f6df0af50b62e4e1c1b3067a
+f5b24a44190c41e96b659f62921dd6075609f7d7db532298a425c675e4537952
+bf06fa9b1295e32068740c361048301f6ba6386602b8cb54bf8f38fd77669f5d
+791a7b1854b7f3effa193e9e5a435694b1c8578ec78426134c9c56e6c9f08951
+2469a5137bb452be5ec3b3d27c3cbf6e6fee0d58d73dd3d277bb0c1540ad102c
+50b746093832be65d05c102ddc982b08c7ec1fba629dbe27d4604c1476d91392
+801f91ff781aeabec88132162e021741f8f31f06db82b29d17fe66ee04e9c200
+5240d68ba82373c7a567ba33bad4aed1d6b5c570d03c5c394c09e5c5eb9e924d
+9e1c2f161a9d52cbafdad75dd674327ec81ce286cf1abfb5ddd8606de9265784
+5c950413caeca8ef80303793826c3ac4b58d9fd4b02fab612fdbf71c2a1698f9
+048769235eb9dd35823536662ccccc128c74818c8ae48edbe9a4624ece9d1ff5
+39795c7b4808520a08d611af58eec60bfc8d272a944201096c45749bc97f32ce
+a52618a1d6d53267ab4fde671ef1a47b03687a59e47a8e277c02a618af09b78c
+f5be89f5da433ea9d31cb8319bbd220099a8d7d2f879abd99b4a96b06ae3f8ab
+4cb89e72fea1355a010405cd72dedc7bd35b3ae32fac3d40642fe88e7a938579
+f4c10f751174cf042331e7274eeeaa28e5829f0e6e866783412a45e8ce8ce127
+5438c3fedf8cfbc9078e2b6dc439d9a513fb2a74a672b496e19af3c092342811
+983b1a3d6c60981db5aaf03b1fcd375f4ad64a10b57d378f52f29cb89dc77814
+1dd5106934bae98eee32b779eddc6cb5fd13d38ee5b39d4513338af4feafcece
+dda1f3d9b6f0b5cae4e7efa14921578cf07ad6e1eb7e0a3bf7d52bb7e2fc9656
+32c5c642a6ab07c108d79699d56727db2da956cb576b97648ade80eab2ae12bb
+2dedab0672ae666115758614c91108c9e4a4da0bd49f15379cf6716a303accbf
+477ae6756d18a0f6e3801f72eaff179849888104e38e69fb722a385a630e06ad
+e71c5553a14c5955f6189e08f8cce45ba8fbb5c5b6b0bac8ca48d11c0656f21a
+9b9286c4f958db61b111ffd40b3c8d69720b6e2eeac5452ba0837fd6806464f1
+0cf5bd73423b9f065b6ff58954a9090a53e31a16fa9703700fba14f8b6666ee7
+4b6a7bec61fdce1fc10245e2e90e31463bb7d326857e389d87834ddffa9ca581
+5a5abc54a958bd3738753e38b255cb1012cadb7278f8e84d9c649a4a659a8385
+19e128ddbd0985505fc613d9c577fc10c429648ad18771b0da1087ef584c2628
+b91d33da27c403c0214e0b7f984a9bfe6c2c574883f446e4dc1a563c32898bff
+ca2433f1161168565e063ecf045408d619f2a3215ee881cbd13a7751b14189bd
+a0cfafee7ce4d9600fdf993ceddd965c4a608129c89b52b5b59419ed079a6f57
+9efc222187cf25f848d47bf3bdca06ec2573fa8fe20624cfeec3f9ead6ded709
+75cae667bf025ad867944faef14fa9d6617b74cee3804d5edb688d9ed4a0c2bf
+1aede05eaca61e297f03026864c738014d8afd79294e062e04e5fce1a784d850
+b00e58fdf6fb5182d6ff08bcd05d2c7860dde223a37ce53131c09cba9c52e9e5
+76cf7e58bb99f045c8b23d67c43449c6c3f6acdb5b5f35be96d17fb0f8d42685
+cc79fbd421fdbed2fbfbdbac976736a6fb34c62eded4a12ad456d94aea2a2bf5
+f1fe4e1519361177998c8272866d26cc0e1af48fcc5a399edb8550046f3e8b31
+baf20c0ba1210fa2487cc2982e3d39e6a630d11813801bfdf4cc3695384410e6
+067d10d37aed54394448427032c9cce4d00f89418f10a45fda8aa392db636c06
+a26c0b39b25f128f7e5ad81a548f643eb1802117172d9be1fccaa3f1a73b7c1f
+1cb21f7f8f71fd8d063ebf41cf2f3eba32392f0303affcb36e113a7e46e5406d
+20e6c47a9be1e8f8085a8c49b04d219e13082d344c91dda69384c0b16d3fd7e5
+e46f213b789603c5d89257d6510ab618f24df56b1ded2029779af220b40eb86b
+cb9ecc32f2b1b713132ba229c3bfa888b014e12ccc1cc902b28bd5a9d5263ac2
+a47b38f37f0c9b279418b1bb81b288d0312667dfeb0aff86b8072c2bbdc79835
+f6a04d34c0825caef54293348bd6c672ba2ace4d7d3fa604faacba1a42dfe02c
+0150abaf33d0a6582071af6b15929a4857cd57048c9e32b04237ba047a2e2837
+a23da6917bf5912e2b1abaab68fa65891788a6c3b30ced2fb907e603a7654c27
+976d2fffbad1538395c05cf876bb3b64198e0a1b94f40d748c03373736018466
+6a9a9e53e0a159ac7a5890bcc18cab7172dedf4b8fdea3544184b0032edee0f4
+6429cd0451949277013d89dc6bbb23566cb158489d168a341271aa99618de261
+9580e6ebc094ebc5d7122fac0cc95021df41feffbf34ed59dee7556ecad36d75
+12b96ace5f81203e47d9e3b9698163a175767762fe2278648d0823b7a41e8730
+16fea5eaf17effef6b2c537b2727de3ab71987b49f9bed16fd8aaa06e5580643
+c2514bda11e7fb68f310bdded37ff6e6fd67b04c83fb8ef288da520954f0c2e4
+ee463de5ce7bcbdfa5403a4de7c64a7d9807dc7fc21678fcbee60d8b2bfa59bb
+c8303d7f060c0a2182f6779a386fd82b9b44550a03d98cfc0095524a7308cf6a
+e0c2c366b8bba56ccc354f3319a888e1483595b28f38868231631d25f50743f5
+07d08da61232da0353fe9fef9bee08c93e66b4cde93369efa538d03dcae19d88
+1fffcbd05c767b97fdf225685d18d7076b2b9b788404b79715b18655bf816f57
+2d96ea5f0d6ec848b399d0629c5775e5edde2a79a3dc6bb189506fcf65ff104b
+389ccf9387a3d6d243e4c83a5d78e9ee659b8e47b54559c80e15e1d9b3fdf838
+565e0ec14404dbfb5da05ebd0c3cc925f2a0be3ba741b9dfdc0531c2d35985ff
+89268541bbc6e4ecc13a50d937cead6b3a9863f85a8c1870838f4abd35494e28
+92248f65c7d3c452f6f743a345087661c3f5e8f09cc6854e7f2d6b5c9461c0f8
+3170337fb694d91478be1ccefe270f28e1c8ce48ffd25ed2dc83c070289405ce
+533af0bd4aa922d8d3b9220edc5b2fe6fadeeb81dfd270ee469b434f44622223
+307e041823e65f762048d028c2b66c306422102c19e3eb113b6f613759b68d15
+9ed5665ab83a1fd1e8d45f0de9c187db9f0f7d145c3bc457b5ac065a0bf447d4
+2f92b61a915f4dc8c03757eaf6cc115c87cf2aba456e01834a0e283347d487b5
+0e9bf1ef39ff7ce6505d1be0e20f425943cbbb4c188a359348d67c0247da0c3e
+3116f9a961e616894a704f192f550ed25503bf14c828ab8bf3211ce02689ba55
+49b2237fb5861f7fd2003d4c82a15ac10f5e1325928aeff9169fcf585334f62c
+571057f3e5d5bf3ebbbac63110353fcb05a533af4199d0abf922653efecb2177
+5fe358df229a5daac3ebede23bb469173a33ada03cb2afbe309195dbcfe08cf5
+472f9979956828289448a9781ff3b8a4a3f375a57ccc6d1ed120ac150b1b4ad0
+5d76760c330d85616e88801050c93c9e44dde990a8d1867ecdcea7700b6ad389
+010a5f0b846c2fbc7e4c4b2daaf0fc6443004d6993fbf3990f78d69350bdec37
+66b9f8d3659fb44cc7917208072d1cdc3618044e0706804aadbfd6e6823e197e
+e8dcbeac8f6ae996260f5d75279b3d1508ba47b61759dbc1dfe41cc229ff0f5a
+a3ff10b0e1ab34e1b29fc95f245b69d61f8ccd5835937b8cf2afda032bd85635
+9448b70d736946734ecacdfc0652cd5fc7653b3fd367c4c5d9c7f7ae1be27671
+8bf62030e55fed95b073eb8b805088eda02d726e2f9416eb02ef7ed5ee19b57f
+f196b0ce036cee614680585d911a289a4953b5288dbadd44e2e6c1e406836a6f
+cab530c36ddfcef4a58922f96cdb00a41929cc98dc77c52dc6c7e9d83b188a88
+1c8a16de3557f9bd5b23e03d5c9cd00f921a1e7f7c520b6c55205ab0dfc8caa7
+6f6d7aa1e2c4819e4d19af8bd07aebb63e716d0a4fefbd0c9600e5c5a9857ff8
+a637e2da191cc7bb8b1a09f4f4d1ebad7549b7b2d41d74cc991cb0ef99fc1bd7
+b3575dc3003914643d277f88131facc8b58b3bdcc55dcbc372de6e3104a92609
+09d9fe9c16e18ddb44a481438ca22330c7d6b7a4eedda79f01bf19c266ec672f
+99fd1f26d55faa7c485af35609a799918dce99225bc28a60256f5d393d488bc8
+660af7a2651391bfb765a8e7993a8701dec9254c983682fbcd136b4774ee2c41
+bb2e1c64a6371db80c433e8b0c14658ce0ae511bef8da3ab0d10d4aec7d9545b
+90881504302bcb086528834bee3a0c744092067de2205a7d773a5b1882d6094b
+9f32b36d94033259881aaf60c7ea2eb496ff2bf705aeb576a41170461fd95e2c
+0c4a65ba15357e7ef123f8a749b89583e037e1e9e2ea2d8f435bce1627cbe529
+7183f4634de4d321d6b63d939abd38085a1d1ecd916fceb8dccac6798b358449
+ae06c3f7dc948b9f32346a3f492db2b894605f0d33bf2ad98445afcb05b6be5c
+2f38338c78b40cc533b4d7dea2b9760f48a5822ce3776e87a8748f81c84ee7d3
+d43971f8d572904da8b6e1f2f20f95bb2c31a4febdf195973dbb2b8399883d9d
+7edd05e710d4161fa96e30244dea9c2dae7ed30e36058bf077f060067c3c6331
+b26d7e358cf3efbd5dd8820d0f383071fd7c8f29886e7fa098a9521eeb557af5
+d275f95ccd75f593e97aa2a1d7463ed69ca8b87e3ca8359861cdf24ff3a01955
+befe00289476891a2db20b0c808ccd590598c4486d00a53ae66acea05e921f91
+b6b8410bb4708bf580998f9e08df92798f3da75b1ba7e380d4e3a5efac7424a1
+83bd2adcfd45d84a0495cb2cf6f647ed38042d03ebc57cc7fb01c9b6eb809815
+aaff355c0048fd1456bb3b666d69b61e62e5f8764d8fb8cbe568ffab07b2b20a
+dd75e6e8afc57cdd6a368b35903177c16db14c14f861eb4318e158caa7615743
+c847e066be616524248056f1f1323cb75f976f3c45db703df818eb011499049a
+3795c81add7bdb60c942fc7dc78cc0644a6cbffee86744af3127d021b0db7cad
+29745fed8dcc7c814da5085ffb6537dc87b2b10c0d739ec72037060d77d03479
+80b7c1c66f33594dcb8fab64cb6a7595bf4321074e177b613d8ff0d6b3165734
+ef7ba807198398cbfa82b5c22268966bf1c6efcb763bda2771370fe353d5f74e
+39b97d0007c8f07cd4ec408d5fd8e0b91898da51c5995f1bf401201f3d990741
+a413a1873c0630b02461dbe7eefc315718b935819245a3e4ed6f7245205b0260
+2ba4e3fbd10e9e9a6d795b7e5faeeb4bb327f8e2bf6288afa17b33febc30da26
+339646fde4bc2052156bb105b6495ebb1f109772fc0cf4e74d04fc343e59455d
+f66e127b01af76306f1eefb66cdde33513c9cd8cfde5191a30a611fc740c7778
+a8ec6ed6ab7294afdc13c647b5beb50d055f1713d94f8f4c34198de9e7d69140
+4a6ac35aee04add4622ee052dc6e5ac069ca16f44699d6dd1135b167daef2640
+6354b17dd0065b0667167a32152e0a1bfabcc906d6a83f57322b6aaeb665739b
+0a129704c95439feae80813d4e016707e58501284f589bcfefb24f2d4f4a64f6
+8f498156b4b33739c6d709353b297803d5e2be86d22a8537ecd6f22eae4a9a08
+8675186ed6b6f04bf4ebf656f62c93b707b488177b56c671218c0ecfc7c331e5
+ebf4d7d19e24e3874722d2ec41850586bed01c51501cdafb58f62da3bacf1e5f
+fbe991ae47504f4cff536cd07c27ea98c7874d0332184e8e9ec489a8b88cf4da
+3aab2cbf5fce08fce1aac46f7be72bfd13b2e68247c6dbc126e94b6c223b65b8
+e10e023b083a73ba7116097cc30b32619e0337e0234231d125c615edab87a076
+11dedf515e1015422838455580e1ee2332f79b66e0b187045fc80b02a42b5864
+ca8f5ec65bcd6740cac58c1bef50462dab2b0148c1f38ece89fbdc8fa08aa8c2
+6bb7f7c2760f88e4b56e3ead341f6696544fc6735122c136ebab68afa8dadb4f
+54c6fc0166bf56990d9b28d0fa8d843f1c971029a46665c1e5b7bcd2b9d794cd
+ef19e52d4a154e0d537d14c4bf0feb4ee4f289cae0c86cc82fbac3b43102c28c
+b5f6a82948da4c51fcf85d95a91a5a60806815f04c7175f695e1af5270f961ca
+bf7ca7376887489e598d4014b2bcfb9f4a745a14f577ccb2f402be279fda4d02
+3f464004998682716083496d4112ed9ec7351d81bd4729511a96f001d119e714
+7641307abad87eef8cb42bb7ddd62f61eee2a3944a834039a5456d3324c53c4e
+7b44016242baa784eba34a2ca7556dedad425bdefc04f11aab77263b508ac9b4
+4f372af561266420f852b0f288c5a8d08cb682b47bcd0f8c738370c48ff9dc51
+17e8551a946b9b0d0b441e8353e15214cb669f5a696b50a23a4092b65d3fdec6
+f174fd8b2d51b22cefac51cd5fc7653b3fd367dbf0aad2f94172a50a1bc5c236
+103d08e2aca1b465cdbd6441a8b9f05b2e998d330ae453cb4d2ecebd069ea9ea
+10a0652dc518141e6bf674282bd3ac1aaf7361513a945163bbfd249effeba765
+38c41e708a6e765fe6b7fde08d192ae7e1e4a12e61ced834f873f89bd13934db
+828fe9925e412e687fd5b99ce4f0fadd5d20a2d5d2c083b98d7e85655df6dcb7
+f488d3bafc7e1cf88a8ddac3e33b3f38c1da0bca42f052f379bf663f7ddd047f
+227dc4461713cf88d27780480ef4154c1cda2c40ef944a86aa053375d9504107
+4149481e9f6d061b6cf70d533ea7f4df16075c1cb2d84fe52ea967b85adee81d
+976e3b5cc75fa41286cefd804fd3b471c74e973e3961346a3b6473390660492f
+1e602e7f27c38f282c7d398013f133812a6e8d2d20c45f8c31c8dd26e53ac22c
+5581ab149190010f6e3213f77da2662dbe1919d79a23aa976d23183074171102
+9dc5311c96a7dcd36bf4e60780ba68f44292b23c6301bfe39defe3f420306b6f
+c94a224e43638aaa1d3ddcaf6d4976dc33831d413f691ad7867feebf53ff31e9
+05a6f084f525873c6e1a7eaf956f8c1f9728f88303c6cb14b6f79fd730febac7
+1552d5a8e5f4b8e0bcb10efbf8a49ca6f470ef4ba8fc2b5b05d0e2077ff12202
+bf480f21c47ef74592b6f8a25ed1bdec3a87a3afca117fd40ce5fd5700cc0b62
+5f29828d7dc95812d142e1967b748961b343cb96408146a6aee63d8cb82e0984
+1073d3085e5db40b230814bf4a2fe816891d33003db3cd0a585c1dd306042423
+a212af55353f1e915436137cb6ef0823dc1f14746d0de7425e036cfb6722a890
+3e5260dec6a7dafd44c995c215d6a917075a3c8220884be62c0660b833b658a8
+74f9af8d280d67bcaa96429d4882427c81b38570c66d0e309628d8d7cc28c442
+2511210838a12cde39af4eca0423e5b040c52124b5c549774e207401ffe8a771
+08192fcbbfb5a127c24241c8d3e0bd8a70456ae850725fc1dc1b2d5c2d1acb49
+71fcdcd33e027a562c0ab202e73bb3e701d4f675fc4e301a710bfcef00da6ff0
+926112665593322dfac72485b8eddfaf4cc449154c53c4403eed5332f38c7498
+9851e9e056c6b8cf9fb7079b48420f04cd54cc12d505f13f111df1a51672447a
+71defa3128cbd7ccd50afa388170bc80162228c1ab8b8b98ab9bcfb0d49686f9
+2fef95de610905562e92665992fc6fc31c2a0e9ec7fc06bd22addc740e02bb02
+d08cff9cdd111cf41b91a7e4d8f38d88d8cbb7dcd4120fbb76f69a24a773f32e
+a1f63f906a92a16861db77523c55cfbfee0e96b00e1f93dd20d154343ee774e0
+1fdb1be91c43a7d690a557dcd5f75fc2bb0e3e18286dd8e1e23412fd61fd79ad
+602259e90d0e5a2894a29eda1c096289766160e356931015f1ccda0a56f3f2a9
+d9c2e495157514e1b03768d8190619e0bd113afcf91f67de3ec80a09281107b6
+b2af3e22a970ddc8a28c61140b102d990392c923a50e55971f6751287aef256e
+f1c4540af2b1d7754b16fec88e3ecaedb6b713ba39fe835b27cd7fc8c132a740
+46702a4b9737dc04b12310ba7f30ad94de90c4cc2c0e174c5a119d14ba6488a6
+a1134a68ec0ea009c0caef6c0b1edbfcac670162a1b3235757ad4dc4b0ef153e
+a4c396a809c8ad3945607752dc6ecd49348d9100c3029da91cdd15852d80cff2
+dddce3e7e2818294a4113ce0bb2e4d930b1069156c346c2eef80e8441084fa66
+7ed142e5f9414f02a362992ad52055fb82d627dd481405966836efb51f858f8b
+6104fe5d335427b528c2a83512fc056de5f0590945e84d9da75012e2382cc99d
+cd957c861f43582d982411c74d62c0ba1320d0e069cdf1ad193a4afdae8b0b3e
+8bc1179ed8b02e0acf0591edd39971dc36698a884f750694b57bcd28ffa1848b
+621f22b1a5c9b85498caa13e82aa9573695d3e1ddd2c408123494d46e671b50f
+5314e92a073fc52522f6492de090208493f1581017e05d7f8bcda9cd8cb62cf8
+2cfa57390a3e27ea3a15014a00c9b6189bc82996f2823b1f7b6c932ae9d44cd2
+351af96c918294e743f054f1cd71f1287e7d0dd8908e573c3113734c091cd343
+95647aea885cd9df1211028b13f99591f653f69df2bc629297ec1c273ff6c446
+a7d6bc6579118915705baad148ce2d8d0f032ab0ce4608d3f2c19ec255df6b86
+c88700d11dfc2953b60391c5c4eafab97fb6fc87c467f25db92c05ac2aec8ab9
+f4f624b08f92522739d920f7e07eb4d32807990a1f03dd323c5b5d2b4b5af128
+c18cf5270e0cfbb3744fbe8041d7ffebbaea8bbc7b851f6e34e7abe48cfa8b72
+00ba0034a95bec468ca2f49f7f34fb3a441936128fe1d842380aee63867b2ec7
+38963bd51781d14bb5971e156e6fb64b2ac5fd5fb3c1c1569a6081ba0f98d8c7
+7736a52aa2313dece258c0b9e2cc3c8547fcdf0ecaf7c704cb646eb9b00b0d29
+4ff444b03c4d041667206d44adf45890b1672eed8cd460c32b8cb37e21be14c6
+0852be32de9002618df604770ffadf5cfc05857966bccb01edd59906bbf4faca
+52a14c2e1f0857cf039a4a82474f03dafd54b8c163882ba77436820fec657082
+45bb02be9e7fcad3cc9ffbd119ab53982acb49e7c74fa8fcd92b2b0dee92eb87
+fc7f8bde3000b42656b338078c2ae8f58eb616fdf447c06ca70048b2f5ae25c5
+123655dd45b546277bd35b63a1308ca47cf4d8917c4bbde2b8a07855a0628c7e
+eb5bef472819729b3878168096a81bf7dcdd59df236fabfcb0c95c86ad80800f
+c2f69fc835544ea36403ff2cf0f3bc8127bc2eb5abacaee987b76e202c8de7d9
+954fdfb49cad1a5e9dd6e5b943d9b3477acb63cc884168cdda0961b483c20b73
+2f7012e980d1c6a353134e2ab65b8f93b6c6929ba78287c215daf08f480b786c
+45b5ee6df0b2b030fa7a27e39268c1526759aa0e095606d2de48f0e7ead25596
+09eae59437b3621716a4892a241d8fa957bb65db783ce120cbd1e6bd83b8a26f
+579f062654e17f8e35159212f7b1b7788934279c0315b9a603b3008cfb1393d0
+edd53889e22df7108a6cb6e2732f9edb9ea792e00469f192a288ca6fe995c7f9
+e3b321efa32329d3b1f0d467e49aa584000810c5b547149295a7676ff1e4cd89
+d8d6da7e117e8cfd0877e8d7ad0869b15e69da45fe038ba3d62ae756bec8802e
+e27ead377a333d62377718b523658dce2f8669a67a70585fc50549b52ec5289d
+22dd47a8892152cde34423e671a3820089f4f3b4007759dd686bbce52c7c9fde
+61585a41e1e74d33d72e052708b3163d62ca2e89fd981b595b3840c94afc7b5a
+3148be452208a894dab2becf9820d8e7769ec43d9635aa867edc4453e7d03b4d
+f0e4b89b4b6a3297cfbcdb97b158e46503f38c9543c49d81a41665228c07c28c
+33965bb4fbdf8fe213bba119605114bf07c4de83aca1eeaddea3345caec6ef41
+ea7bc93bab21a2948eb3808f467090058fd08f00510ff600658fa971d1f66e54
+7ad1965a174786e4d92e059fb6769e965b1147f4048648fe82ec51b13bc56c71
+229b19829abcc1ffa85a051c413245bacfe48d720fbf8d4496c05d7b5f45572e
+6190fdc37301f6cbdc44031c1e97cf47f9c5b9ebec8f79c8bf8157c5fd861ca6
+4a5d258d12a8e48f7c9ed901fdc289b53641d5cd5e63310a8be495b1104a96ea
+664da883db4d8b8dd8ab1dfcdc10d771d37562edaa793cc9019389d060709efd
+18d78bcb6d3719a0ee0a5316f9f40fa503d9707a6d6abd1cced61dbfccb06be6
+714985a9ca63809cbba77857712c2381c569207cf9db2699dc0aa6b7f5565043
+816bdeb09e29be611cc336c31e84302245c9ed37231a7b92d711742c640d06ce
+e484024b5335dbd0387548b2b71655e83cad37abc1496e1cf9d8fe792fde96a3
+bb684ca9fed1bd75d9bf50b06d58bc2929de047f96f003302fa2872ad8be3e0b
+68873f143abdfa4a73702f481ec4e32018581f19ff862c93fdba310f5914a242
+30a24df9f56ee75d274c9d5cdeeecd61618c14a3c2d36ae5167b04312e7a7797
+0267e2df6837256f0cad7af1b5e29386fb384a8ec2a3a116e026bd2e48fdbc5a
+90d503e2f3c5919c715724b234b71349ccc9dbee51808884d39a8b1ffa448488
+504b67e245eb403221348735723197708a564298581b5d75384b13ba92a7e25a
+5c40792e86d3401f9fab4a41762d95982365f6408f9683877402ce4e98e4a00c
+c3178e104c0857719ac4cfd53d07c219eb60e66d463708e3ed65cb4280285c2e
+723673836995e18aef5a9a503d7e89aa2ce4e17c33a1214df21ef551d68c3066
+64583dfd9664e09acecee772d6a70a23f3840ecdf6a671dd7870e1fa9f261e79
+6048168a73a20c53a971656d47a0ecdf7e7c5cad76073335ccdc14477746c675
+e2628951ee83c402f7a0b581af8ee6a594923a375d18c98a2dd01d3d5eb8f71e
+657bd49e5b0c4f4cc0a62ffbe13f10cbca8bc2b631d21b43827a5c3ee7548ed2
+baee96c94af70cadccd81dd3a48c7190b0630a0c7ce48249b91d92a1bfd06ba3
+123f97326e31c195fd1771fe71abf66fe3fcea8b285d6a1d4cb55b9de4699b0e
+c55bf2f5d6c245f41cd933a4ce3eda258cb90e401c6075a02757e36717c8f547
+4b4e9f3a8c2d172b5d380dbb9e5cf8dbbd8b2d8cfd014a5011b9069cc00b7dba
+21b4d17f44a55ef1e5c49ae01d91ab509c3313f2b0a8461194bb89fec131a396
+4e58fceb5677baa816bfb9e15a5121b3f0649562f920ddc302a949268741a677
+4e8c2fcd11288f6705250232d4de4caa56a8e8219dcb805ed08722a674d94491
+ee9f530d2eb8e07173f86912f0e585e0051cdb190e4d0040f9da037f29890068
+4e4d5577f67a9359e6f86b690ed5e6d3e772f7817edc2867dcd6d11dbd59b9ed
+904a57f2c5dbaedc298dc4f2bffd42a93e77925e00b4d74460c68196f0326f39
+f1ba9059cdee88c8a5377b623fe791c7eaaaf6ee2454ec3f681bf42600a97acf
+bba69df07157f26b64a642849a3da66f0e458fa885255699024771c0e448e620
+c3a2607b32cedb6e0b72a5a5dddb10981c3cefe08ed3921c4d0ff87c18cea04c
+16bde8148e530979c140973c68ae56da99048448c6041f1e15d162163c46f7f0
+5f9bd3ec2c7b2012d6f3c29eb76abfb56641774cf6b6bf17ba63b7dc94efd7f0
+c5412d1448ae3a093cce169908469e6ce06971d89a547f25d5479fbce1b89278
+2440339e904500a9446ed22e000a9213858c097896a83f008b180d942e397804
+a669da5d731b2b29c3af90d009e4d5fc9885e9a446a8e4c7d1e4f2e64629d829
+444da50342173a10deee11630aa33d865c532f39cad6326674ec49a348a02da9
+966cf4f77692ec51253f5dbc8650e28992673c97a8bb57ae2b2a1331644d3de2
+0236f8cf1c3f071fa6bb2bbda641410c39585a57895988e5d653daa8558d797a
+87d8ad98fdd07477a82c09076c6d5b8c2b6da0c3f4b916149bfae6ab3e47b977
+9c00f9e01cefe0e56a3dc2820a78bac7ce577e4e3e9b30724ca1ae980bf5320a
+41f38e2fea835dd1b7f8f252ec7052793b34314e0faa27ab4edc58ce25feaef4
+011cda00461160b772ac3dbb072cb3bb02b27f33ac2b9873a8131954a4abcffe
+1ff1b3acbb237877ef0faebe4ba87e886a217d62a86fff64119cb68a54ea16e4
+6042eb0ac3194be279f6662a3bc1f6d46aad795274df8c5810832a8a7b442a4b
+acdef42384bd3c25765321fa46f000e330d01a4d63cd71f11bf2cccecbc0916d
+3a78816f519b6ab5e58cae3543236eea83678c61c3bf8da0db91f4f9553565a1
+ef7cada3a2eaf42bdad0fb7702dbf84ccaee6e01b97b2acec60a47cf19f42871
+c4255eae175c0265ce822ba301bbf495104a995014cd9456e73d2ec34d417402
+38c08c6c296cca1ca3b6591e3bb34ed9aa0061b1309444c29f544bdc8b22083e
+f8a1975c3b20d6d5ca9c351c26334eea4dd8b8a94f33e6195c1a80d6e37bd2e8
+4122ec755d4c52614af9dd42d9741e365b019654bfadfc69a90e2ef9b84957fc
+6d546f6d8d8f75f7dcf53ac4f597cb302ff298fd67a72ec5fd2d2e480e239060
+d6c242f3101f449fb1c394a0e5aab4e2c045506d3839f811d173c0564fcd1657
+f668ba835451a95dee465ae2ea790afe0e9538a6da482e3c9dfd134366ebe8ef
+d446d7daa9c05da01815985a2a5f2c3ec1f0a7a929717d234e3e2eb26a5552dc
+3577e7aa7bb1ca1ca6c3b36503d08f079892a2e797a54def5681eee1f9a3577c
+40e7ca2fc5dea406e54890bab23732e6384a647926d8e072de0bd636f1c75e51
+952c8aa250389079d6b04490e372b4218613ab658ac1c77ea9a1ab429277b778
+6ea8dde78ffb71bc0bc4dfd9ff7e1a6b2ef19a12276f3ce7d4a5b281401af046
+f8b6c6548bda69192ca355cf43bf1175b033f01e1bf61ef403053a79288671ba
+85dfda331ad1fdcd203e875047c740c42bb12304c4a18a407bc99a2c13a87c60
+645b158b0513a90049845a03cf574e1496acd0f2fb0f914e011b103533e61aa0
+1ac25bcfadd456f33b64000440c1e935fc1681e9fb6685786b72c909055e4f40
+b70afc227ac5f2dc1e3e58363547223cd835d369351f36cf698120403c53e4e5
+a16b756600bf0a37d1965a2ea457df75e07f3021023072b12aa7f3d9e8dd010b
+2dfa92b24d7375b7ba1290060b5b5c9f1537dd0969cf9a7701fda9f9a96359c6
+ccc15f736f5d2479824e8488f6aba11b3d25187a9337effa41ae611da4e35763
+f60ab26fc896697bddca64b70300f09a104d041ea41c4dff6f33861b8ce4f45d
+98fe08b1626cd9e60a4453597e2fc3287d9790296da5087c25ae7b85be898583
+ba29d25a59c776bc93fef8f3059e77827449056c2e55a4772385354bfdd19284
+7a02b8d0d5ab6b34ad9968d68c1641a4ccb2c03e5253f1aac444c1afcca8c94b
+934806c61442f810a7c53f06db5b23d0b017a72afb5be70287eedd1e0289ad87
+43965dae9770a18faa7944cad4c638eb8d918558aec3a0a695765172d5c834c2
+13e680fc79d9566fc3b47601c706c2609892dc9d1650465703ee6c2f7eeadf92
+809cd4831e47a0188c633102019b6425cb76403f967af48976a2737504060649
+954c118f616bae1de9cb94ed75f6a3dff1ce4954dd298ce8b5007d1e182a7e6c
+e0c6873f582d21cbc2f0638d4a7b2679ae58bb54c7f8a1bc1c3e9eafbbe909c6
+40ac2bba74aeed432242720e473163091cf6fbfec4d44095434600789d7d795b
+e5ae5500584e8e637daf9a7ac458532f3e44320be7ce7aa1f106b6420e67a96f
+459c23b7b02404c1b7620742766142545551de23fa8ca655c1579fb2c0a10e93
+1c220e584e9fc86eca3aab882f3881c530ff92dae83340193d5aa93bc857671d
+ca3fb1f03534ef6ab4cab64e29c02a5c37e19d1274618211f2e7493a4a7045b5
+3d39adcc8de68bc7f2265914218d2d69abd7c49bc9237535b4d364cd67bbbe35
+f13f71b88e83e3bd6f09caf327c629cbc2b8ee728daa1626795c2995c7919a2a
+e966108bc76de252e4df625e95338332b6aba850f0fe97fdf247e85db1fa4a8f
+3d307543c80426061ff86b4ef8d26997baaed5679cf430bc88ce20e31cf510e6
+987be01e58a170bfc8a6259f113cdd3541fac41467a862acb3de36fb60f72de3
+e0cb8a02e17ea8cd2eea27aba315fc34795ab06dcabf5ffd1b83e2308b2a21c7
+72cb356f98a7d2d0f0cfcf69d672aaf7fbe6198344aadd2ec28f593c7332b0f6
+1809ef49d7c19bcb6c065ec2d890d4c8a172e061f47ef2cc392e974d1874278e
+250453cfbbe55c77a0715475a7cb383a287cb83f5a14fd027c332f79a09cfcfb
+2c7546c8aa601d925ef6b72a2f1ac1df26505bcc8229a4aa0c7167b9d7c56508
+901efdc77754cdc396482d17a6e9fcd200a0cf35f67948d11beec6d38794a826
+7c48da337fd191705e26eed2981df5ac1e8f4bb23952b9eb6f92cef4139e1884
+bff9f37257cced07790b6ec333d66a6800d8c8befb1d6362786b3ed00ccd233d
+80e8c4fbec37dc7dcde3e5ff2b8a81596dd10b714d9697419ecc542a661be66e
+a5082e50ab273dbae50d60ba68273be1adc6f750ba2115af58ce22e8e10443e7
+2048d5b248294cf81e9e1995dffa731795817c556133b714d4970b379b310698
+d4f690f2d799153b8890e74dfb72a5a50c476be90cd65157b82fe6b42c5f1be4
+3d5cb0c00ccc62f270ba879311467dcfe9fb62aec22c5d4872f6b02b8a8442d9
+d309722c4d30047232cd0c97049695a2cd80478ef3845f6d1005f36730c41429
+0576753e869c83d458f0dc3f42f68264e020270889c63218df8e1a8ee73488f6
+49ff10926847ac73be9e89879157cf78af203a66ac491eb93ba365b8891dbb8a
+bf8a4403b36e0334beea6749cc202ab18dcd07028f2f6b29b0eb5865a3748aaa
+c60575212f532b4cd4c9dfdf00aedf6cfe6703ec89b63950a1011785f6dc9c78
+d09280ba67eb5a78f9bfb797add8d7b705ac5ac61a16fbdedfc1ae720f9a4ca3
+4cd9828c287b680c24f11bad00b644beb9f6196dbaec8ba231de2d065bd7f42c
+d731e09725adc7213bb1f5e675bc11aadaa56da37f62ede33a390d43f42a795a
+0a5df034adca9ecd0e3132453aba99fdf4ae8cc2a7600e4aa10db1db113674f5
+10b2427b1dcb424623a259cca49ed14c97f0613edeb93f459fe9b9cb75b4f308
+15f5c65f839208a6f25652c8f63196ebc83c081c4739a692bf14ee8ee54ec4ce
+72a35bf84d5f4e6c0015a4443e7f799b7bc6828e30a97687a0e5b8b722fc69ab
+5baa477d07bbe2ef09a4fb7f05db2e02923bc8582f27693a5ce2b473ca02a815
+9c0789ef683ad4c7d8708dd28c408a21e358fa2c30cc598bc36c5651917da2e0
+593e472b57d70c0d6854fa5422e8b3825a9e39b0a54b0634190c2aebff4913a9
+7534bc70e3e9b3458f272d24087e74a538764b43168e74108da00351ea558e3c
+34bc8bdac1e1bc9061a688723bd54f0bd4ed7c165f32dec016b056b7cc566a4b
+1c873e36ffdb7e29fb088782ce60524e2d75cacd23fc274e307dd1d2b07558a2
+7f64c4742eb3ba451c8ef3b6591d193d4a55a42fddc17f12ec3aaf51de8056d2
+80f09997bb31a07d6112a855dbf7062218dbe89a6844a006f23899f98624ff6c
+6c364bf079c4b3c2f92cd79534f89925cfc55016218192a98cdf6cf4fe1b5fc6
+9540ea49643891db45a7a71cbb85f41035f507dbc6b161f7f1eada70e7905aea
+0b1983f7909d5575e3e7e8095bf0f4978217b23f329f057f1152ffc14b9bad8e
+cd6fff8598542a6adabc992ab3563f424d108d95abffe9977f09630ef4c6ba01
+90828d34098bb40bd230685153e7282671478138e578f608873d64b00eaf1339
+6dbfa02de3fa983735d90fe2d0cf7d28bd9f35a475a6f55017124a58988d00c8
+713745c7c999ea746bc969d31aa62eb0d722cab1e57cb0ee97cb969d1c3c1b41
+cced2d7ad87df16215f908e02a358b725eb5b234fd620f7f54d70f764eafe8a5
+de65e3e171c885eb012b3db1707c76724b62bb0c1a51be0b67179f96a46ea538
+17bd480e774c31fa131141e017ecd4c1864f62636127a4c7e9bdd05679462314
+042f0858441e3d261ded65833d91d80a6ce303de41060312e60527a3dd45404f
+55c93a9535c3a393f1748609b002be9511d87af20c7bb2c4cfffe466df21b100
+e20172c8215855aec00e3680c7efc58128a95820085cbe598b9603c70638e128
+bf0c2ab7113eb95b62fd40fef845b1eb5414737cd94ac65970a4d6dc2726d2ed
+2ba7567155154ad30301ee997a3bd52738dd17030860d3a479426c652cdc22b6
+2c70f185d812543f836c7d0253ebf8788cd6148ffc163699f72a85bd2600cd2a
+72104cfae46c836939c5a8a6584a58022d0ffbbc1d1f035d959fe58013611cd5
+be08db068630e5a4c5fe915e20e5652bcdcc44f0ce93221ae09c55fd565c2523
+79189a85ccb93f2d6162402f2209df604f140799d7e0b34639149eb5e54c33c4
+05389d46c775ef19519f1ad4a6f63a62435c090d8e3bdcd6423f5b7dab32d263
+165c93dbfb1af542af08aa5ea25595b3d8ef053e19a16e5a1b13f437fcd2586e
+4534aafbbfe1e76906e832c6d1d47a3c5c868e65afa7af2e21e54ce233170b0d
+e44bf290c512ff3acc84f0e492c73a0d827d701bc9766abc4c08d12fee986020
+d06762d4a0163a976031bb13e7e86fdd3101b8d5155046ad9d82fc26496661d8
+8cc516350229461d6c11114c35a0d13a1c6ffcb96fdd56105d67bc5666512de9
+6aa15713998ad53064b7023f64dcbec8ecfee625ef76f5a024c92330585cd17f
+1c3f82bbcb509d4d31b535cd5853d803ab7cf1862e8a87cc3d6557aeb7567ac7
+9ee04bfc8a445169afdd1b1375274fbb78ce431ffb9c6fd61a99a0bf1b41c19e
+1ccd5dff38a45647d51fb014099c74ebb11c5a07050078507d45a1ff1fa2e178
+567f745448157bad850f1fdcf9a37afc8968120892ebc0f85f5dfce9116a7108
+b5cc34c5d85a91a65d18b9093fd27e9778ce26bd93644e4c20ffad131503b509
+850f4fb260edb6d8cd0bdcca4beb8a25ec1fa44eb5b31e9213d11d0a42d1aac0
+650e1ad3880d59d2256a2720081fa2a7482e3bf1e4f4afa59ec16b810c0c1cf3
+2cfc726205b285ad7cb21d7a59fbe526a844a5d75ab5d61a801701c2984b48f4
+ff020607f2ce9b468df2f74e04812262943d8932d2e3c0478afd42a8a04eb7eb
+44f9dd70b1da28153f4f366df0fd25e89f942aef374fb9bd31d9575803c03e57
+50e5e75975f108b6c7bb770d824e5eb88d67dac4526d6e4df1c6fca0a5fce033
+84b06624e906912a26412b36cb177fa2dd18279591a0db289d18b1754461b97d
+076631b5d598cb2741652bcbcb9804e1d01a8502b03739981b42e4754e21287f
+ae2adecc9ab60a3ba753bf94d1b586c0f689309cddf12131eb442180c1c25b75
+05bae86b985d6975d1048d3da9ccac02bac3e6d8a2748082e252167809ec7b81
+7cd72fd364f27f9aa276cd5e70edf56cd5a0b9cd00b815411c766b67dfa424a5
+1d9a9c7e27cf57e513965379e180f1bb94b174bf827dcbdc89ffef77f5e2ffe3
+9f1dafdb747f003723dfdfc91af3c6c23619237e4f661de027809a510242ea85
+d9b93733b9276e37c01da481bf86fd040b2a21df21b3aebd8cb974925ac339cc
+110b604531af29325b8d844305196e5e1366edbd5e6196ddfcfafb27e28b1adb
+6c2a3fd16d904a1ffb4a998a4614cacc85157d8463f7cda5363f5f03b9141bbf
+4788f8e92de9680f3fdea66415c9ce8295c8a1276fbb9b57e97f899caa8ccd60
+275e20ef7c0ea888cd60bae018ab8d59d4b9b0e9a19d71cdaa96392893a7284f
+6d94291130edefb156dd9a7d172c89b881aa5dd8d1a4d9177640bcc8d308a801
+547a4fe7b75f04f99d529c854dc059641c7652132291dcd0deca5ae1044880d0
+4a82600f293dbce78d2dd503a4b20ea39f25633c216021b78cdeedf51fd73c9f
+ff3b4e7b637e90de3838f8aa7e1703cbb6b5c74e1f0ef474c9ecfedd79d0b8a6
+1559407b87435fad2ca5ec36b170391fea7cde5824286f66bb0f2b14431d2481
+0fe6b2a6905c2ef0908bdd4b05a16c01c00b2eae7bf294251633e806ba08d21c
+3e1823e8c0e9b6ff171c19f2d4b27f070389606d90ddbe0303133f565644c8ec
+d9dac4daeeeb490b2dd2ffecc57d69daf3fd4ac8666adc318c245e14f5e67d44
+d8941a967696da623d22c077d7484bb1c9029bcf536aa060626df3d5593bd4b7
+af56296934d03d03c682426f00542ad5d3f9f8631e97eba46416979b6b2fd5f4
+4f19b0cc18d28d551fec3b3fc5440f3dd5a0d79fff8df917dd515f68eea2ed9b
+73fe68e395c7fa2cfcbbffbb4a7cef13b875b96f5e805dd45a7c8d3e2eaae5a1
+34e00e26988281b159cd8f9d31a2f15246efd8d927e881a173487df644cc7656
+b847682c04542f35de33a7d638b5a2187a74f036ac693e4ad95142e8fd852899
+b8ffd2405f5d79af6fa919f2613c2f6f4fb729f5d6c3fc8ae921eb23a0827667
+11432b8e7ea4c6c823b57acb073fc00ae93223a0f893bb8e3ddcde28de69cec6
+5c0ab65d48799ab8d1c269ce7ce346d40256992947b609cf9d3da1d758ceb593
+1d6f05acddafc290e2b59a93bd94507be8a4d4d675dccca80891ee13846be24b
+8cc4f7052c5de700c953319b29aa4b87ac3c85377ad31a14584ed6970928e850
+5819d527dfdb85e330a5cd47a60932ac93b359bc0921ca40703f8abea6ad6d63
+83f8d2660f8c8571c5a13d889437d12d6a41552d9c4afcdf2fbc2bde8055d681
+3b7be320d48247a34cbe1b84791d2c8e9c417777bab9869cafea2c522c59b12f
+23cd59779cb58f047e0b81fe4d42d1f4433dae0ad691b9c26e8f82b5ab867ea6
+fcb2d7f2c0718610130ecb7ce93d1ec2eddd2cedf1458412dfed5f7f067c473e
+aa0235862bac6f8c8408c0f61d978312fc1a671c292620bc863537d286a60a22
+03a2d5dbbb772b365b925b9a616ec398359425e3b935c7e833e572ca8ecddf98
+121a7bbbef3f8e262d4c08cab12d97f15776e7e202f493e2e12b87f2e421e5b7
+6e3db0ebae2f1e62239072e98391135f5ce4141cea6d7e7a19ec1236b81d1ea8
+0fde0b0f2fb4aad1fdde1a004ac235c896a8a5cb15a4806401727e7dbd148ec5
+8bbdadd754e31592c37a7207a82989e5405a7ac58067f07932acadb77de65c56
+92caff85e5c183181cfde88b045e596cfbca6b4e0d37e6b881bd3f081ccc4a04
+a6d576b41e277b1eccff69feeb5253bbd9a8e11b554e496a16f714ad3bd963f6
+b8425f07ca5de122de2717d09917f19ec3238fe557c44782caca0e4e52f38b5f
+7c207b180b498a8fa04e66f6abee7d7e79fae9e54cf566803a885db7488506e3
+8727b5bd772198624051c64da3945da5e904788ec969dbb890f264e54e2c661a
+9574347d34634550d5b1a1355f35140adbc07be4d5ca5c791cc8cc01c4b26875
+26d8937cfea55684de8165b792f7740044e50ba4c19677a30bd453419a7b8d46
+437f44d206dd8a871269c903d78bfaa35c4d1a054d0935afc44f048da40cdfdb
+5c15cfe9c8f19cf87cb93a6594cd394c7f64fe21cb599985a69836ddbaf80410
+4848eb7252b523563b1c24e57d4966d1eb261e4c9c5ecdb8547e936506175440
+381020593bd8cbce659a7a345a4c05a9790df98ef2bddc9fe45f6968c49373d2
+d55cbdb4de22c4f7d53f8263843f8690baa842f6f1bc7cc1545f3e0a169633a4
+01e5656f08a48b17a69d7babea61d476ec62fe1395d1c01988926fb0e69326da
+c69b5e5347167c0541f226d4d2d8ed2b4828ba74f3939a4b77dea31d881c7b70
+18e1c1b646c10a935807e6d3a9e6674aee575023f941a098d2df22af754b5296
+360777bd1a5b1a8fbb29ec9ad201dbe0170850ecde219ad290af01017f26b777
+56c14a5d3ebe7ab8560ddf0140924131cb0a693433437d4a17f33bb22e92503a
+337de079b27ceea348d9218278870e1404f7733200e31de980e38ffc55268921
+200bb4a6bf116ffc94e5d68015c7b0957254589bbcfd6b0d7318e1983d18bb22
+982e598484b2c7c7ca5b08482cc0f23f74d1ca4baa5bc61d1fd9094b860edffc
+44c5bba1d314b46d6c45a5a1ad97706893733a9eaab22bec79f4437a5917beed
+f24271040da4386867c830621ffd6744ddfb078caf5cf59e7e7fa456eac5abba
+dfea921d226d36cc6d03c73716badab60c4021630643824a81ae62a731d7d647
+69749ec4f34068330b11d2f9664f56ab8b8a91864a3a2d2680717a6f7ebf8785
+a18fababe5abf9b1e759573dad33c4a968505e887c7b0f9bdf65db75bc04c8e3
+f397740b94b07dd154a5523b19b87879f8f0466d8fc9f3acfee32fba47661417
+a6a97aa66eb83ab06a8347a76890bc83e6bbb6de46f007e7ddf4c316e904244c
+214382939b4507a69a29fa3a60ad96e420d6873f0f28c8283fe85c59f9a5ec11
+0001740aad1de7f7bb72ab2b699492b5b6855c1bb78562caf497ef78ce8ac3fe
+b5f05639785318ce1b7e9ece0f28d560dbda6d134ba8222d94fc98e1910eabb5
+b8ac30c4d0f2574f2e83f309293c3530ffa08d140c4b568e1868c7b5b3edc79d
+716a5597bc45616e2f11b4caa3e248fb96d9e84afc205280fcd76842b913fdb0
+d22a1c741274af30fe94c29a2c690e4e32292220e58e31e815000aadd624fee2
+4d72625d57ebd75a24ead23e75889bf3fb35738516b3b77a6fddb6590ce59804
+6bc39e4e9ae32ecdccb0bf9490ce9c8cd23e997a63bbef088fdedfd9ea40f21c
+4d89f264329c674689a8536d83e395b5c17e6714cb48fc72a4879e525a954048
+595ef80d4b03d92f12664b841e00220e3a2c7abd65457f95d1d6b53ac96e545c
+25b8ac77d8549bf343fbd27700f1d5b344e965aed917c82211f723410c8260e5
+ec603111b03673922f118f1b41f1b436dc347909355467db3bf0dee4a7844c27
+5b15c9ffcdcf334ccde89fb61c58e42848582bd44e2833619b0ad907fdfaa8b6
+97a09f114e9e204db7e2319a41a5866eccc8665ef6d94a9abaf54a71760bcae3
+f253f7079fde5e138dfa210368707b963f0c4ad772d30e1b56b969d911192482
+e1c3484f52bcc8751b4c4a20430316d90adb8114ac02e8fa845dc66978cdf313
+4f1c58be8a9c2990a1dbf0c1151996d1eed8dbc6f35d4a0a5f44c22515575d32
+6772a70d00dd938360ef9940beec9ac9a9aa2143cc02c3c030fc6288ae783bf9
+e6c6a04cc0549e2e0108807b31acc0def31458a8b32096d5ec0ea1e9871db2b6
+35dba89e4b0cb6b965c48be61d891a0faeedc6ce4ff743bf826f524d4f313414
+9368b2da65c9c14546084d955e937ac3ebeb5823d90670e2480b29402d0db8c4
+31e12164912a5214b439d8546711a39648e652d68cddb4c4e28aec95cacdf5d5
+73a7141e0963422cd82cb1d4568ff99cd8cfc938614f13a5948add5d8914dc57
+ac6b916f4fc5fa6b0a42f35e7c2120b623c61a6b95e35ee2c6040654623d2e78
+f2009bc55a897e0373ec35427787370450bf03ee788482396b09dbe8e2ca8d19
+dbbd3d71115978044e3568b63fe15d56cc5b08767f5a72861b85262e8b194db3
+5e2bd58275c19de4a46f45b3ac0c297a9e1d8238cacdad6b8728fc1c3c7c980e
+49e05424f7cb02991dafd70e277388591e3e34923415c383d62cb366dec6f2f5
+da863baefbece4000fc620227cd325071aef5e62dc3e4bfdbd4915c03e686d17
+371c75ae25ac8cbc6683a378f9f617d19725057d84531c89cb0bb686bf9e66e4
+844fc8d6ac46b4c309cc74a1a948bffdd1d5d663672ccd358c9ed2e21e72767c
+16a968605b3c403d99b8bc2bd8d9fc41a1f30a692a1302943db69e5b9c69076c
+51233bd6963cfa363b280b8499331c172abef2000c09ef43f04db75fd5502a77
+b0c1027c3fb29456f60b56d7e9983e8574b1e75032b8605a27a713be6e0106f5
+fc61ff596c600275a27a13140056fca17426d235e71cb6c4eaede2cac9529d0e
+e0bbe303ef02bd6e4cf0446fc21d90079f91eab4484a9251bd956483113c5eab
+36e7b8c94cd0dcd6c5c82a082d335665b37effc612bcb5793e7db4a831b51ba3
+2b44f1f9a10027a92cd92634c93167e88b0564498af080eb0e1f92bac9672986
+ceda766c4d674cf74a1124f9aba60cf8074b493bb28cc379485fc6d11f050dd0
+80a6fb444933008df47b50a0214bc210090f9a0cadff94f254e58af23ed76340
+34eba92a70693c7288a79477d7e9a6afbe342867ccbf6b44ee7c9adebde42793
+e4a59ef5953d031f33fe4f12652f26a74b958fe4ced8e6025289027340bb27aa
+946ae6445d6c3f79147c67393cf739061c2417cf81c75f85be80c99c8f37331a
+e6526df9d46a11c3c1fd15886f07ca08112fe60ee83cea46016d15f68e64bb48
+167cfce032d138f6c124ac0281f297439995583cb9825547be0017918dde64c0
+1d78776bee428f7534f80a3bac7005aaeb2ab9abc0b534be7563cb582d298390
+f47c2e5a6f0376e7312d6d0093bf017f64f6697b18b9ef831b2a29ac630f80dd
+1d6cee9eba526f2eab63cd1a53d842f52e58df2815f07f166e7960a410726b4e
+b4437978696e134d4cbc80a465744b872bbf340155d9789194a06e891d1a1f88
+7a74da8de0065d6bfe492d1326910624cc8f50405bd9510c27dab17e281b462f
+7decb40932108ec5ad173e146a3258d88c43c9f30be963eefc7bfe130beaafd4
+98e339cafccb138f954576ad1a38c0f149aa852cb3f0354fde3c7949de59dcbd
+a42a73f39348a8fa776c27b5e23dcb6d90adf52289f3c84d49c185fd5394f712
+0d2c35f112a99197484795467b3215ecc0d51a807fc77d76264c305cb61377b4
+97a0f1dbaf69c35529584a46de46f516fd4d1a8172360bb1e1d0d0c44e158511
+87b471d543e4884c6ae7cb6f5618cc584d1e978f744b3bee0269bbae4dd4718c
+ba02513e332a6170d0ea4015dc74aec1ff63a35b13b2bb91233026504a8a328f
+ac1e11c82fe34d6d24bd5753caf5f0d8b83c945add3e7b36cd0aed086bcd0e47
+3996dd6f821236d36324e3d387ca7bf1707e7de378286a5065e050dcbfad91d3
+5c82b436b4fdf3736b422c00c6728583539527b554ef64b1c2e6a9c1d17e35b1
+3c58226254ac2a47dd1223193b4e1649095013734c40aeaffbd7160679f8e801
+f18376260ee6e393deec9e0693b4e6b728e35de36b1f40a6683f7e34e6e086cc
+9805432b4ccda429b0136dcb4115c56cb01fc85f333204e534fa17558f7fe48c
+ad9a89128ca6c821b01864bbb67062d5286283ebc1c559e5668e11f469bc7e33
+ef69003732d1bd8f0b3f153bc22e8b99a3ca9669ebc50526461fe3baca5cfa6b
+5f7962fbc8cf0372cc716062f29dc7728499c9861424ee92b094227303046fbf
+54aa62766e8c9eec066901a8a54f395beadf01af8620298cff08f20a455416e2
+2247b637c6597a272f7b0a28d616a82e234e49b0ada3999073e77b58f11fd0aa
+ef941d49f73e962e9ae57574d57fc46a6b3ba2547dfc9e9667c08ebc05c98fbf
+02e2e43bffc8a244136206f481f0d584fba9db1ba41ef8a2e4c10227633aadb0
+846c79c5d7049648f50528b5ad34e449540418ae28b53c3e34ae0d8867ac3902
+9746aca8629d847851a6a13b21e2cd95acac4abca1ccbf5f955e6749656314d4
+5bfa890e77a9fcee0d7d67b9a34cb2a140954678555ee64f95aecb843d32b4a2
+281ea103839fd3860ac5de0de726d02341bbec556d8626fe39e3df189c3269a6
+b73d27881d4bc81c125a1cc26b0c6f28811d4f74bc8a757785053162f64ec819
+4b9bb8300b7184699ac7edaeb491f1f7bf236ce695a5ff56bf5c1f4390fb3c68
+68fbe2d542005635927e401e338b6bf6bfc12702ff8fc951a22fdab1d720faab
+e66f16d85889d6a15586b2f53edbc5f907179169c2a8a22e747a12d1bba358e0
+b0fc014a1c42990e01d76268d311e05e4ce5948c42bfba4ce63f0b86e0cfde4b
+4be316bb2cebd0e73e26f446ca8478bebc132a064ed117f1a02affff29dafafa
+c4ecd8e8e32bf8128a62aae5e7f2d84e6b63435cf6d2bfdb918a5d28ef147547
+f9e569b09b50dbee423c1a7ee40355a1cbece709a13273cba0da2e3825c8a970
+8037c94de0e67d5ada84c0bfbf5b96c0741fa043868c402079df2ffe8b8eb03e
+2492ea5a6338a5deb290315e26eaf43086655014ba9ffe48f4d19e172f9bedd1
+218e1164730a81bbc1391615488c5b66d2f4c66bf1bfb54e226df3893a4b5a48
+2f540f19560d9f7d3b32757582782aeaf607567e8f4f0a0a30f976dca0ba5206
+02488b0b7f41861a28247eaa84af240a91ee8a4292454f7c3c05ead6f814bcb7
+84b96a394e834cf5ef4dbef50ca3d287409b060f767758d95114a26b1613266b
+6debb528e85d77deb7f3bcb8a572ea917bf39aca584c4f841ca622e6e06ff8f9
+e8970dd1c9510c1618d01e1af0baec26c3882b6eb80a7e8f945ad9abd60911e2
+6bcb6aaa5f0ce78068400f9eea67fcaccc763770ccdcf7477f32dcc49abb5566
+4e75f91acaea2a9141436861ecbc358628590b2adc5ac76f1971818a49383737
+9fd6fe8d74e76a367abb6f7b624568fc7a4e2132513fdb1e66e06b9f81067e91
+3d6d1e5d95e052d569e75164830c1cad1bc168213da1cd06de144f4c714b0e42
+34ad2f8037115ec2c1a8fa611ee4094accd1ac585bf7235b1f07b6e2b14111a7
+409fe76c1abfaca8a35436eec605e4b43760883b31c6ef176342f834647de59b
+b54c16ffe522bb5d71f432a253b966587dbc5bdf85472472d9111b8a23c49285
+391771f0e037bcb24a5803b323a27f6f0aa96b46f1c23dd91cf899bb59b2beaf
+6ba551cacfed10663e3e7c6e7dd856ee8a3aef30b0dbbc22aecf3b3a8ee10fe9
+873892898ceab62300309a4cbe779873fb3bc7784a159e02ecb484f2016af7c6
+bff8798c7f00bdf847a6fe0e317465343b4c1585d03f83f2aa56904148de6d90
+65999bb74ec61877d74bb895b7dbb977d88e55fde157d4e0176416b03044e895
+655fcd0d98ea455342ce0e27a1c71c9178fbecd2eb6c35666763a6558d297fa9
+9983a24384848911542f0fb3956c7c6761b7e2b8588354eeaac4aa145203dbc6
+2d3b319538af1da0602ec260085ce3256b9a0ce6be0208920dc49eb2954ad6cc
+912846d093aa398ae77075faf578d0c86650318fd8bfb82341281507691449d2
+b498947c9ac0c46f7676e0ec94a1a7aeab00dafbe8b1a464bdc9b41137e0219e
+9eeb5a8c72795c7bc34600c7325efac4f2cbf55aa9b4a996c42dbbfc0329a694
+7f9dd261330856fbc0819043b357343eaa1d3fcf362fbd0af9a99ad57c5cc4fd
+a10dd10097941bec739ba978da465599d16b6be018bf7fa1ee3ffff04d35db7c
+a7f6acf148a663f999280b36e51bd77763d6a6ef6ff28950a8d4b04590d8b51a
+dcb555cc47d92b21ad032d31cd54ff911516f356127e38f2fa9108aff32d2e5b
+1f1452901c38cccfc8f23122f4680bc316785e012f61d2ddb468cfa55ac91998
+93a8ee273eec61e760ac5097167994cb9f52e62182fa0845b2fa6779c914047b
+41d1a35ff4c499474ac5c277aedc0ee957400fb6643a0437f7f6eace0f512ed9
+3a33686f28bbc8ec68aca9a2bdeb407e975a8524ec3c068bc0a63ac6779cd84e
+1d46c983fac8c89073669d02af734fbfddec35e195c1444c4ca678835a54b0f3
+f8a0aba3f3b5db7cefb364f491cd80110b6333f061613b5bcb834a3ceb20d95e
+8119457df0838105bc9998b41067b9ce9612825cdf10a56c7b3af464fc4bd133
+a9d9b14bbad06ac3598419ce3537fcb383ce9f650bcec756592bee0205e0ab71
+77b172fc8c15425359e43af5aa75959561e3b0e6ed08ded9da737f3181218d1a
+1be1044211e9d9fc86f23f48ea8dabeae2de9ee4ebca14a734dfffe643c55875
+e934dc377f170ef8fd4e83b1ea53026cf1343dfff18fdf667bfdcf21f82a5a3d
+d40d34e7c3090e76c1dfc01aa0194721d9a0a3783dda58edcdd65a497718a66f
+658a3c0dc104bc44b36a4372794702baadb0f7cb1594598414d5eaf0369405e8
+9726efdc14ce04bfea94bb1935c4d07587400eaa1d565714a33891f3e64c1d25
+fa527eea2ef14dad353c11733d6b0189dbf7c910601528d5bae1f98c01c6a347
+47b9c484e09e03e10e13b5be1d22ab7b8906c5e41a0b2ebacbf640e187468ce8
+af86b096de62d206bbc54492431b8728c55249aa887c01a82219e62e17b62eac
+82a7e09b484eca291295f575593607af6373ad85c692e7130a4b652ae5a706f1
+2457875d6098e477354eb52010424e6ecb1b69447261fd1b1f93e72de04e3f31
+7dd7313d7bcd5afcfff4e0f02f7e2dafe2f476810450fa2cf4daae1f85150f6b
+df4e5e7ca6533523dbe78054c7bdf73b04c4b562fc75fd6f6e2f9285410fb08c
+7bade37df9910ded1d21f0543b24ccf0c1c984a4d7811b8dab97ea56a14e9028
+999c9f71c8ee8fbcf78da616194678da5311ad63acafee762fc268fc2bf79881
+60f1a3a61ec3467956f15085c1808c8187ac5c5cc91b9165bbd978dd45d2c100
+de3fd8533581beb1864ad04e7dd74a016dd77520d2739a1d3e9524c8498bdf6a
+d7e9e0571c5108c7c7870dd240ca2a53e30353d7bf39bf075d3839681c1f8eb4
+b289720980a1d3ee6c5bc7349bfc37345b1ad0d770bb12819d72834cbe2670a2
+7129cb9f51973694153d80309e5a4f6f3a0476025074c15b091989d0976b5e15
+858a83818105b144f5097087f3219bcb26b446b033510a955c891a501983b25a
+d72cbfe682ad3b64c90853163504c4d2872a88ff8db26c3a62ac564d49eaae00
+c7f92cba1712464281b4100725792f62d16981ec1b1ca7a7c82ae9687c326ee6
+36ff55fd3211461deaaff7dff6bc94b248ea50f86a39d5edc51572afecb891d4
+c893dfd74fa47ebbacfb0d4490b5a8c012db8a6aa5f4531250869150c1c25fd9
+2769ee82670d8ea963329d3119005d280dfae888d29494533edb026fe0f11018
+3bf246ec4bd9f5c0de7dd158895ff40b7c2fe9f8cc537d23771cddd1bfdc171b
+72d58e2fe9de17cee61af288318932a6137f976d3804e2fbfcfdbf66bc31f435
+ee1e0dacc8599ef22ec48010d4c79707118da59588bd89557142af786ca66ed0
+a5a2bd6424d137c2352a0fe3ffca8db62656d143093f205c18e8dc761448fec5
+510fd026493991ee9ce12f4b2cecdc157f3bb6bf46509abbdabeaf66703a6a2e
+616d5d15f1145ef8654f810b2e30158cc883f04d31798672421afe80979630da
+fa9c444aa6e4b242c28df13c80d8799b8916c9dad6eeeca41ca24cbe3741de73
+193674f95269bfb2f832513d434c34704365a3aa76d3f6fae51b8d75f4460df2
+95b64bf0668fe311f78177bb0f560dab089ab5a6fd11ee9bd6faac3e6c5ee08f
+79c94b34fe82b1a2561000fde54d3b8fc1fbc7d82392690b2216982e8d9b09cd
+1151a8aae36231d9606f2d737dbf282f4f685698e48698815664d0a4eac63dea
+f0d58ea4edc1ef27ee2f4995e0231c246aaa5e212bad9a4f133f1752140cd8bd
+7e203b235b3f54a74a8c2d8a4106ce38d558d3e9061c34cbbcb5d133820cd5ec
+fc7ad5c83bb1b22781bf049a9381bd3743c6ff79bf98a25fbd361dfb8a7facc1
+708d075739668598c9d0b597a9417849de1e8169df8ba2f2dc6fd57d7d07ed4c
+bf6f64734a56795c6480216208026a24e0272e871a44752e850892e9fe72b22c
+15d83271bc1613cccdf6616838668fa293c564f8dbfabfea0305b7497286fde3
+411e00adaf00fe89f1625c57ac1121927000bf28cf712e55b2a717a4f5d2db33
+c2921df3f6449bfea10d566ffc096450e78807f7611e281693e30463d32b2135
+4d94b8a6d32f158f1053513eea31213d73dedbd3db635e0497bf3ff4876049d2
+922e0bf8a8da35e25d19b6b0ce73eb7da1402b73627e90ba89877bfc16f3166c
+5a458aca38c58e7c652926c6547d018e109c4fc5c9961aefb4607e48b0c89cc5
+b373ad46dacdea10a1d503a7496ada54d41c342c36dfeee1dbbe8e7fc3664460
+6860622ae1c90d5045e3a6eba40df23d4eb867693a5c3d25ea2e1c31d8d9e630
+b22140e1078d577df6cc81b92bebafd700562ed45772247a7edd7fb4b99db8df
+6aa8ab82b059d995474744b7a7bc637a449cd4175d19116cd1e122beda56868b
+f904ab38fe601ee18ac0d56afaca712d10c09763cd21df0b222405160cba40f7
+a1828fae4db2edd32a8e2c5c173434348271dce53562d1c7f96264023f4e54da
+63035f84596d3a2a028b4692ef98b3362258c59996c07b04e83b66d7e5ce103d
+2352d8404e32c8408a5b05cf150f151484d3cd493f2429ca170b555d7d2be9da
+96212b3b60936fa14a71b72359b03bc93d72f9018aab793ea640582b24109e63
+5bae72061eb66bdc9f28a9c6ba27caf470e05e41dee81b43ef660d5baea7463b
+d36c47bf900b78f8053cdc842fa4c642c5e313fefa82c1aed58038df06201027
+79450a5b69923a4057d090544c8b343d45be71ecb3564b5ae59e18685679c804
+3ca2680a60ffb8dea818085870c75b54014fb031764e95b666d6ed70b3711b9a
+012479e72d988d5cd8f8a95c3d923b9ea9717eb15c47885c7cc5ce411d4adbf0
+8cd49e76553efefb04888f85f17e6000c0d1c3a875b54ff0aa1a85bf1ca4dedf
+d0adf53da875eefa66319d47b8b7af2e641dafe9466a8a1ee3f81458359780f8
+4037e4c647a86cc2630a6cb49675ffc1dc97667a589df4e77e3cc5c41215c16d
+43691a68f62e779eb70af689ba7770ce5c2f6e060b35c7289e3fe5369ea79179
+8bc466d7c415a0c68fdccdf5af35776cfc5074a92a8f131b5c2423cdd13315ad
+a0e4969330ca8b505c0d86efcd3cadb7f5c77c2666dd9e89b9b03023e1db13b6
+34d442bcf0e3df94c239e06e9519c70632788c64bc9f4c0d92be238be009cd8f
+c8765e62d5702bd31435c9eaf5f2a04d3e2cff1406337338821096da07740689
+68e4918bd0e5e26d0404e4a8b0169ab68b2980d2eb8a5c1ce0a8c751f5db447a
+b0cb1f731aa4c912ed0e07c3fee570ccc6653b649e6ce447bf2fa355ac39f692
+b54d744d5a6c1c03249e03b298301941d89bf065a2b30a0c3aa993ad18eed0b9
+cd70955aa7aff8003711178da2eadac58cefc9c1e0e4fe7ec10ced8be8e1b40e
+56effc97fcd8c1e68230a111da8d36806ff8c9bac32c879fc461c4cce809c49a
+5c489b352f86456032a19b6bed07c13089bf948d32831f2ed25c587a477f21b8
+ff3bc437460438312fad6141cac9311bf29dd9f221b7a0cf442b48ef64c810db
+82a097530a129f2e087cc45cb96d8e7a9b3e761d3f3ff61f8f84ae47d6a2407c
+c653914a9c1dd0139f299c84f5f964e6266edfeeacabc4f5fb89b9189b119ce5
+7c8ba8709b3f3b73e9e152de57d9be8c3a339ee8ca40c1bedff6008c158d777a
+611b1f2a7e8ab0b52d211d7a8998f6cf2e59d5b6ed0f0ba65985353d2734f025
+11d235720b0860e35cb63b65657f2c002fa54d6ba8054cd94cb450559bf18ff8
+41f886d991d1298cec09ab4bff98440cbc4977521f74c2c1024eb76c6c827b20
+64af83474d982ef71b5b4d10ff5e99f69943d9ac4cad444fe5aef239e51d0bd9
+2bf89b9aa0462413961b459b1d7fe25672cfba44da5d787590db7cf6f41bf8ff
+f568147f252935fe642f45be670a1f5ae82a53d6e850e6a0d7a37efe8b6c3384
+7df10e6dad855b05d8cc56db3d5b457cb44ba2061ed18bcd714b6ae4cfeab6c1
+f44a983d3b09fdbdbcf49ab4d4328aa83bf6d35136cbfbd7d9efb9387bb43859
+82761e4dd9a2e8af77f8f975b08f158dcec98b861a7da0da7b72c54fc7f32ded
+961d8a402150fa9936415195a5d45da466d90bcf96269b77a549c615291b4aa1
+9387d40c6fc1befd017e98de8f8353e8eedd53e13f858f5b428d8d481d43b23a
+34cc07b024e84e56ba46df541baf7ae5444de3aa77a387d27d5149ba0d83a910
+44f1f3692324fb3a7cc2adb22d5063e5bda1979b7cf64e264da24a21c636699f
+57b75385698366c7a44d025c4d96735ea99cc446bb20371b234bbe531aa36e4e
+2d2aadb8e6282fbee95b8b4b71f90f2d7d6d38c9030428b0ff301c8e6620375c
+13828b08801a6099924f114fd3fb26afdba9100caa7796f312e5b98b9057a932
+e5789fd8f710032d29b526c9df47af7023d4acaf512e4b7f4f2d13827e235821
+3c4a8e0224939013a38fc7d73fd599b1a7f2fc9e314118864621981243462328
+61e1c62f8ce1d4ce8473d8dff76600b670e7649fbf8422d09dd79b80526a6284
+e74b3d04f1ffcb2a31bca539a25b64754f893b9ad03d965b5fdab567cd967320
+8a1511b9442f896f0d5d5094649b6296279549ad3699f348796aed2316194404
+e0ddb0d0f89682de1648f4675c2d8db63b68cc653d390fe2333ea0829eedd9c1
+b6f6a945aeb5ae4d7973756eb8297231c9ce5213446345bc133ce5b1470c6b79
+063c8877442e16dbda8de7c5365a9c8a91a8c9c99767d15acbcc2b82e54c8784
+7c93f7a51f91c2dbaf550130ad4dad6896d9c1d905aa7746d7af81b7f22e4e9b
+18db615cf7e7bc813159f5b125fea93787fa42db2b4602432c487bfb49f29d3a
+71ce1ffd9c30206b294f8281f58fbd27218317587316c308ff6f719cc347dca6
+49536a86205c1273edfda12610b212f375b91a841657a3e9b9a359d398edaa43
+f21041a76f2b2249e0f07925a996b24d56bf902cd2e6a2ea2b21c1840c0c1a2f
+85c98789b1438292bc27dca7dfe417258144d267c15b53532e49023d620b4471
+da5f47f99ba092cda984a7b9bd308b65fb0dda4c92cb84fca3e5086df0100ff0
+7a031a99e1b432fdecbdb2136e29faac53425c65cadb7ec198da5cb63f6e652e
+c84866add1b4f754c639ed962928edbfeef1325fb2bd80f8624ebc1465fcd609
+0931ffbedbef0aab40593c54a51d44ad7491e07d443ee0ca86167233de12657a
+34a84d7a96e9b73698356f5b7010eedfd0190287810dfe3bf4b75dd58e98ce98
+d10b180f754b686e8ab4c621e4684b34680d062bb6e93c5676f9637d72520771
+82e1f2d39e889b03785b28928291fb0997a2a859c7e732b27cd15ad1b70fd13f
+9200246496651f4d5580c0372a946a6a272c7eae3b071afca478e5f23e95f950
+f58ac0bdf7dc5e4ef0616bfd8e2b51fd5d2ff5ac5a816c4f79efa4a4435ff54a
+bc65250ec3ba0671ec26fd1179edcbf4198b5dc25434170efae9f03b5e6da69c
+f9d7c08f7aecfb5da7e493708e51339a7ede11d3de96e97300f97b550f84fc78
+175fa0d6ec79c2b29cabba67356f91f43bbc1bb659dac336d54a39eacb36038e
+29f8c412a4b8ba0272c2e0bda184577d41e3e257194925643c04ced8484e7f3a
+899b12a5d50b058f62578cffb5fdd06eaeb05def00d246c2a7233629efe00e24
+1d5a9e8bdb053c506cf1ed8d8305d4ad7bc7575d84fa8cd1c135a6e69ac8da9d
+09b0115a6110c6df36f7bd530e6f01beb2730acffa423c64537f419bfc76e56d
+099a956ee260ec7848be76d4ee19d16eed24b8f0a3d8ce3df142125dd863d00d
+8ec3a0b843378fb2c3ea865cae64914acc78f8951503547690233b4541f3f07f
+a38bfb69c3747cec408346d846d9a94b5e259b51833e258f78b8d56562190a80
+1e649a8ba76fcb180b6c93925e26d258c7c407262739e0252e1c41f4404d4e85
+458d6c0c821b6b5f099628edd92ed5e49ec2e72c3429ab2f3e5745d42d2e2d5a
+8f110fad66cdc39fa4a5cbf520130e2bdc9cae5b182e4d06d4e14caa48f035ea
+f9661ce17cc29d4c91c57d757ee5586400ee768b901c4fa51ce00bcfcfcfc5ce
+1b2c8a7dc2eaa4e4acce7692f00ee06e6f9ecc8be980cbedeeefa1de4571dbf0
+2ce7bd33eda192d31d31befeb2af77c43795fcc79b8d8f6cb0c693d3aa6cf49e
+0f3925aaa9f1173ef04f03d98e712334039fdbdc37308ffce34b411448bdca7d
+cbc32d6da5087c881d55e98f66f4679955b7a93c02f6b2960052319bb62c4063
+865b82366471478fd4f07b44d6443e308f8b2296068e4a32301cffa189a2a287
+74ee405234755f042addeee91b058edc49d37f438bb8693b5c8ec0dc84719b27
+c578b0586ee86290d38c1122da90e89102be47e53ac93f3be3fa55132dda885f
+4bccd87bdc334c32bdeb08933d4a5e6d85fd678f3858cf91f2338207c490c475
+7ce9d56960d5043b8c6b373bee6c0e173e6b3af58578fe6b36679793f07aaf5b
+388c7727d8e04a6f0076eaf9095a3ac2ca87eed2bd7a5c35fb9ef360158b0f64
+48ff114f281f2e8844d0ac1eaf0d3436c5310e6ae25b11e3740cd03ec49fdaab
+a40732a5bbc101b84d70449cb1c8880b0723e07bb2ef63bcc66b95aaaa03ad7e
+c163bb5e98ba3452d5373fc320fb774b13861b72624b46f92178c2d6212fb3e9
+abb7827037747261fbc93521cf1ba38a47a44efdbd6223a0f3f929309cd4e160
+83588b4ac6a9b874aea87a69a8081c5b6fc2f2f70a6b5a086493b644e5ab1d01
+31041fbd60c970f675b13a311e48b86425a83f68087dfe05a6aa22d6238e3e76
+d31d029915bd11d27dfaab5a3ae95e04ca156d2a600166616ebed20132690873
+8fd58eb219a91830624f6c1556436d82ceba3293b1c742f29fc5896fce7d9e54
+e1fa81bbe5b0ab391b73172c7ce9b1bee7b0784cf4495bc6fcb755092a5f2327
+c164e6d783be38ce46375f27afff4ed0878f8eb46634b8f0d84df58a54226d5e
+e4d25edb042bf51bde2ab6c83852c628bc95fb10ffb4cba46678b30f7b108ff9
+3adffe98f6adc255bf4b0d323dbf68fd559caaa55166dfc4f1987508ec46d6c6
+f1db251152af4b9366b559382a3741e8fb0383eea8deeb6b4ee9ac5c9cdad65f
+5996ed7dcc672c41852e9891d25ff257dd49989ed6eda6b3e5f762c930fe6d39
+75b412254c0279ddcd9fc664b9cfebbf06c884986340fdc76c8e4eb560300943
+3d2d96a10f09e80ae7b2eb1f74d82bcc9e16fd11617663768f5f51dc464355ce
+f33d0c912abc567c2d327b0bf95aac49f503927236814663541abb7c689365ce
+75587190537231e16c0d049332d3ce07562cea63b3dc191d68e86824b43887da
+4dbeec79ea7b0089f9bf2e61248ad3d27bb881b704b827e303c21cab98d23447
+dc20818fe8a249bdc03d772aa548f6c75925c74287ca9100e91ac1693d029f7d
+73027e8ae734e354f2828f18a8e87b13d803fa68e6d7c4a79333b21d177c61cc
+6097ca7ccbf700a75ca3b340472daacac6e39be45dd201f4517b22fc0acfd06e
+8920c858542ded1c95590a1b5814103c700c13ac904e7ca36aaea572110080e4
+0057ad118b690e48cb8545bd2eb99b06003c1387354fe3947eb5fbe0244e9350
+e7fa6e8da13b62b862b22fe5a28fb5f933d0d97d0e54d2ea8811762ad02488f6
+163c146a53f8ee78478ffab1b9e123872ee7f50e7f8b124765f3bad9c43b588e
+27c75b8e7ac979e20edfdd2cf68b506baade781facd7c00fe6c967d84c2a8758
+3a44c786038aaac152a10f82d5fcad2fb609f62c24fad60bbe5e8e0d3c3396f6
+3b00be25467570f5595f6dc9e53bd5a437fb7388acef7cfcbc22689149c386e0
+97f429be6c7ba8d70d8548e796192921b764deed94f5d4de9365d3535901aa91
+6081b7b548c3b7555f8936e343eeb062a2596b3065ed42f1489fe03ed0441236
+eda3796353e768a256f7eb8f6f9c05d9f8449bbe0a1d7e99519b7e119ca452ef
+46600b666c010d1f9a2eb21a66cff233852e8fcd21cd475423d32a5ed8f1df40
+e18b1290ffce0fb278066132598133f094b52988a9e3ceff74297f4bd6f8b8dd
+cc069cc7ef14ba3033ac19413afb7676de9a07a2ba70df9c96bd135564e7ffdd
+6ef470d939c5164eebd1d3824401c6e75b1b5695fbff0ecf5056142cc8afe527
+e260e93ce22b5e0098fb54ca8cb1ccb24ba0c4c8ed9ebc172dbb1b5755d34b29
+d175b739f71d3befbd43edd58f0b3b560466348628aeb205a6db30a05da09ed0
+a2a99eb5126257cd30c90758af231106871da538b340e7d6ce24fb0c43397f2c
+9dbc10f18c1839cc15c0e331deec3fceb468a9c6f60c233df45b0c53840dc659
+6a776c43976449b2cbe76e0939f060263cd00b837d21c32e1dd78d1b974fce3b
+e8df3ade9b4be5ee8c02bf10fc08f0148177e4b50bf6f1c98589092d8d9c52f8
+2ed71c6009abfefb3a2976e40e700a02b2d5ea69f59dc6598d46ff6b7e3050b2
+983a3e12acc618b4a2d369151b49cb89ca2bd0b150d6faa0ac681c932a7fef1b
+e77349f948e67596893acf953df11844fa2b074fcc8ef0b37e5d43a9282f3f82
+92436c0508cadf1b59b4e03f778dec524fd6465467ebe7b064ab2ec7f1e24a7d
+20ae5843b6919e1f4bd0215d91ab1ef346fd97fc9e4871ba30f83b06cdfd5180
+79d2926690101525f2361a54983ca427b4a7937b550aa9d3fa3409f6d4087cc3
+3507a7dcc6b5769a8023cc0b790e149494e0b4a62474fcf199f0522d0c0edfd2
+68a3b5b86d600177616323dec75753f4462239d88eb6c44827119de33665e57a
+a7a1f133819dab77d51b264df8b9028651050598ee266a645cf34719989a059b
+5493aa548ac308eb673f9c5edd38fac3997c4f7249f28883d763a6dbe8faaa9f
+ba42981773763490fd6ea356aabc5178f8e97387390910b3310bf74d879a02df
+15ce08929c489e7ab1b7fd5dba4b31f4d10a4d5d860b354549b948179339d08f
+09459bf54e119824e5a856cd287ee1066a69f78e0846fef677dcbe223357861c
+80e5b7f08bff3a08b1d3c18e0fbf66b43adbd1a91e0d609d92636ddc654afd83
+d490b7909b780c130d5ce89818169700a3ea8de81df1ace8a8443b4c365531e6
+ff35353f0923afa95c1c3fd19cdf0b9327ca8708ef9c891c5d5c96ea695d9047
+0ef9b222757d150824fe741bc0f9afa6ff11a882da853c1ef45a7f6936fbb2f6
+6efd8fcb33650ded480ee0d3857581fee0623dcd0207e6e708b19b0b93822994
+0151e373c3d60366d6a1b61f42ad9dafb751e557a7d4488ab09acda685610bdb
+4122e2ae576196f50c38a26a757b2b6e6ebb332a62b98c801fb67bc10a97df07
+f5b6ee43ba7a1c4583439a69b90f160a46f3cd05ee9d58774ccb8f11f8a1bf9f
+736579cf2d3b1ecde9546d86aac7e5a9602bc9cf2cb68dd3264a6b12f5d185bd
+10d2cff08d2ee240c3b2d4ae0d5f93e2dac55b8d3adf1166473119c3ea90cc51
+bb7f28012b48a63ee7acab3e774f043a008666af6d0efe9460c40ad82ff85ab3
+cbc09e4cd5867a52e25b1a72e678ae7cc06f6d8f913ac783749f30dee5feb866
+5ee18012b1c596918a5869a2eca7539063f16bbc2631f4ea99f041fc64b0be69
+82d3761638d1dea2ef828388c0629e3d953ba41816bb171d65e4b99cdb003281
+cd6c9b350577d2a922144402e90d8b2ede231304f055f7db115536c6fccda5ca
+a5f182bfbcb454c4d45dc9754d04a0e881fd03fed5383d8b30e8397fcef19169
+df94e71ba1689f4cf42e60fed255e8f49ab1c90a7148ac377807e088bf4a9110
+afb3a02181f44a7caa8fdc61d2fd78fc18883ef6bf006126737d5889dcba8e99
+7223053cf17a110798b1a1c5f319317ae63f9a6ad23eb6d47d61b34c596e37b9
+c1e107ba5eee08c98af96261458d758b9077c3874a4b7ad2cf2bf1d3a8f7abd2
+9533934e5f6adab0b87b68d953fb98e219f956ceb8f172f03286cb84384e9288
+6166b2b49da6e5696f042bb6006d942e34589984e970c2641b97fa88bc536d40
+5155f236de881c851a52ab869969a377433cafade08178b1d1133891e9df4b21
+f8348e0bba155ed17ed3c26fbbc282aa8c3f27192e1cba91e2c461dfb5a52977
+71874292019b743cbdbf11dd1a6bd6b7c1695f774a34f37748d49aa32b5ecf5a
+8a1a30e329e2ea9eef8f90bfffa68641bd11d749f5ba343cfdb0181a92ac9702
+97725b3968c8cd20a0af360e9b740bcec8644e4e1bab4cd7a3dd1876f09b6af7
+a90fa670a465949784201096eea610fcf2d5c15f57d39642bf0d3b246da6fcf7
+d01da8ce59b4d349ee76e8ae72fe26a170b2b320eb5d0a7d816488e248cc016c
+d9f4af759126fed20f54f6176b44203b3f1c248d9cc0e3c6ce05aeeb2d127183
+e1f89aad8ab25e5660ae88852b0465f2bf00f234aeff78a82abf21b3bd807172
+94760e71a0472d14f9d3445c24b38d72df442b8f78640c55412f21d6b42f7645
+277caed0dd19ffd5941e1a325aad194512d13e144259ae01fea735580c28c4c1
+48ce5ee289d8bc05bbe1323019076da77187d90a35fcc1d0b52e921ebef447c3
+fd0e445d5fc62c809df85bc141fe677098b65d78c29d9f279cab0e7b452d4490
+e93e6c3ce30c84ce73e2c419c91a304d0e265df004b549f73f69995347e3a654
+7e6a0ad2862de39e182bc4e880bf717643165a90b094ab19207369bd92fc3cab
+e1b71c496698044f2f1d2d30248eb0de57281ada534155d6991921fc3442c8e9
+0c77c1b1811bb3ab2bf9045afa0ef9f74f8577e48580eb9840354602b222e9a6
+cc9dc2c70f7c7e7ff18687a420c3f2a46680007c14d75fb701e5b8981f4e4175
+fc81d4286920db1080ad6c8d066ed15881d710952e5bc28c444448aad1df885a
+634ee6bc8ddb52470854ad55958e179f05e61f8151a6fc6311bba2419dd9a122
+93d6e5cf69411520f281b8ea2908ad8f4d774e777a633a8e9aafab363f426b65
+cd8889d109f0c0ef516da05063d1d3256250eed899d6ecbb587c7b2ea943127b
+f76453e581002ac170d6764cf3ef98ca600f07c75e2d124e3faff95fb28e23b2
+3969ed2b48b2be9b9c388660cbef5dd96ca0249dd307bd2044a5acc598aa0bc5
+73a97be07ccf5b6bec78d99b0e97238e550e4c56da12ec2657b6dcc9d9687795
+8411cf3d159f8796f6e9b8ee98daef301c4cd44bdf9da4c6aa6e50ff517dbfc9
+7be31ee0955a1440fe1cf27b6a708022104a9131d6ad031f87143394c7d21655
+2a540ddd9eb2cc438c06c9e38caa404ae87726a1edd9aaefa7e6b269cc543224
+35319eb5556497653b293197f1a5fee96c0c8fd7cefab51a34022eb008dad5ef
+e010fbea98e316dce7b94a9a766c121907be64f3759dee51413669aa882b454d
+51fb13a4b428d61b3506c20d551a6e96c1b538d6f10098abdc6fb8dd739cfe69
+512dce4336bd31eb5a626457ff6c1495200056c716a10c4c8e6cbf24e5a89a91
+a78d31db4b1dd71d65bb978edae1b0821bd3ba879a646c4e41ace1fb44d08e61
+1a9d945397ac69d01e8eb8e75fd60ca662ae1649346031ac32c3cfb352d84419
+52190ff0f58283bbd1bf4cda15bbd219f22e738d2c0a114e3d50f66f9fa5214a
+34dffca2cf6c88883b0e416196e49e45a513994ce8f1abe93591c2b6d67a151c
+820f1c7f4ec6a0531d2d13056f35c2c625044abce5111e3f625c09fa566c52c3
+615e8f83b28506f21ec2a9f0d93fbcccd3a2362ca0612bb8ca2123300786581f
+88f1632c5aa76dcaac69c8f0140b45bb46871caa9ff1c221dcebca18e506ea12
+c8ca217029dce549a4d34a58c3a5333e90286ce87919665657acec87d268c255
+36837e5b6cb6d46367faa2951dfdab709b2d2932f44561f73545c82e85599f5a
+8cd72bad593341e0de99b41b34f3e5f8c1ae749cc7e551f465da293ba7af4973
+35bf4d08a68e7abb0bff04ce36b4571b4212e6b98539da495ed516f0438baf8a
+ce13dcb57eeb473bec625cb3c5bc70ae22067e6f9a8b0f4b4fd231110aaf84c4
+a48559e8303e7016c0d02125d73c690abb75c178855eb2cfe2ededb11e76b06e
+6942a101a031fc5b110576389fbade6619ab0e05e20b8d750af2241619ff8224
+fd1396f44683f3735c0b83f93da04d435aabba9073a7985040bbc0ce5094ab39
+7579d5bf618b518ca8b1c4f9a8856c711aec8ca0609d7d338fc6edeb27fbb400
+db045314795a74317812652d0c1ffb1fcf453d5d769df6b38831c3914aa0d5b9
+ad41e0b7b9da3074043926028a84d1b09f52b619703814911710cd915e3ba310
+d17cd6d1e5bd4b7a9e7e3fa2f9d0dd50f88b50c895f3c7cea1077242cae145b6
+b6311f96996d437e369ee8e46d6680affb65c151541ce5e2eab2c851b693cf8f
+2b304d62d428fee0c99841d4518fe2f67ffd0a26934bc70b2d970e0dcc13892f
+d3b725ef5a946c273da4714ad1b2f4f54f1c257511770cb2bd0c010c3c8b03f6
+3190f52f50bb4db5aa56eacbc174e40452fec845b974920633f2185bb09beb6f
+f7f1be65ec2fef9d10313e6830db245c9c976784acd842f22c9a12d51fa47759
+d9c0db2858e0ac424e923a48e89eb55b4d8ec738caf394d3b4761a1370fa496b
+6d318f951299eccfd3de0b94ac27a952911e165ef590fd2729cc2cd86e0362ff
+f5b51dac1c08519ea8fd5cabcc45d1672f3f04d1e463acf0bf4125434e72cc76
+e900f78a48609069187cc80acb13d0f177b56280d468737cdfc77501bd9b4036
+fb146ec5319f7a3d828d5523ea007c9cd545ca043bc48f174f72bdbbc984b40f
+f6d51401d414fea2bb1ed31761cd919dd49199c0e0d0f1ed1f00e8c3b59659ed
+55a1bcbc97ad9195e1104af67d4ca355c0c37ee8fb6ed7be5f5ff6a68cc2ddec
+49cbbb71871262c4a1a7cfaa39aa6c86002050d91f7cc507715fd6b2d3777d2a
+3b9074928121dca38c6934019d4fb8401c4c2484dd0c0145950067025edaac4c
+67a0d18a77005bf9c9f953e9e55344f449278cd75a65a3b79071f4c3f0431c82
+f9fc739fc4f3e54611bfb7fca635a02492dadaccd7c85d8a4465aeb5285620be
+fc6180d39c44795748f3fa23b9de86a548feb40bd8658b4adaf79ba705edbe4d
+1e5c36ec57d22a9c8d64bbc765b504658c167ca8829e60526239ca6af7624781
+2c3d3b0d06f4a51cc6d8c8edb841bc0a5ed2f1ac4b9fe64e09fa32417817304e
+bc2ab036447956eaf189ec62f0e415f7020da9264ce26c5dbadb6dab9166fdcf
+56e1e323947513da6e14e111996db6f92e93c429cc62d4744f9ea96cc7c55cc8
+f1859730327991842fcc7c2f2fc74895ec3974ae60a1b3345edd7929761b6338
+5450c2927d83993844e156ecf5266e8bc5613bae81fda0a628954e111db74915
+285e808bb8a4660c098b60f251ce38512910d17628131476de7f1ac985ae1fbc
+a382688cad091dde470d6407a0967cdcd26328fa9a28952ba14fc49af8a908ef
+82cd09ef940c6f66076bf67250e40b19a1e9e4f164246f6d1324c702a59c143f
+53a829d967d83e905f49fa3a64a2b72689d35213f3ac78f80ae21fa415f2440c
+4bcaceb6ba89836679103b22bc93a425606d83c43fef202e4d6f20530da1ce81
+2963064da571136d79edefe79ee876a4e01fd64a5466315ecb61832fddcff2c0
+73e6aaed9940bd8d28eb78f75a04dca7235f8794074ccb5b2b8e5099124b91c2
+047aeeb70e8fc5dc8bec6e18376fb7d5546edc7bf20671931dac014c6fdcdb35
+43d15211873ee65f793aeb3291f3e88a603367f5beaa7ee59e862f54add1a819
+1c4b38065adcf525170842879e91529f35bc5f7f78e58f17f870dde83938c149
+b9d15b3e8996fbc2429993e05683a4c7e64f7bbd5a7efc2c9ddee24ba3f329cb
+0a7b32180e73cc1eb891104bc8b0a294816011bee7039c6fcec289e625c725c1
+546ac9b505162aa68156bfbf40720498408809e5312b4fcb1505e42effe3d3a5
+04d792517c2b85388280567a1554181855fa60e250dd91af20b44bc17931fc52
+a88f9a9eb5b8c1823ae73f4a7bffcdbef86288b38efb87dcd76ba02bacbc8349
+3613b0938cd5e98c4f3966339748854ee61bc5592ec209de1759a50663bb3360
+831bbcbe02f72cd061ad784137e2480fe026a9c1130a5a557062d72808530bd4
+0b470f97a025ec3b2789b6274ef44b92f5d7a7f138683cc6dfe03e0cd78f9df6
+5fc8eb68eec6f8528ae8a469cf0521062ab95a4c7997f38969541cdfecc7f89e
+3263cd46c25cf6b7cc64fd20e741017b89d0298e2c55e9b582e6afcdfa1edc1d
+fc65f458a91007254db8a33c38db16db0fb35352d54d9c88d602caca571af64c
+b0ce3b21689d64fa4561dd65be69b6e8fb81d3b2e32f7aba81353cb0f583f90e
+57df42cfe601d5c05df071b49d4bc7a45df5ca2eb8a1b547d8767a1c0263b93b
+e41caa3561b425093e8c8a44034d9553d96fd11e72be93b81b975533b30530f6
+fb7c44a81fc33028d1474d6d04ea1e8863403a4f27da27dbdb37f2af7eaba079
+ac68f395e8237597cd822fed413d10e4bc700e8e3409729b51ec700b17732149
+742fc60c9f5b26b11aaf47d3e80d4c671e5bc0511f8bd6e7dbd28790e622a32d
+15e9cfb4739f4c6d67c8dbee302232a3fc9cda721102dcdda24447202601daf9
+f6c374b9b5d687d06c0755df942e51d5522b896292211705cae907523b9e872a
+04f49baf43d89c3a4a37cddba1d3f20301666f7bc970b72f5cba25029bcbda99
+9afcb5df7a9fca9e11239902bb4b2e4609fc6fed679f9324c16216e4fdce2f3a
+50057b1e73344eb5a8b739788b30d3c4e3ed6336c95c84a2237525c33e59dcd8
+4a08ca60d031829e9bdf9a3ab6dfc6f8bec79e40cac1e69ff6d5d2ccc9c65a38
+bddcc8526320d847d7c6543f29f6f50959efaff66812939136bb69233f36d376
+c01c368e7fc20fe2afd1192229305000ab8c2068d8d2158878144b545c2ae572
+20700483bce6259efaf2cef4706059847bd721da53a0f842011e6ccf8c2c3ff8
+97a477bf6dc659a256cc73f4f3b02f541bffd23439b12520e45e1f731e06af12
+0c93c3b21993d1cfee5ca71eb495e705a830eaab201d4ed332d65661cd38e493
+039f250a3ddaf8c3a88231fb9a92c72e02d50159faeb9aea556c89017a8703f4
+f2bfc73d0a40182597a260cfb29c7cc946780548fa1fbf28d35a93a2393a0793
+1c789d864ef906249c1d36ee78a62512e29a9a3f970f7a1be3e7e566206d07b5
+15135f143706e0b713591524dd318f1b10210cda652d5a6cd43e4e4a1bb0e855
+26157e515df835a962e7fbebc7f51a74d5224d20b5e0472ffb65913d8da5900b
+31d90ca409446da57450fafdda1b5975d898b4ad2cd43b9d0660aa81417d2847
+d4d0cf0c9d73ec645e61f16b80e1506a2d2ab8f87a1ae7617d7fe8e5caba611e
+ba028b2036b33721f78dd420557254b02c071c99fe068be1430a1fc3a29ac15e
+37097bfd6ba1d7f095e98874ba909a6a58a419f82545c65aa70f5f2418fea14b
+1dc7efcb3144c4cf060e6bcf0bb68c3830e09d069e83f830e6e4cabda75e9cb8
+cdbe388b0a7c23f7e62a4990c705ef96fa6d95ecfd38b2575b0be70d0872509d
+d66bf4e2951121cae49c0b21a13685782a2a83a1ec70bc987bb9a0fa8f41b273
+e071a94a9fa55c8cfce83d1c7d9b0549c6227b8f8c0ed03760f5ca091c29a663
+749627d4038021a7c90bedadb4fa40b8548126986369e04f62c1b9c31624ff89
+debef79c9ca9b8ee82c6db65d8a67fd9c6fb4463e956a1d7120a5bf735902327
+6cd611fae157909073a41db7f5586666efea0c0ee7216a8a5699102ecc251f18
+f26c6630cf5273bfc9c1a27e6abc5156231d8879c0d1d767f6c86e7bb09829fb
+6b1434eef852c1c72751010e25f689adadc0e0ec47fb9e4515ee95bb75a77271
+0b13290ca72e10d1ee9ee35e9aecc6f32c83f2b2eb30aaf52d6948bbadef1b58
+7092fede63953a88925df565bbe37456e085bc3c22c2b340d07106ad7b699614
+f7e74dcf82bbba467ee87152378a4ffb6192fedbb103b9abe1d6af8e0b84143f
+f2da81b8bd16f4b9c110602129530d051a23df6d993e5f0251429a9dd8bac65e
+e13e0ad97335e389f3bfff17b562ed1526ed4ba8f1e52450de53dd275acfd050
+dfb13f167c1f18ca022c2b6a74cf496999b02ffc072a6aa998e386cb1418dbbd
+c30ab9ee4cd0ee2f0b4eb816f7cfff1916d0cc8cf278cc8a1ff0501f48a46b6e
+8dea7d7242efa272bc744d96dd89c2814c27417ab2d6ed7b765c02d8b9a1403e
+3ec34f94e6c198994da83a17ebfaff5e310140b44f9496caffedb1640d85d8b5
+d3e2b2b5ce786f6d910da398db88738498eda315c4c84acd1e3534f6612b1c41
+a27fef1368c001a24ec8b5ee18a9e0d84c6d79671cc362b0c89217158b930cbc
+c852b3ba003143a4a945aafa7d9c060c911722bf05018898840e8f72f459ed72
+13d17972624f593de7d4574a1a9c7e2590cf1d17c5e284021472e4c16fe656ff
+e0cca210ea8ef7a64190c489ecc098c09ce5d0fc6700ab2b4ff0d6dab59f37aa
+8962226106bcb7a416c48170e107f5bea3fc9fe680ad4a7858be6f1184bf8521
+4eb16a7549cabdd7fc6dc23c62f3164728dfbadeb65487fd0b979d3ddadfae17
+06cc49f9e781de915ff8d88ae280986549fd6e8e228940aff26e1263879af4d7
+228c6b84de15ce2b2a5ea3d3771bf8e95c04afa2e922582f3b7b4aae3bf322ff
+30a6464cf8c3dbb95a21a4f724c4e59d581a01bd6be1ec8948f82365125cc16f
+3caaecb92d36829d781ecb3b6cc69c70b28912a75010ef84402c6245a9013d0f
+7f24bce18f8d73b5c2ac9e9d91337bdde61801fde21bc848876817d2cdbd500b
+f745bdff95fe3e4e3751b157a590e88ca7cc089ef91d55757792019742bb9cf9
+000b47a1f0d14a1fbb71b8875f7069f246ce622727e771363475a132909f2723
+9d4782bf2bcb24db445a975d7b84c0c0668b0dd8dd990583788bfb5f6a4c853c
+32bfe6ca6718801b712e3eadccbb086a9245fe255413893f64a6a4083586f476
+c66112c90d7f9572e2ad81853e8157af2bdeb4d0945c02c046a1f0309ca2c93c
+fae5dde45a34e2d6e27f28af0a606c854eea86bc0ffdf96c77df1b89b39dcb75
+a49c09b7047689abe355ea5c2db4f5ec9c67451a4a3b7b92ba4a202b96b1f314
+ba8935ee7497f1d4375fe761841bbed442591f8154a5e9a62cc7091d594688f8
+70f11412ae503e594e26b3a55d6ec4f24b4c3697343f22ef816bd8fe8d13acc2
+c7544368358b2dc55ec9312ec58c536793ad6488e7a410427ea80bee493881e5
+273efb06078b999d66884513052db33f5304aa8a5e35f2881c3d7a3400fb6302
+9997a24ae45d0a092bc78d0d005da85969615da06213333c212cc42f4ac43902
+750caf338f4cb0c590c4d5fbc21d676c5b5c39e1cf9139ac9b66272abf97f0c5
+863f4a4be9c213b2b1bd67aa45b917d919ff99748ac72fa997a52d5b5958b38c
+40b3b89322dacb31b2ff49b98c2c6a5bcc5d2e26ecd69659bdb88ffa1e226a74
+d4810d6f0a7602d79992067908c058016b9d0d71c6dbf7bd1d13a2cf2280b7d1
+c5e53ae86e6480fe7fb57efc26de2df4cfb7afbf0a0f6fa6060f0b2a3d1c64c3
+cb9367e1f54d49cb808c1adcfbef9b278095cbbd2d3720f5ded47187106ae1bf
+becc86c6676c98a95eda58cad196e2da2bfbb8f11681ba95207df27dda96d8d4
+9943d4554ab623396c271b4a087a3693cae43f944a00bedc65e2c724cc3eef22
+0289695379b2bbfc594837e20963dfd0cf5dca06d8b2452be1fb37fdec2229bc
+e6928948e26596437978defafaf1020386c1523f047ac699802cd674359b35fe
+9bb4dcd3e2b60b54c70a28a5bb780baeb2a12c5e333fa7315b38eda4d03af438
+ed35fecb6f50f5cdd3b9fc770fb3bd6e3564b49737dfb730fce448a1ab008366
+2ed9113051be9589966ec890c750df83b89f07e95a8d34b9970503336941c35a
+fe2f09432285cedc586d70ce35ef5b6b678c2f20b5011e92d285421272c052e5
+4bac78ffdbfa89d1407572c8595e274244973fc29f3c1e3a85fb63ac3ee099c1
+5cae84c945970fbe4f8513055d5803139b8207687d2aaf9eae9f87f459422bce
+6fa9ac1b6b7cf09b8529b93b31abdd91a44278ee39faf29974f34ee4d787ecd0
+e689b5076c9113e4242c5e62c01a847825e402376465ba14d12477a241779bbe
+659eea57ef267d35ca3c88d34326028bd24747ac2cb6fd04e556a99f0bc44d8c
+35137b165465764bc92c9ccb13f77462fdae83fb68a2b36d70719346f56e79ce
+5271fb10bddd68982938c5a4be1601b16b32af11586fac8299a76d37ed286643
+18c06a87721cbfa8c5507b6b40458afd111e0945077f145cbbd2137ceeeac951
+5295ee98c1747f3209f8ce643017911a30a0d184889104fbfb01f9f1062af6e4
+b05b489302bfb2c8ce55839f7d6186b081e9ace3799cb323894e9d9bb59aaa91
+a7d78afca8aa91952fa5cdd1c76f602c95c31d5f83470c62e4fd90fc59f72b92
+2fb22b1f4414753875674e00ec274551d88a5a15341a2f98ab8bd6555edac3fe
+c5415609051a1c0174e69929fb164223b6cc9171758da0a676a5a95986229dd7
+bbc4f2e3685bad35de0cfe6e42dbc304bd763d8f1fb6557de226e00ce34b6f26
+2f7e621342a34409b3390c4b81c50d1de3ca485e1c56a61d6d13127be952cbb4
+617a079b3137af9e35bf8a65634b212199e232775d98f937d6a8bdf4c8aa233d
+5e598dacab2e87363acf2748ca7670fedb4c7f5ce9dd007a0b786e4619a82cfd
+350ba74410e49e93a15f1046b34c2014022ea2a44c31ca910e7105138c60a289
+a1327aef09a7e4dcd4b63ac29038cf2c57c5440a355446fbae9c90686493f055
+81a1fe20b5a1d9c657ed6c3a0ce0d363c86c5d9bf5a608e148d2e512b9234aad
+295ee956dff7ba58bbb2e2b62bf4d86099c6daceed8859c8f666f758e5533dc9
+63a98e679be18626d5cd0e04f2d92922fd0b1657c57bdd39e85adf80019953f7
+bb5f5c4b7d7b3df072a764e6f1e41ad3ad10d23386ab594cfd17d330c9341e39
+e67ff6cfecd3d7e2d4ba48ffac38572b61bef1f6ee7d631967a357b6299159b9
+a20dfdd3aa5b2b7edb1e686fc8081106912a5524b234b740a627741751ccc556
+ddd0424f5491a7a9101cbb197b99e3e9833790f8a7e65b4086e3f6014f0aa6d1
+424e405a5f75a55bc7d55374fcb4c1f50153f6c3bbae87b0f3af8261bc42f7e2
+48ae94a660db17d3db51527264501e8a5ffd8ce7582c71a0c220573a86181d12
+7346b4c7218a87f9578fba642b365d57cda7aa59a770f402f0c8ee2334360193
+1d519df5ac2e9cba36be0cad1a3817ece63b75eec256d0678799ad0a56f3b56c
+db021b6e8431f840e1a2aa9c898c5ee73cec569a8110878612c459e0356d51e5
+8d132726b2c4ca26209156b91950bd344628d7e6aeb7628ae4154ec8cd1cd6e8
+f77d9ae90930427d682fdf44239c52f43d93a5649ab1a94c8691622057946600
+23767f3b2ac73390eda83db23e6d1f0a3b58f09f7d82221d6ef5835a15644a5f
+6359a98de0acf1b8c06ec0de6d3c91804bb86280e11a982f4f238651e1d42335
+eac6301249802389fd8b45c30056fc29add3c4ec7cb007097799549267a45495
+9b83099d6409ef78d725cff9e13a0d5b13b59cf4f6d6a3b2402a1f80c1e35c19
+c448bc004cdc88cb76867b97b0156b0988a152eecf47b30d473f731df755c8cd
+8693cc86e9f0a1a0dd9dcfbbbc724f521dc300ac50bcd78e181113dcfeb74710
+a70d6e6c9920256d613c6b28b46ab08f82b5af67a4f216ccb31d3cda24d91782
+0e7f45b0a135c50e03fc75e622819aa54c02339474366c07129a5c42f4d8f2a0
+e23a38c34d47cddb09a417f2fb90923cbc508655de01235df27c7eb511aa56c2
+4fcd572512586843ac6a80816816e6dc852a36223bef2299c6a478155776e16b
+90031f71d6b7ae2b7227c5d8bbe57d1af693ccdb8ff883730cade61fde996bc0
+6566259af76a91b9b123665686d660ad9de6b292c5bc47eea9d2ea19a2adb49a
+7a1cb138d99e4473cf1a2c52579ddd41c04d302a9214c5ad7ac412be4794fef4
+15a5e11eeb5a84ec3a2e68811820e084a0256dd625e6f6a61241f8904bfefae0
+899891ffcc05cb11519da935e3b9e692ff22927da8c03efccd2aa9311e014ca7
+548db11e8e8c8c6aa5aa6507c70ffaf846a571e76fe421661dbd503141431262
+fd436c79108d8c7ca8c5eec44fd9eebf54181beed85aeff4e1b6ef6a9769e2f4
+ae97060298f4c7fede54865740cda6cbaf52ceb0c963e935fc894318b997add1
+977b0229fd0eb7d13ddabe457b7aa8a2d05b2b99421e01b4d775cdbcc820b1a8
+3ff39d8a7c4d6e4d96125ae2ad688f997e5f221b6ecc4134ce16f9613881f4d9
+049e171462845117cff0bb70669a62570e7361c5efb8aa5b6b858d8aed27c149
+70155e3406acdc035b1b9d1e49664a75c8d52b2e8777fe77aaa8ced567bbc904
+a44305a7b550
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+cleartomark
diff --git a/e2e-tests/cypress/fonts/Type1/UTI_____.afm b/e2e-tests/cypress/fonts/Type1/UTI_____.afm
new file mode 100644
index 00000000..058c34a2
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTI_____.afm
@@ -0,0 +1,1008 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Wed Oct 2 18:58:24 1991
+Comment UniqueID 36549
+Comment VMusage 34122 41014
+FontName Utopia-Italic
+FullName Utopia Italic
+FamilyName Utopia
+Weight Regular
+ItalicAngle -13
+IsFixedPitch false
+FontBBox -166 -250 1205 890
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.001
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 502
+Ascender 742
+Descender -242
+StartCharMetrics 228
+C 32 ; WX 225 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 240 ; N exclam ; B 69 -12 325 707 ;
+C 34 ; WX 402 ; N quotedbl ; B 206 469 489 742 ;
+C 35 ; WX 530 ; N numbersign ; B 89 0 620 668 ;
+C 36 ; WX 530 ; N dollar ; B 66 -109 586 743 ;
+C 37 ; WX 826 ; N percent ; B 133 -25 830 702 ;
+C 38 ; WX 725 ; N ampersand ; B 95 -12 738 680 ;
+C 39 ; WX 216 ; N quoteright ; B 147 482 300 742 ;
+C 40 ; WX 350 ; N parenleft ; B 141 -128 493 692 ;
+C 41 ; WX 350 ; N parenright ; B -11 -128 341 692 ;
+C 42 ; WX 412 ; N asterisk ; B 141 356 493 707 ;
+C 43 ; WX 570 ; N plus ; B 93 0 577 490 ;
+C 44 ; WX 265 ; N comma ; B 46 -134 208 142 ;
+C 45 ; WX 392 ; N hyphen ; B 117 216 376 286 ;
+C 46 ; WX 265 ; N period ; B 82 -12 204 113 ;
+C 47 ; WX 270 ; N slash ; B 35 -15 376 707 ;
+C 48 ; WX 530 ; N zero ; B 95 -12 576 680 ;
+C 49 ; WX 530 ; N one ; B 109 0 464 680 ;
+C 50 ; WX 530 ; N two ; B 33 0 573 680 ;
+C 51 ; WX 530 ; N three ; B 54 -12 559 680 ;
+C 52 ; WX 530 ; N four ; B 67 0 544 668 ;
+C 53 ; WX 530 ; N five ; B 59 -12 585 668 ;
+C 54 ; WX 530 ; N six ; B 91 -12 586 680 ;
+C 55 ; WX 530 ; N seven ; B 165 -12 635 668 ;
+C 56 ; WX 530 ; N eight ; B 81 -12 570 680 ;
+C 57 ; WX 530 ; N nine ; B 86 -12 571 680 ;
+C 58 ; WX 265 ; N colon ; B 82 -12 283 490 ;
+C 59 ; WX 265 ; N semicolon ; B 46 -134 283 490 ;
+C 60 ; WX 570 ; N less ; B 86 1 564 497 ;
+C 61 ; WX 570 ; N equal ; B 93 111 577 389 ;
+C 62 ; WX 570 ; N greater ; B 86 1 564 497 ;
+C 63 ; WX 425 ; N question ; B 150 -12 491 707 ;
+C 64 ; WX 794 ; N at ; B 123 -15 832 707 ;
+C 65 ; WX 624 ; N A ; B -23 0 658 692 ;
+C 66 ; WX 632 ; N B ; B 38 0 671 692 ;
+C 67 ; WX 661 ; N C ; B 114 -15 758 707 ;
+C 68 ; WX 763 ; N D ; B 40 0 802 692 ;
+C 69 ; WX 596 ; N E ; B 38 0 692 692 ;
+C 70 ; WX 571 ; N F ; B 38 0 695 692 ;
+C 71 ; WX 709 ; N G ; B 114 -15 772 707 ;
+C 72 ; WX 775 ; N H ; B 40 0 892 692 ;
+C 73 ; WX 345 ; N I ; B 40 0 463 692 ;
+C 74 ; WX 352 ; N J ; B -43 -119 471 692 ;
+C 75 ; WX 650 ; N K ; B 40 -5 821 692 ;
+C 76 ; WX 565 ; N L ; B 40 0 603 692 ;
+C 77 ; WX 920 ; N M ; B 31 0 1037 692 ;
+C 78 ; WX 763 ; N N ; B 31 0 890 692 ;
+C 79 ; WX 753 ; N O ; B 114 -15 789 707 ;
+C 80 ; WX 614 ; N P ; B 40 0 681 692 ;
+C 81 ; WX 753 ; N Q ; B 114 -203 789 707 ;
+C 82 ; WX 640 ; N R ; B 40 0 677 692 ;
+C 83 ; WX 533 ; N S ; B 69 -15 577 707 ;
+C 84 ; WX 606 ; N T ; B 137 0 743 692 ;
+C 85 ; WX 794 ; N U ; B 166 -15 915 692 ;
+C 86 ; WX 637 ; N V ; B 131 0 821 692 ;
+C 87 ; WX 946 ; N W ; B 121 0 1110 692 ;
+C 88 ; WX 632 ; N X ; B -1 0 770 692 ;
+C 89 ; WX 591 ; N Y ; B 131 0 779 692 ;
+C 90 ; WX 622 ; N Z ; B 15 0 738 692 ;
+C 91 ; WX 330 ; N bracketleft ; B 104 -128 449 692 ;
+C 92 ; WX 390 ; N backslash ; B 124 -15 406 707 ;
+C 93 ; WX 330 ; N bracketright ; B 14 -128 359 692 ;
+C 94 ; WX 570 ; N asciicircum ; B 118 228 582 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 216 ; N quoteleft ; B 165 488 318 748 ;
+C 97 ; WX 561 ; N a ; B 66 -12 598 502 ;
+C 98 ; WX 559 ; N b ; B 82 -12 592 742 ;
+C 99 ; WX 441 ; N c ; B 81 -12 500 502 ;
+C 100 ; WX 587 ; N d ; B 72 -12 647 742 ;
+C 101 ; WX 453 ; N e ; B 80 -12 506 502 ;
+C 102 ; WX 315 ; N f ; B -72 -242 539 742 ; L i fi ; L l fl ;
+C 103 ; WX 499 ; N g ; B 30 -242 608 512 ;
+C 104 ; WX 607 ; N h ; B 92 -12 623 742 ;
+C 105 ; WX 317 ; N i ; B 114 -12 363 715 ;
+C 106 ; WX 309 ; N j ; B -60 -242 365 715 ;
+C 107 ; WX 545 ; N k ; B 92 -12 602 742 ;
+C 108 ; WX 306 ; N l ; B 111 -12 366 742 ;
+C 109 ; WX 912 ; N m ; B 98 -12 929 502 ;
+C 110 ; WX 618 ; N n ; B 98 -12 635 502 ;
+C 111 ; WX 537 ; N o ; B 84 -12 557 502 ;
+C 112 ; WX 590 ; N p ; B 57 -242 621 502 ;
+C 113 ; WX 559 ; N q ; B 73 -242 602 525 ;
+C 114 ; WX 402 ; N r ; B 104 -12 483 502 ;
+C 115 ; WX 389 ; N s ; B 54 -12 432 502 ;
+C 116 ; WX 341 ; N t ; B 119 -12 439 616 ;
+C 117 ; WX 618 ; N u ; B 124 -12 644 502 ;
+C 118 ; WX 510 ; N v ; B 119 -12 563 502 ;
+C 119 ; WX 785 ; N w ; B 122 -12 843 502 ;
+C 120 ; WX 516 ; N x ; B 31 -12 566 502 ;
+C 121 ; WX 468 ; N y ; B -5 -242 540 502 ;
+C 122 ; WX 468 ; N z ; B 39 -12 518 490 ;
+C 123 ; WX 340 ; N braceleft ; B 135 -128 458 692 ;
+C 124 ; WX 270 ; N bar ; B 165 -250 233 750 ;
+C 125 ; WX 340 ; N braceright ; B 15 -128 337 692 ;
+C 126 ; WX 570 ; N asciitilde ; B 133 176 557 318 ;
+C 161 ; WX 240 ; N exclamdown ; B 17 -217 273 502 ;
+C 162 ; WX 530 ; N cent ; B 129 -21 598 669 ;
+C 163 ; WX 530 ; N sterling ; B 44 0 584 680 ;
+C 164 ; WX 100 ; N fraction ; B -166 -24 404 698 ;
+C 165 ; WX 530 ; N yen ; B 107 0 680 668 ;
+C 166 ; WX 530 ; N florin ; B 39 -135 623 691 ;
+C 167 ; WX 530 ; N section ; B 90 -115 568 707 ;
+C 168 ; WX 530 ; N currency ; B 91 90 571 578 ;
+C 169 ; WX 216 ; N quotesingle ; B 196 469 309 742 ;
+C 170 ; WX 402 ; N quotedblleft ; B 169 488 508 748 ;
+C 171 ; WX 462 ; N guillemotleft ; B 114 41 505 435 ;
+C 172 ; WX 277 ; N guilsinglleft ; B 106 41 302 435 ;
+C 173 ; WX 277 ; N guilsinglright ; B 79 41 275 435 ;
+C 174 ; WX 607 ; N fi ; B -72 -242 624 742 ;
+C 175 ; WX 603 ; N fl ; B -72 -242 663 742 ;
+C 177 ; WX 500 ; N endash ; B 47 221 559 279 ;
+C 178 ; WX 500 ; N dagger ; B 136 -125 554 717 ;
+C 179 ; WX 490 ; N daggerdbl ; B 74 -119 544 717 ;
+C 180 ; WX 265 ; N periodcentered ; B 124 187 246 312 ;
+C 182 ; WX 560 ; N paragraph ; B 144 -101 672 692 ;
+C 183 ; WX 500 ; N bullet ; B 145 192 464 512 ;
+C 184 ; WX 216 ; N quotesinglbase ; B 28 -109 181 151 ;
+C 185 ; WX 402 ; N quotedblbase ; B 28 -109 367 151 ;
+C 186 ; WX 402 ; N quotedblright ; B 142 484 481 744 ;
+C 187 ; WX 462 ; N guillemotright ; B 64 41 455 435 ;
+C 188 ; WX 1000 ; N ellipsis ; B 120 -12 908 113 ;
+C 189 ; WX 1200 ; N perthousand ; B 133 -25 1205 702 ;
+C 191 ; WX 425 ; N questiondown ; B 38 -217 379 502 ;
+C 193 ; WX 400 ; N grave ; B 181 542 403 723 ;
+C 194 ; WX 400 ; N acute ; B 249 542 471 723 ;
+C 195 ; WX 400 ; N circumflex ; B 222 546 519 720 ;
+C 196 ; WX 400 ; N tilde ; B 172 563 527 682 ;
+C 197 ; WX 400 ; N macron ; B 228 597 524 656 ;
+C 198 ; WX 400 ; N breve ; B 262 568 536 698 ;
+C 199 ; WX 402 ; N dotaccent ; B 287 570 394 680 ;
+C 200 ; WX 400 ; N dieresis ; B 207 572 522 682 ;
+C 202 ; WX 400 ; N ring ; B 221 550 437 752 ;
+C 203 ; WX 400 ; N cedilla ; B 97 -230 276 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 211 546 490 750 ;
+C 206 ; WX 350 ; N ogonek ; B 103 -219 283 0 ;
+C 207 ; WX 400 ; N caron ; B 248 557 545 731 ;
+C 208 ; WX 1000 ; N emdash ; B 47 221 1059 279 ;
+C 225 ; WX 880 ; N AE ; B -53 0 976 692 ;
+C 227 ; WX 425 ; N ordfeminine ; B 112 265 495 590 ;
+C 232 ; WX 571 ; N Lslash ; B 46 0 609 692 ;
+C 233 ; WX 753 ; N Oslash ; B 114 -45 789 736 ;
+C 234 ; WX 1020 ; N OE ; B 114 0 1116 692 ;
+C 235 ; WX 389 ; N ordmasculine ; B 121 265 455 590 ;
+C 241 ; WX 779 ; N ae ; B 69 -12 832 514 ;
+C 245 ; WX 317 ; N dotlessi ; B 114 -12 334 502 ;
+C 248 ; WX 318 ; N lslash ; B 80 -12 411 742 ;
+C 249 ; WX 537 ; N oslash ; B 84 -39 557 529 ;
+C 250 ; WX 806 ; N oe ; B 84 -12 859 502 ;
+C 251 ; WX 577 ; N germandbls ; B -72 -242 665 742 ;
+C -1 ; WX 370 ; N onesuperior ; B 125 272 361 680 ;
+C -1 ; WX 570 ; N minus ; B 93 221 577 279 ;
+C -1 ; WX 400 ; N degree ; B 187 404 463 680 ;
+C -1 ; WX 537 ; N oacute ; B 84 -12 557 723 ;
+C -1 ; WX 753 ; N Odieresis ; B 114 -15 789 848 ;
+C -1 ; WX 537 ; N odieresis ; B 84 -12 567 682 ;
+C -1 ; WX 596 ; N Eacute ; B 38 0 692 890 ;
+C -1 ; WX 618 ; N ucircumflex ; B 124 -12 644 720 ;
+C -1 ; WX 890 ; N onequarter ; B 132 -24 840 698 ;
+C -1 ; WX 570 ; N logicalnot ; B 93 102 577 389 ;
+C -1 ; WX 596 ; N Ecircumflex ; B 38 0 692 876 ;
+C -1 ; WX 890 ; N onehalf ; B 106 -24 847 698 ;
+C -1 ; WX 753 ; N Otilde ; B 114 -15 789 842 ;
+C -1 ; WX 618 ; N uacute ; B 124 -12 644 723 ;
+C -1 ; WX 453 ; N eacute ; B 80 -12 518 723 ;
+C -1 ; WX 317 ; N iacute ; B 114 -12 415 723 ;
+C -1 ; WX 596 ; N Egrave ; B 38 0 692 890 ;
+C -1 ; WX 317 ; N icircumflex ; B 114 -12 418 720 ;
+C -1 ; WX 618 ; N mu ; B 46 -232 644 502 ;
+C -1 ; WX 270 ; N brokenbar ; B 165 -175 233 675 ;
+C -1 ; WX 584 ; N thorn ; B 51 -242 615 700 ;
+C -1 ; WX 624 ; N Aring ; B -23 0 658 861 ;
+C -1 ; WX 468 ; N yacute ; B -5 -242 540 723 ;
+C -1 ; WX 591 ; N Ydieresis ; B 131 0 779 848 ;
+C -1 ; WX 1100 ; N trademark ; B 126 277 1129 692 ;
+C -1 ; WX 836 ; N registered ; B 126 -15 854 707 ;
+C -1 ; WX 537 ; N ocircumflex ; B 84 -12 557 720 ;
+C -1 ; WX 624 ; N Agrave ; B -23 0 658 890 ;
+C -1 ; WX 533 ; N Scaron ; B 69 -15 596 888 ;
+C -1 ; WX 794 ; N Ugrave ; B 166 -15 915 890 ;
+C -1 ; WX 596 ; N Edieresis ; B 38 0 692 848 ;
+C -1 ; WX 794 ; N Uacute ; B 166 -15 915 890 ;
+C -1 ; WX 537 ; N otilde ; B 84 -12 560 682 ;
+C -1 ; WX 618 ; N ntilde ; B 98 -12 635 682 ;
+C -1 ; WX 468 ; N ydieresis ; B -5 -242 556 682 ;
+C -1 ; WX 624 ; N Aacute ; B -23 0 658 890 ;
+C -1 ; WX 537 ; N eth ; B 82 -12 556 742 ;
+C -1 ; WX 561 ; N acircumflex ; B 66 -12 598 720 ;
+C -1 ; WX 561 ; N aring ; B 66 -12 598 752 ;
+C -1 ; WX 753 ; N Ograve ; B 114 -15 789 890 ;
+C -1 ; WX 441 ; N ccedilla ; B 81 -230 500 502 ;
+C -1 ; WX 570 ; N multiply ; B 123 22 567 478 ;
+C -1 ; WX 570 ; N divide ; B 93 25 577 475 ;
+C -1 ; WX 370 ; N twosuperior ; B 70 272 434 680 ;
+C -1 ; WX 763 ; N Ntilde ; B 31 0 890 842 ;
+C -1 ; WX 618 ; N ugrave ; B 124 -12 644 723 ;
+C -1 ; WX 794 ; N Ucircumflex ; B 166 -15 915 876 ;
+C -1 ; WX 624 ; N Atilde ; B -23 0 658 842 ;
+C -1 ; WX 468 ; N zcaron ; B 39 -12 519 731 ;
+C -1 ; WX 317 ; N idieresis ; B 114 -12 433 682 ;
+C -1 ; WX 624 ; N Acircumflex ; B -23 0 658 876 ;
+C -1 ; WX 345 ; N Icircumflex ; B 40 0 488 876 ;
+C -1 ; WX 591 ; N Yacute ; B 131 0 779 890 ;
+C -1 ; WX 753 ; N Oacute ; B 114 -15 789 890 ;
+C -1 ; WX 624 ; N Adieresis ; B -23 0 658 848 ;
+C -1 ; WX 622 ; N Zcaron ; B 15 0 738 888 ;
+C -1 ; WX 561 ; N agrave ; B 66 -12 598 723 ;
+C -1 ; WX 370 ; N threesuperior ; B 94 265 424 680 ;
+C -1 ; WX 537 ; N ograve ; B 84 -12 557 723 ;
+C -1 ; WX 890 ; N threequarters ; B 140 -24 851 698 ;
+C -1 ; WX 770 ; N Eth ; B 47 0 809 692 ;
+C -1 ; WX 570 ; N plusminus ; B 93 0 577 556 ;
+C -1 ; WX 618 ; N udieresis ; B 124 -12 644 682 ;
+C -1 ; WX 453 ; N edieresis ; B 80 -12 525 682 ;
+C -1 ; WX 561 ; N aacute ; B 66 -12 598 723 ;
+C -1 ; WX 317 ; N igrave ; B 114 -12 352 723 ;
+C -1 ; WX 345 ; N Idieresis ; B 40 0 496 848 ;
+C -1 ; WX 561 ; N adieresis ; B 66 -12 598 682 ;
+C -1 ; WX 345 ; N Iacute ; B 40 0 486 890 ;
+C -1 ; WX 836 ; N copyright ; B 126 -15 854 707 ;
+C -1 ; WX 345 ; N Igrave ; B 40 0 463 890 ;
+C -1 ; WX 661 ; N Ccedilla ; B 114 -230 758 707 ;
+C -1 ; WX 389 ; N scaron ; B 54 -12 492 731 ;
+C -1 ; WX 453 ; N egrave ; B 80 -12 506 723 ;
+C -1 ; WX 753 ; N Ocircumflex ; B 114 -15 789 876 ;
+C -1 ; WX 604 ; N Thorn ; B 40 0 651 692 ;
+C -1 ; WX 561 ; N atilde ; B 66 -12 598 682 ;
+C -1 ; WX 794 ; N Udieresis ; B 166 -15 915 848 ;
+C -1 ; WX 453 ; N ecircumflex ; B 80 -12 510 720 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 690
+
+KPX A y -20
+KPX A x 10
+KPX A w -30
+KPX A v -30
+KPX A u -10
+KPX A t -6
+KPX A s 15
+KPX A r -12
+KPX A quoteright -110
+KPX A quotedblright -110
+KPX A q 10
+KPX A p -12
+KPX A o -10
+KPX A n -18
+KPX A m -18
+KPX A l -18
+KPX A j 6
+KPX A h -6
+KPX A d 10
+KPX A c -6
+KPX A b -6
+KPX A a 12
+KPX A Y -76
+KPX A X -8
+KPX A W -80
+KPX A V -90
+KPX A U -60
+KPX A T -72
+KPX A Q -30
+KPX A O -30
+KPX A G -30
+KPX A C -30
+
+KPX B y -6
+KPX B u -20
+KPX B r -15
+KPX B quoteright -40
+KPX B quotedblright -30
+KPX B o 6
+KPX B l -20
+KPX B k -15
+KPX B i -12
+KPX B h -15
+KPX B e 6
+KPX B a 12
+KPX B W -20
+KPX B V -50
+KPX B U -50
+KPX B T -20
+
+KPX C z -6
+KPX C y -18
+KPX C u -18
+KPX C quotedblright 20
+KPX C i -5
+KPX C e -6
+KPX C a -6
+
+KPX D y 18
+KPX D u -10
+KPX D quoteright -40
+KPX D quotedblright -50
+KPX D period -30
+KPX D o 6
+KPX D i 6
+KPX D h -25
+KPX D e 6
+KPX D comma -20
+KPX D a 6
+KPX D Y -70
+KPX D W -50
+KPX D V -60
+
+KPX E z -6
+KPX E y -18
+KPX E x 5
+KPX E w -20
+KPX E v -18
+KPX E u -24
+KPX E t -18
+KPX E s 5
+KPX E r -6
+KPX E quoteright 10
+KPX E quotedblright 10
+KPX E q 10
+KPX E period 10
+KPX E p -12
+KPX E o -6
+KPX E n -12
+KPX E m -12
+KPX E l -12
+KPX E k -10
+KPX E j -6
+KPX E i -12
+KPX E g -12
+KPX E e 5
+KPX E d 10
+KPX E comma 10
+KPX E b -6
+
+KPX F y -12
+KPX F u -30
+KPX F r -18
+KPX F quoteright 15
+KPX F quotedblright 35
+KPX F period -180
+KPX F o -30
+KPX F l -6
+KPX F i -12
+KPX F e -30
+KPX F comma -170
+KPX F a -30
+KPX F A -45
+
+KPX G y -16
+KPX G u -22
+KPX G r -22
+KPX G quoteright -20
+KPX G quotedblright -20
+KPX G o 10
+KPX G n -22
+KPX G l -24
+KPX G i -12
+KPX G h -18
+KPX G e 10
+KPX G a 5
+
+KPX H y -18
+KPX H u -30
+KPX H quoteright 10
+KPX H quotedblright 10
+KPX H o -12
+KPX H i -12
+KPX H e -12
+KPX H a -12
+
+KPX I z -20
+KPX I y -6
+KPX I x -6
+KPX I w -30
+KPX I v -30
+KPX I u -30
+KPX I t -18
+KPX I s -18
+KPX I r -12
+KPX I quoteright 10
+KPX I quotedblright 10
+KPX I p -18
+KPX I o -12
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I g -12
+KPX I f -6
+KPX I d -6
+KPX I c -12
+KPX I b -6
+KPX I a -6
+
+KPX J y -12
+KPX J u -36
+KPX J quoteright 6
+KPX J quotedblright 15
+KPX J o -36
+KPX J i -30
+KPX J e -36
+KPX J braceright 10
+KPX J a -36
+
+KPX K y -40
+KPX K w -30
+KPX K v -20
+KPX K u -24
+KPX K r -12
+KPX K quoteright 25
+KPX K quotedblright 40
+KPX K o -24
+KPX K n -18
+KPX K i -6
+KPX K h 6
+KPX K e -12
+KPX K a -6
+KPX K Q -24
+KPX K O -24
+KPX K G -24
+KPX K C -24
+
+KPX L y -55
+KPX L w -30
+KPX L u -18
+KPX L quoteright -110
+KPX L quotedblright -110
+KPX L l -16
+KPX L j -18
+KPX L i -18
+KPX L a 10
+KPX L Y -80
+KPX L W -90
+KPX L V -110
+KPX L U -42
+KPX L T -80
+KPX L Q -48
+KPX L O -48
+KPX L G -48
+KPX L C -48
+KPX L A 30
+
+KPX M y -18
+KPX M u -24
+KPX M quoteright 6
+KPX M quotedblright 15
+KPX M o -25
+KPX M n -12
+KPX M j -18
+KPX M i -12
+KPX M e -20
+KPX M d -10
+KPX M c -20
+KPX M a -6
+
+KPX N y -18
+KPX N u -24
+KPX N quoteright 10
+KPX N quotedblright 10
+KPX N o -25
+KPX N i -12
+KPX N e -20
+KPX N a -22
+
+KPX O z -6
+KPX O y 12
+KPX O w -10
+KPX O v -10
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O r -6
+KPX O quoteright -40
+KPX O quotedblright -40
+KPX O q 5
+KPX O period -20
+KPX O p -6
+KPX O n -6
+KPX O m -6
+KPX O l -20
+KPX O k -10
+KPX O j -6
+KPX O h -10
+KPX O g -6
+KPX O e 5
+KPX O d 6
+KPX O comma -10
+KPX O c 5
+KPX O b -6
+KPX O a 5
+KPX O Y -75
+KPX O X -30
+KPX O W -40
+KPX O V -60
+KPX O T -48
+KPX O A -18
+
+KPX P y 6
+KPX P u -18
+KPX P t -6
+KPX P s -24
+KPX P r -6
+KPX P period -220
+KPX P o -24
+KPX P n -12
+KPX P l -25
+KPX P h -15
+KPX P e -24
+KPX P comma -220
+KPX P a -24
+KPX P I -30
+KPX P H -30
+KPX P E -30
+KPX P A -75
+
+KPX Q u -6
+KPX Q quoteright -40
+KPX Q quotedblright -50
+KPX Q a -6
+KPX Q Y -70
+KPX Q X -12
+KPX Q W -35
+KPX Q V -60
+KPX Q U -35
+KPX Q T -36
+KPX Q A -18
+
+KPX R y -14
+KPX R u -12
+KPX R quoteright -30
+KPX R quotedblright -20
+KPX R o -12
+KPX R hyphen -20
+KPX R e -12
+KPX R Y -50
+KPX R W -30
+KPX R V -40
+KPX R U -40
+KPX R T -30
+KPX R Q -10
+KPX R O -10
+KPX R G -10
+KPX R C -10
+KPX R A -6
+
+KPX S y -30
+KPX S w -30
+KPX S v -30
+KPX S u -18
+KPX S t -30
+KPX S r -20
+KPX S quoteright -38
+KPX S quotedblright -30
+KPX S p -18
+KPX S n -24
+KPX S m -24
+KPX S l -30
+KPX S k -24
+KPX S j -25
+KPX S i -30
+KPX S h -30
+KPX S e -6
+
+KPX T z -70
+KPX T y -60
+KPX T w -64
+KPX T u -74
+KPX T semicolon -36
+KPX T s -72
+KPX T r -64
+KPX T quoteright 45
+KPX T quotedblright 50
+KPX T period -100
+KPX T parenright 54
+KPX T o -90
+KPX T m -64
+KPX T i -34
+KPX T hyphen -100
+KPX T endash -60
+KPX T emdash -60
+KPX T e -90
+KPX T comma -110
+KPX T colon -10
+KPX T bracketright 45
+KPX T braceright 54
+KPX T a -90
+KPX T Y 12
+KPX T X 18
+KPX T W 6
+KPX T T 18
+KPX T Q -12
+KPX T O -12
+KPX T G -12
+KPX T C -12
+KPX T A -56
+
+KPX U z -30
+KPX U x -40
+KPX U t -24
+KPX U s -30
+KPX U r -30
+KPX U quoteright 10
+KPX U quotedblright 10
+KPX U p -40
+KPX U n -45
+KPX U m -45
+KPX U l -12
+KPX U k -12
+KPX U i -24
+KPX U h -6
+KPX U g -30
+KPX U d -40
+KPX U c -35
+KPX U b -6
+KPX U a -40
+KPX U A -45
+
+KPX V y -46
+KPX V u -42
+KPX V semicolon -35
+KPX V r -50
+KPX V quoteright 75
+KPX V quotedblright 70
+KPX V period -130
+KPX V parenright 64
+KPX V o -62
+KPX V i -10
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -52
+KPX V comma -120
+KPX V colon -18
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -60
+KPX V T 6
+KPX V A -70
+
+KPX W y -42
+KPX W u -56
+KPX W t -20
+KPX W semicolon -28
+KPX W r -40
+KPX W quoteright 55
+KPX W quotedblright 60
+KPX W period -108
+KPX W parenright 64
+KPX W o -60
+KPX W m -35
+KPX W i -10
+KPX W hyphen -40
+KPX W endash -2
+KPX W emdash -10
+KPX W e -54
+KPX W d -50
+KPX W comma -108
+KPX W colon -28
+KPX W bracketright 55
+KPX W braceright 64
+KPX W a -60
+KPX W T 12
+KPX W Q -10
+KPX W O -10
+KPX W G -10
+KPX W C -10
+KPX W A -58
+
+KPX X y -35
+KPX X u -30
+KPX X r -6
+KPX X quoteright 35
+KPX X quotedblright 15
+KPX X i -6
+KPX X e -10
+KPX X a 5
+KPX X Y -6
+KPX X W -6
+KPX X Q -30
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A -18
+
+KPX Y v -50
+KPX Y u -58
+KPX Y t -32
+KPX Y semicolon -36
+KPX Y quoteright 65
+KPX Y quotedblright 70
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -72
+KPX Y l 10
+KPX Y hyphen -95
+KPX Y endash -20
+KPX Y emdash -20
+KPX Y e -72
+KPX Y d -80
+KPX Y comma -80
+KPX Y colon -36
+KPX Y bracketright 64
+KPX Y braceright 75
+KPX Y a -82
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 6
+KPX Y T 25
+KPX Y Q -5
+KPX Y O -5
+KPX Y G -5
+KPX Y C -5
+KPX Y A -36
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -12
+KPX Z quoteright 10
+KPX Z quotedblright 10
+KPX Z o -6
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -30
+KPX Z O -30
+KPX Z G -30
+KPX Z C -30
+KPX Z A 12
+
+KPX a quoteright -40
+KPX a quotedblright -40
+
+KPX b y -6
+KPX b w -15
+KPX b v -15
+KPX b quoteright -50
+KPX b quotedblright -50
+KPX b period -40
+KPX b comma -30
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 54
+KPX braceleft J 80
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 54
+KPX bracketleft J 80
+
+KPX c quoteright -20
+KPX c quotedblright -20
+
+KPX colon space -30
+
+KPX comma space -40
+KPX comma quoteright -80
+KPX comma quotedblright -80
+
+KPX d quoteright -12
+KPX d quotedblright -12
+
+KPX e x -10
+KPX e w -10
+KPX e quoteright -30
+KPX e quotedblright -30
+
+KPX f quoteright 110
+KPX f quotedblright 110
+KPX f period -20
+KPX f parenright 100
+KPX f comma -20
+KPX f bracketright 90
+KPX f braceright 90
+
+KPX g y 30
+KPX g p 12
+KPX g f 42
+
+KPX h quoteright -80
+KPX h quotedblright -80
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -35
+KPX j comma -20
+
+KPX k quoteright -30
+KPX k quotedblright -50
+
+KPX m quoteright -80
+KPX m quotedblright -80
+
+KPX n quoteright -80
+KPX n quotedblright -80
+
+KPX o z -10
+KPX o y -20
+KPX o x -20
+KPX o w -30
+KPX o v -35
+KPX o quoteright -60
+KPX o quotedblright -50
+KPX o period -30
+KPX o comma -20
+
+KPX p z -10
+KPX p w -15
+KPX p quoteright -50
+KPX p quotedblright -70
+KPX p period -30
+KPX p comma -20
+
+KPX parenleft Y 75
+KPX parenleft W 75
+KPX parenleft V 75
+KPX parenleft T 64
+KPX parenleft J 80
+
+KPX period space -40
+KPX period quoteright -80
+KPX period quotedblright -80
+
+KPX q quoteright -20
+KPX q quotedblright -30
+KPX q period -20
+KPX q comma -10
+
+KPX quotedblleft z -30
+KPX quotedblleft x -40
+KPX quotedblleft w -12
+KPX quotedblleft v -12
+KPX quotedblleft u -12
+KPX quotedblleft t -12
+KPX quotedblleft s -30
+KPX quotedblleft r -12
+KPX quotedblleft q -40
+KPX quotedblleft p -12
+KPX quotedblleft o -30
+KPX quotedblleft n -12
+KPX quotedblleft m -12
+KPX quotedblleft l 10
+KPX quotedblleft k 10
+KPX quotedblleft h 10
+KPX quotedblleft g -30
+KPX quotedblleft e -40
+KPX quotedblleft d -40
+KPX quotedblleft c -40
+KPX quotedblleft b 24
+KPX quotedblleft a -60
+KPX quotedblleft Y 12
+KPX quotedblleft X 28
+KPX quotedblleft W 28
+KPX quotedblleft V 28
+KPX quotedblleft T 36
+KPX quotedblleft A -90
+
+KPX quotedblright space -40
+KPX quotedblright period -100
+KPX quotedblright comma -100
+
+KPX quoteleft z -30
+KPX quoteleft y -10
+KPX quoteleft x -40
+KPX quoteleft w -12
+KPX quoteleft v -12
+KPX quoteleft u -12
+KPX quoteleft t -12
+KPX quoteleft s -30
+KPX quoteleft r -12
+KPX quoteleft quoteleft -18
+KPX quoteleft q -30
+KPX quoteleft p -12
+KPX quoteleft o -30
+KPX quoteleft n -12
+KPX quoteleft m -12
+KPX quoteleft l 10
+KPX quoteleft k 10
+KPX quoteleft h 10
+KPX quoteleft g -30
+KPX quoteleft e -30
+KPX quoteleft d -30
+KPX quoteleft c -30
+KPX quoteleft b 24
+KPX quoteleft a -45
+KPX quoteleft Y 12
+KPX quoteleft X 28
+KPX quoteleft W 28
+KPX quoteleft V 28
+KPX quoteleft T 36
+KPX quoteleft A -90
+
+KPX quoteright v -35
+KPX quoteright t -35
+KPX quoteright space -40
+KPX quoteright s -55
+KPX quoteright r -25
+KPX quoteright quoteright -18
+KPX quoteright period -100
+KPX quoteright m -25
+KPX quoteright l -12
+KPX quoteright d -70
+KPX quoteright comma -100
+
+KPX r y 18
+KPX r w 6
+KPX r v 6
+KPX r t 8
+KPX r quotedblright -15
+KPX r q -24
+KPX r period -120
+KPX r o -6
+KPX r l -20
+KPX r k -20
+KPX r hyphen -30
+KPX r h -20
+KPX r f 8
+KPX r emdash -20
+KPX r e -26
+KPX r d -26
+KPX r comma -110
+KPX r c -12
+KPX r a -20
+
+KPX s quoteright -40
+KPX s quotedblright -45
+
+KPX semicolon space -30
+
+KPX space quotesinglbase -30
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -30
+KPX space Y -70
+KPX space W -70
+KPX space V -70
+
+KPX t quoteright 10
+KPX t quotedblright -10
+
+KPX u quoteright -55
+KPX u quotedblright -50
+
+KPX v quoteright -20
+KPX v quotedblright -30
+KPX v q -6
+KPX v period -70
+KPX v o -6
+KPX v e -6
+KPX v d -6
+KPX v comma -70
+KPX v c -6
+KPX v a -6
+
+KPX w quoteright -20
+KPX w quotedblright -30
+KPX w period -62
+KPX w comma -62
+
+KPX x y 12
+KPX x w -6
+KPX x quoteright -40
+KPX x quotedblright -50
+KPX x q -6
+KPX x o -6
+KPX x e -6
+KPX x d -6
+KPX x c -6
+
+KPX y quoteright -10
+KPX y quotedblright -20
+KPX y period -70
+KPX y emdash 40
+KPX y comma -60
+
+KPX z quoteright -40
+KPX z quotedblright -50
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/UTI_____.pfa b/e2e-tests/cypress/fonts/Type1/UTI_____.pfa
new file mode 100644
index 00000000..de07ce5d
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTI_____.pfa
@@ -0,0 +1,1165 @@
+%!PS-AdobeFont-1.0: Utopia-Italic 001.001
+%%CreationDate: Wed Oct 2 18:58:18 1991
+%%VMusage: 34122 41014
+%% Utopia is a registered trademark of Adobe Systems Incorporated.
+11 dict begin
+/FontInfo 10 dict dup begin
+/version (001.001) readonly def
+/Notice (Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.) readonly def
+/FullName (Utopia Italic) readonly def
+/FamilyName (Utopia) readonly def
+/Weight (Regular) readonly def
+/ItalicAngle -13 def
+/isFixedPitch false def
+/UnderlinePosition -100 def
+/UnderlineThickness 50 def
+end readonly def
+/FontName /Utopia-Italic def
+/Encoding StandardEncoding def
+/PaintType 0 def
+/FontType 1 def
+/FontMatrix [0.001 0 0 0.001 0 0] readonly def
+/UniqueID 36549 def
+/FontBBox{-166 -250 1205 890}readonly def
+currentdict end
+currentfile eexec
+a9994c496574b9cb23f8cbcd64a4a16861c70f0f2da82f0c7b06ceacd6521bb0
+cc26f1cf47836cbab75757d7a81793f43e56cc8f22f926da04d715ab6ff2e257
+5a135dabbaaa58f31f548cbe8a76c69e2402589b9e5e46e757f06bf2eddbbe6e
+e48a624cbe1c4840a338e90f7efbe9f2194aee1c869bc4cd76e2f1937d78e207
+d8149c05b50ef0bb361f5905977c40be7d4dad07b54e087896acda5aa70ab803
+9dfc55a73134c7f1c9be9028d3ec6ccb0fbb8fda52bba4d7551a8124e68481d0
+775ed7f8ec68d8073bfdd3b67f72ec68634ffc57727e16b9aba841546ae54d99
+b60e227682315510edda09bd6ead4d1652f449d737592c44bb178689a3840169
+53d899636686efc6f838b19f966be5f833f5e7d41af38a899df96fdce1ebc116
+9b0ec87c930d9fdcbab7e74880e9693a24de9c67dbb0dd75b3dbd113b079c490
+60018433ccc06ac1df33fc090c4642fd5225fa0a188c131974ca8820319704dd
+14b1719b958779d1475d92e712322eb2e6a79d652c4e3f5833aca3091675fef3
+fde103446e565428267b009d87bb7d6bc40f46b498b19bf1223aba33079e41ba
+a9561254a9df97a48d71015b6a24cf952539f664f172d565f39a55e063154d80
+a960cd56c34011fe26e8849c6677427b2c7e728cd16272363d76661b3ab8bb2c
+85c20b747519a4431421d4c83783feb896c45688ff7f824381cff5654a1bfc68
+6f4894e1d265455fc00064d08c37dc3a47b59a5b2d14dd893a2b871492b07c4a
+591695c8df012ef46a750d6cb7fd86696e7ead280648bd737e9ea490140f3b1b
+1dffab2ff8e085a8b1c78e4b9ae9cee277a29233cec5ab2588d1cdaf4bbb6512
+e1b1476c68fd3db18ecc2536968ebebf4333c2c3709ca354f21fffc38c3858c0
+561378a701158ed0201a3b236ca9cb7f7528d1e5e07a9bfab04f8d0fa3d12405
+5ba2fa9dff76f41b980bfb36e8db1300f7172c902dcb9f5f0bba3cb9c212bf31
+76cb802ddebe556f4610021fa3700d476a2be2a66ae6c658fd0a95bc1b9629ad
+03fe4e775ffdfa9f87a274fd904c0db5beae258f9af213f257db2be0265b964a
+681fc4d2855f5356fc87576d847db274fed9ca5bb4961ffcf53f28b673f50e31
+c3c634604e70fd0895af711c9fa302572eede069f65502f68884a90d9f1ed7f1
+cdd95fc73ca6ae925cce4e01e49934539739f082f0ff1398deafda84c3d9b1e0
+cafdefe5bf321670bfa286078bc066bb1895b1a3f0253abc079b753c8f020fae
+ceb70acca52c4fd011825ba3ab32ce9c7ec263b4828d979b57588c48de210085
+e56eeb002dcecc967c95f219728511ccc6c51817125e1afdd3e4a9b35c8e5551
+06a026900808425393fa10ea4b6f4513f0af047d326d80462d36c2c7d033125c
+ab05bd0d3058b69f11a1d16f23345b3c97e444ffcfb4b3496743d9f6abca53e1
+b702a359cedda4d78e1a771989290f86ceee70502c574d6582a64045acbc33ed
+436871e9beaaefb12174806003037e8616b5f12ff470cc6e08b0af6789b9614e
+6ce98441b72e79b0c613e4a01835cb4d61c9cdecde9a66184af5876f0c6bea8d
+8280472a4c6a2f38fa95b7c475ce75f8acd6850e8e82dcbbbb96e7b389612cfb
+082c280fc4babf5da29e26f1f163d5ce353c9eb61122a10634ca3224261089f5
+e999b1d591d92216afe16a409f7735043885cd0218b4bb7289fb4547fdca93a1
+30280807d3c5fe5abf6d6ce88af04beb42ee031be89efad865f05daadc0ec4f7
+871c1a8d78e51a453a8c4b0ea780268639009c2ed6741fef8c598248ea9a2c5d
+f3ab0401a05b0fc35f66bcd6c812087ac03be6cee22ff97df7152681279de3bc
+be0a5c3574240a561f667c87a9fd065b788c9bf4e38dce8abc94f29acfef1933
+6568527336b48570be215a8ed6c54642ff28aa5edebe0228873c55a65430ff76
+8502e836e97ae8f2f6ac4dc5eee76beba71cb16389399a34535296a0175974a8
+c44918f7c668fcb07a72c0771fa8c49ab7d238611d8c2eb17a2a6f7bdce45b45
+2616dd51ec465225f83e886237b273488f4ec220d1837272df8a504273fdd95d
+ad88d197a3a6e92519eef80d7c18b31df15b9530d96f9982c1f7f870a95272c1
+325b394ec41c9e7fd2f13ce4adda1cc0cca0ad9b068e4d1c92e17d1ebbd4e365
+f7f4a9c547a81470f3ea6386dae456937b302a2e6f9c4553a39ea280895ad232
+57b00bf0b1c9c38746dd5438067ea5ba365b1a4509a29bf938f3c028cd2d0dbe
+441c1e881135b7abd353f1b14f225001812697df59ae92b11a92eb68b41a6e62
+637f097d4b079e266a81e6bc30a69d5741ebc518a6bab96ee0f79b9cdebb1d35
+04f88ac6a46db5e7be43a3c290399f00e2f78da44d348c66d7d12ce239b30dc0
+8f916c875e3e54cef7f5f1b966f606d8fa3e072d4a417b3cbd43bd3b644b2928
+626f29f4d369502b63ff43c7be25193bf11216db04ddc2f39faf825a559576be
+427bdb147daa9d9dc8a9839daba32fbfbc8d9fe41bae9d7b15d8106ae01e2cf0
+d59c6f8c2cf498fe7518fa7b84f38c6f4be7cfcfd0e75908650f52a107ab7d7d
+2bbb6361d5378232b80edb45dfc91750eadad74c9b42a19339d0227d83caaba1
+72a43d3e5c64129284a50ecbc669f29a4d4caf634369d069118fd7a3fe12bfdf
+e202f3376727ae5f1a9340d824dd8b76a517da44eacd882063c8243e121769d2
+809ff04b1e23d34d9ce5f496c7f1926cf1b0930466df9076dcd5b82c97fe1b45
+d1579437aa0820561298a96e22c94befa09929f74c067706594eb37375c136e1
+a9491f80c7a0a0fd447f078898778428309ad92d89cae7f48727d8912deba5f0
+d7b49674111a6033b67c19bb92129b7127d4ce80bce642b1880628df772a87ee
+5b48f395b55aee658c50ab2c81ab7de66eaa52382fadcc60957581a4e09ee989
+331ca245586217a7e6b0f746455c59552906d8a525f5ba5a07cc1df0c45b6ed0
+95230142ee05ec75e12531f65374f78173615c913532a330659b9beaa653eaed
+c0895b65d1a44304e09765cc9e070f61820249b6236194d952103f17799b68e0
+8b447986aea15ecfb780f7717986537516a6ae928648b16a51895f33adc17310
+f0813e7c715368bce559bab6c2292b42198f7f28a2ef66661f70aaad4e5ff132
+07de7de9f1691d5d1235d1e77a71403c2ce61c8f8960c262ea5fdf4e93fe87e3
+b4d7a400080f90a71a8805257c24318563575bedff7ecf2ee53a6ca8a24f8ac6
+e26c70b028f87a1806e63784542067b3d9deb6c96122c644e4052624a9f5e2fd
+c04d4c1d02d39b438097040027c0b7131dd5febaa0f09b767cce35c4e4447387
+567dc3887cbe792114a9a0bbcd01c3aeeb167b5ea4e08f2e04eeb67783669fc5
+16e91b1fd880efd3b1e9c1553a2900b5f1ef5f7b6ac7d3574456eb2fb6494511
+cdf324639998efce019716334e92407673b4ce1d19dd9ff89d301a7954e95b4b
+11e33d64682ee3a8e215ecbb482532e9f0dfa9859b014711a1789d97b8aa0809
+a1338f2de4e58a9c3d727b6efb754806c2cb3698d01ffda6ff4baf277aa570d6
+524ddf0898a02c508bc6684ae45ced430993828c8193cdf0091b2dfd7d811424
+364490b8e607aa52cf695a4179703ca2ea9d0b595770729228f912fd9228360b
+359e298a8845c00bb1fb2aca1abdfc1c570b044bcded5e35986c484f0c84cf37
+6fcccd6374804a3d380b48a6b50506b0f52c51dfa6d49c83c9a2a8bc366137cf
+3840355310b16356606f84b5da7783d2f6d6f46284f63f1ca4bd7672e114eb49
+9a2d80bf132de70d992d326f70a272c64f6264b68aa7722e7a0e400eee21e634
+8fc2524eed6e4df4ae7da2667dca0cc4d79eb78dc7db496a7f54564931434aba
+a06e085a1d1ecd916fca69e8cfbda338b6f873c90f3440ff8c442646dbf337dc
+9631f88f60d4efe91d35df767fa8a0e3c1db3d0053cb419e78453c595d6cb05f
+a4205d1b3334183c34b5bc80a898ed9ab1cc015c65189fd0efebe5aea9c71196
+ae040a0598a301c4aeeef4864e4789b1124dfe3f8e1aaf66a3a510705f6a21a0
+cf0de7e2f9a2b89179cdd4b009b33935b87765f3bab41ab667e39b74c3774edb
+443c772d06d80f7248bb6366d64b1a408044b55f61f641793436d23b35fd55db
+3a8f7d0e99953a6e74338c1d20083b579e534e887994c2080731ef4f9c6cba7d
+5ed6975b583022715232c005033ffb24995ccdffcb1f8655de53044fc3a46950
+694b82f991cdacaef5879fbbbc1ba7de04cab076fdc76b7e75aeb177c61143f2
+4ea610269642f4b1c765063418ebdb6e0fd220f6005ce8223f956d6755b7d145
+e734a947ffebea2904bad11b0fe43beb9baaa38108e475ff79e8ac33bc9d7138
+3feb6e6e7083fed7a31f178ba5b930f47f4c197fe5217f9ae5b64c3ee13ce1ea
+5db10538f5e54aff7b81aaeee741713bd60bffc2c8d3f7361ceacfdcca39882c
+6b0e2217bfe1990908c1c79c59939a2344174f73d069d0bf1279aa30f722691d
+faedace1bdbe056026c8c7970def57b68fb8fc30e7a86d461a2210998c103c6b
+835238192c02ceb0144f2e035fc89f160fb9f14f3a6772b5dcf908244629ef82
+30fd14e830fec7280100b5eda94d83208463e7358028f83fe2605e7dedb85355
+50384276f3b72f5895a6bd4170abd2bf25d6a6d8c165b2ac5620a3417927744c
+2a9798576343f472e6edd840b3082a22cc0efef8f53787167273e0cf926ebee7
+868803e55a266e761dc6ab8f7722efc301fa93ee781e081f4f58e6b6ebad140f
+ab33411eca49b23928a425e4348cf705a1e0f4fdd320025f3c315cf0da1170fe
+1a13ae53d45b712a3370f2e68051c1381311cd34da1096e7cc8d2c8e018bff87
+45621d8452fd40edaadb9e5d4f89d92b51f8ed170dbea911ecd14ac32be75dce
+dac5e26c212d389c94cd67126b98cdfab76600fdd7f36fb51813987daa38bab0
+974d2ff8e114b529a87d187e5d6e782e8f4f5e01342a5d8c95131a799149e496
+9549a3cd70a85c5e69894ea8b544c3df17e4f25d704b96e1ab28aa76babe5b74
+e39b799c88ba47826dd52f55d7840a32481524a5067c247389bd392e80318ae6
+cca790fbfd0d7c6ed9ed7523b80390e2a67d5e11d03cede53ca91580a4f90e83
+17fe325ce9f63b99ba9874c84cf44fd1b87d593bea28b5b674a643753758f9fd
+27aa8213e088f885272f0cdb0792cf315ba0b03ede938756d26a025ce9f682fe
+2c6cfd369314eecb40063f24f365edbeee61d41370bbec9b08665e43857ec274
+6516dc151c4fb114da529efafe44ba73a2675d1d19b1dd9e696c380379df7682
+42de54a93033c757441b7fdfa404678109b0034c4b4777ff05f4badda38c5b88
+d6c1ce1d281521ed0077f1bafd0426e63b522c3ef1eeeec9df67f3bae4bc48e5
+15ba02efc3be471c0ddb42372553ebcb4863d6625245dbb6c0eebb15efdf7e37
+27335a4e3ad99e4f70e57e218e749f30bf9c9a20f172c40f0532dd17d93eaff0
+911ffbee62309b59cc549faf8048f56afbd6936cf686514b16bca3113d17671e
+15731aa12b2ea9c46f887180c3885878a4b59f965f15e1af4cfa20f1d9fd84bf
+7d21d8488356d638867f2ce794c2d1b836bd471bd561daf63e1b427ea3e3c371
+1acd9f55e8fd1d53c279f5c93a2da8a8aa80857f1312d2e9e418ae4440a7c4ad
+6216861f66844f4ff36f5ccf37e96f76706f8dcf7d2a8b6e76a8bdd30d66abe8
+6846fa3b8d5e8c9b516457aec4036b402bffd6913d24cce8714449de2ba50848
+6d3f3fcfc5f81e9f85c99e20cd7551f1f0b63a04b5d8d9734fbbcaf55b44897d
+621b90bb0f3039ef877c04ea5d7c747cdbc9c25b98df15c1ae3946453ba2b8ea
+733dddb19f5a1f3ddcbd8d0c6bfa2e4b914249156d26e0e0bf16a088a9eaa107
+60fcbf45dd8e9d8bd7a837c3e4f840e1d747ec1bd31a82172152819011c53c1c
+7f2ddeecfa67ae102bcd5b12532bf43d6e1b515b27918c89c32d4df49c3afb6e
+3b719f37cf1cc9bd04c45129bde8b221f88e10cef81a75e890543f0a65afb387
+7df9dffbbc8a0bf424628a26a48448e17f180c4dca4c25daab5f4b5fecba8ec6
+24d80359767b2bb4dcc58163295031ff07ffe7b73bd22928f63c62c2e556c419
+c3aa968ba13b7bb559197339835490f2c15d7617578787ceb25e01cf3c7e031b
+607a2397a682db4b4ae408247d00136abd2e631c85653f45c833948b48aac4b9
+ce421dfc528d49cfe98ad6f5bf332ea672288b85520b13efdd67bd326a10e118
+6b9e7026afe8f70aa21888e25b6e28ceea8dbca35e168f23aeb233a18d291a34
+05cf105b4bb6966acf3a2ea3395cf406dc6271a358c3cf51ee3f63949e738c69
+3a2622ee7dd92ed867a3c279aa260499e75ce46503f33c62a85404c6715ee35b
+861357f1e5ad91e1779f474165b3c61b3f5cadb6e38af59fec5eb9fb81773bf9
+83e923c4a9d7aac4c64a4490d575030e5d1d2c788de808e626968374fddb4977
+954430d7513b84f74d7c16755027fe46e5b9ec95cc3913dcc28f8963e90cb00e
+ed7d1af4c8c05c52f4f799aa42dff81ce677d750aeed905ddce7ed886f1e7ca7
+ad587245d7c74f4b9272860b36a76701b184705e7e0225ab64ccdf3aa20f102c
+82ba6d86254808c33fd7205f340622360844e19a937645702c923faa2dbd1218
+ee5a8eafe6e66913d3a895ba65dd825c7dc1b579b0b8f6d4bd47de45e5f922a8
+25b20041f7b8a1a55281f0fc44b149f0e346e0f3e1e8fdf5634eed22537df9eb
+40d8c8bd63d844ba24798ca2060786679aaaf1753f2350f12c3a984628362db7
+f4df3e4ff03f14be6ca123bf8d4bf43817df72c92dcbd6234c0fee3b8bae81b8
+5d7495a6acdff5033bbc102016c267ba56013a739c9ac62dfe9fdafe427d22a7
+1fa02c29744c713dce48b9e52be12797084cc12dc805dda41fd60578a10a9f2f
+0a51b5efa3feb126888d0430649e858dcb9c59a17872602334edce0a92336f99
+250406dedea2e1b1d3f8031b2eca7ae2e96973b852c90b59878faf06286e8dba
+f7b1ff75a5454235035d1d68158585cb5f08aa68b4c5971ff0499f0a3f491d41
+8e2ed07b0cdfba8001740ae437674704ab6378f650d84b6b562bf346d5b3a338
+e21fbe69d330a12d69703a6625fc77e4eeed50c3eed7db53553e1183258cb374
+25c209961144262d9145735df60c1e3cee6c21ea9667d328aa6424dc1d0d4c31
+bdfb91c001b3b9c30e6343bc3e6af5148ef219e48e9c49e2d9f8818b86e13ec1
+ef1ba0e04c9a93b509972038925babe2964cc034d5947c9f8232fa9a40999fbc
+b26adb0ad1a69e25c896c97307ff214ff08b76427f982d0eb9eedd8cf2126219
+9758b2b07735c086e9a6dd43b4094d320379b3ef0c20bb9fcb691a6fb40b9c25
+9612905890f8fbbe837b1edac168764ae7721a97d9e0ef67424d2fd6166951f8
+00097747f5db43cd16c8f0a5e2041366bbfec05cf950876f7e4462c4231f83f4
+3f14e61553b99ebfa7ad033f51ecf10ad964d71c18b90e360e208ee79f86b722
+d6f062498e4294ec0991b318d18270374de5212418f54a5a1700a19b23a31dc7
+b1c4bf77bb4c4fb26d219d87c7c0ac10a88f65bf5cbb122cc250035499ebd3be
+7b839f311cb67349e68cacdefcaf9970b1e22a120ba90bdec02374a7fb96b232
+08619b8a9cb59e86fffb65f1f0cb64579de0cc5b4a1d428c0c4f4b46ce3ea0c0
+3a4c815b2d210e3ee18f1c5cef74ec07c3415f2dee72c7511cb479e78d3a0d96
+aa7f15360ae7455210f4528a18d74863516b1c688ce1e539306e260fcc203e9b
+5af0c06c3c04fade53ed1009672d75ddbf3358396868c2225e9460590590a37a
+d9b361c5872562a91042a635d8531a62088d1341f9519903f98e339a196c4e2c
+8e6ed843cd24fdbeb101f09bc4516a7cfc4554320a9318f10448fb9d10c45939
+050a55114e979f4cd9473af6254311e3f50d619d9f304df427ad53ffddfad0c2
+f849b4ea37eaf66005afa5269d435d1b3362553043e98eb631d8b4e820e9571f
+88ac251d767e3e92666f3c0aad0f5a5901dd5f0a72a44ca5f957b40bc29541ef
+5c28f10e36b8102e5038c96ca8a7cac9698d6f8fcbb82bc413d364e3bedde064
+a77e2347719c840ee53489d836646e8fd4ac6019d00a045157bf223f32f0e68d
+379965445dc50bb7b2c223f2aa12fffb8858197093319a10d3203038f4e16c04
+c62294d51a206da6ce0ba74e5b204656bc172c22cc2404f882e91ad157bf4dcb
+dd619aeb837e0bf53c4328e57e5be92943a9dc6f2c5664255d7872c976386c42
+5235e3ee2671789c3c32b7d5e7fee0262a43cde4d3bb98d2aab133429ae002f3
+b71c2b3688d9297cef802a2330b12b1e03989fc8b9d1dbc91a7bdaba94703525
+4efd991952a2be03e1147e9b5ca770638a5778a7a141a705ae4ce474000033f9
+e1ba21d948c4b016162cfff6bca97a7f8f81cc2c22f8011b0cb74e4c7c92ac80
+c7c53ee9c0520d2c7a5dc97a78fe15cee47994362c65210336de52f9841b8d09
+4be1aaa04e39d7f66d0c4d01eaf270e26eb38e09c8b303816c34ffb8cfda9f41
+c03d95d95688a154c087930ede2dfa63ed8f228e3917dcc8722d1f8636305073
+31d43ea9b53230b0d0797013e7604b528215f1a128cdbb26b0cc3fa43e3fe25c
+b57c9bf8f8e290586e27924cd20025c54d6d8792b4fa52450684c8d0e131aa49
+2ab0a4fec2d0f91f4b81d74153b5e2e9bcca216c1b49bcff347c57a727202068
+0e040eb5d5e2479209d7e085760a7604544f0e1b541bbc73431ebd722806cc24
+505dbef65e4ca3457d0587c4d4fccaf7f14c31b5cf62ae5f0c08661c9f5cb386
+ec057c82dedeedd54c8f380320831f2d6d9094a5d0075a0c5c203be292231353
+3aab44061f0c75483c8c90ddfe2de61eebaa5c23f3c1e14bead51e741963e5c9
+393e8c906adce443244f5fd6ad7ab164ee9a217e609fcf90e5b8157d27623d62
+a803207d9839752a19da399e82082ded26d15088bc3623923fe5d5bcdc2bec1c
+639e190dcf9181e90ddd7a07c773e6a0511f1e268acf8d1a0d985abab4c95363
+c88093bb9f111cd66e7b7d8cd8eca3407fdd45d01d566944abbfb99bbcd5602b
+dc8e250247dd7a37f4389e94c1342b5e6ccd99ac4d62595b91b15462dbad20b6
+5bf5fd83a8d8864cc9e75f018caba8d7feb8dbe58766dc0a06405ba89e39a6dc
+a302161f88b641a7427aa926d438cff5b5e463309a6cd2abfea827a512d03128
+ec1f323c8a92929a4a6f63c798fe0b73fe59ebcbb1e4614c2d3ea0ce81cf5cc8
+8a6d82334df23cc978d9a97930cee6f567067f7c3877cd46b672d116805b1543
+4a019e47fea549f9942732bd1537c24fdc2f1b0780f7c9b3ef7e846906abd277
+82e21a4a8456cd0a880174efbec65e6eb267a94a9e0bb6bdb6c1c4998d7523ff
+eca5ed22cd608abad4f8570d80ff8cb65bca81b09536ae22a0925ec620cfb875
+70ac3a8e19abf258af93113ffc08aa07f0ef6d751440d874b3631f8a4bbd7721
+9a796911b9dbc0b2e1fc5768d40cb8dbcd38ee9f970c35bbef52deec4582d18e
+17a184203a891ef39fc8d56c29ed3b9010bf9092c8afc63f072c6ae93eeb9342
+6647475cb38c7a634dae53b14995468e4e39464ad9829b48c97e78c580ac32ae
+b2f5631d37bda7f69936d4cfe342279eddc7ce5262969c87e74ec6c4728c82bb
+eff94703031bb14d7743d97690d7a2ff863904ebcd94d1a2fd7270343f38355b
+ad318250da6b388c644a9f62bf59482d7a7161f5d3b65ad5b41005bcced2c16b
+c60f81fb3b013544a32c81c2f455f34cdc12e195fb29631b613d0db8fe3b4957
+a49b3e0382b3ca954a0c135e19cb83b7e29c7904b5852641bb08cee1bdba5a09
+f1f03521a866d99a6b5e670351b73cfb453263adf92a8677de8067ff52ee5a63
+e9ba822e8a11dcbbfa745caf433ef673fbd0e6c7d28f9d3260e2b2d75b888181
+3662efea0548a4f4c2ea1031376182e12b36987f2afbdf205af3229b7e87b20b
+6b334fe8615d13b3c640d25b5c822a2270111d964e4cff01f36d2df200f41872
+29389c1482c576422c158fbf2405e3dc0d87d76237f6666e6e89c46b83289a9a
+718686fe767577688e8524474e522ba5fe3f6222ae659a12855b295d408b7da0
+042c02087ffab03b422ba9b4bac8c39d08605624026e355fe62328d4df8c5d9a
+63e3118bdd69a142412d43f20d23d990ccb50ef669a76a9fc3924a64fbd9bb39
+fda7015bd5b56d3402151be6c4240dd647138bce226f7ebb9b723b3b3e6b3b27
+4da0d1245dfe453bd9ea5fcd36eed1c09e800b20eb206e3f1782d70c67a82d88
+10451c2cdf052ef603a41d50b90dfa15a5e967c6ae7b505b1136fabc9898c004
+5c1043754b8bd712703233855db0895697dbfcec02ef25092d073f37f8f86fd5
+e647c80b0af65453bdd645f92209412ffdf14cb48b7daa02781bc981d89e075b
+39c7e8251d2d384835691f495b3d5a43557f1dff58b935246c2c0b715b40d4b7
+ed7386bcdf5be2beecdbb223dbf57c9a06864f399d4748c11b89760579f1e420
+a4e8c67241a8348ef3bd04634b9e69d5dc5015f89b10f7580a008e5dd424ffed
+60f14a39a759a0c05e4bc3b1e344feaf613be4e1007f5535a7850832760b84d7
+3391a60001d472449b3e4920f46186d0fb8c8a1b96165e2f463e39e289274985
+e4062504ce4ce8b24764d050c371e6545c3aaad645242e39876fa2d8c8fa931f
+73d56e5206d47649cbeae14ef3187d43f6a3bf48bad527f6c01c8b1287edefa1
+1016690d1f50d310d463f9636d6f8514a60337fce30e6d72ddd2f381bf2bbecd
+d416f7ad7338fd928296aeb8c58c33e08737974840b690f1e8d6c46928fddf4d
+65c07eb4cde5f6b5dedab807a67ee995fdef7e7fcb285ea981e0877da1b8bc42
+fc201fe269c44a48be11a5ee8a365b50760c477e51005db9fde781c080f8ab14
+44181e4a0bc80741530370fc987e7eed25cd1b2de3bd5c53e0cffd67f24f9e2a
+82fc866abac0d9e690a0075cb623a625ec6154da7bc0c36431f20fe0a8df2fe0
+50cdb6c3d767b5f640833e7ff56016665ab4d42f28853f5bc2726de5a7372ed4
+8666c51df290f796fb129d441bff05f661a123dad14e5b8bd3839f0de22e44ad
+77cc296603290f731b23a695b45f5b930a8d6d3c6783466226fa0a20ff0ac7c9
+f63eea109a17da0ddf5e1356e23c37528776ebda66b0a5ab37b7aaac291eae01
+aadf7c555a4fb77722c0a527b5a711188e28944494ac58f58c6dab6df90971f9
+9ed636c4eba0dbcad659aafc776c3efff28a7d28da972a8bc5d8eeef3b7bdcbb
+8de66ae1d2fa6ec3eb33c37e632118bbc8dda3bfa80da3b7a2770908fc4575ff
+de9b1db2fa31637a13f8b97e73a55f7a975fc836eead6dad2453aa31d00447d5
+6350145197d430f8e6af1246a7d605e7df7a5ff4fb58e8241469481bf48b8436
+31acf36fd14a7de24d3a90d681246d5bd65d31dcb7795f2c9f6dd3f04ad330ab
+37636fb9da5f27693b7613cb6268d36cbe13f628cbbd5257f2644b9be91b69f7
+2403f914beb707992fcc653b96fe7b23eacc1f46c325bf02427894a28ea28e74
+482a2585f57f4cca2a49fc473b73b2cc47ebe014e78f9dd868dd4c63923f660a
+0d465c1053e7a6dd9138ddead25cdfe5f3a211d6f06e2c19453b405173a814a7
+5017f34fb8504208864db35ea2406c19d989e65e5417105ddd09fe62ddce224a
+daffb81a19999b0459da464d3d10492b62c12d085f4381fb91b597f218b7fc1e
+c0418a9292c6d2fb2cca2786b1b9a5cbd5edef092b5a3afa05d09e6e59a70f02
+1346ef208122d8c8f6bd2da33609aee5917f0c92fbc0941dff2cb3df9feef235
+4efa7155231d3403ce8211ca2786915280811d9dc9275456e491331899265c69
+8b3698810effb9e6e6fc2f5b1daa88e0740f8195cc8de64711be512f43128d28
+58f130156cc2bf774075a2b0bd3a0fec0729494003d58fdde93a2fddb529803c
+76834d20a2f0654a56d1cb58ec344aeac5bb976c694fbb328a3cf9c355a5ccfa
+3de62645b792d17c86e64a14465c5d29686a03037abb8eddebf2c55c94e4d984
+713982f983d942b390674695a46485ee6eac26cf864348762b018ce478fcf5e1
+c434b8a26187f6707c6963a933c1e7e5addb9dca4d5e9f5ea43a40c43d068b87
+f3d8bd0324f2d10f52d92da826ad20917b039076bafd236881654a5abbae112c
+7fcb2bd027c96d69fa1224a8108d1b7caa4d4b03f813d461157a7b5956be1296
+d929e3e3e5249cded2fe79950e619b47b99cfcf0ce26e6c5135ef0d5a05662d9
+24d57e0be99056aea4094b3894b644585ecc7a6f016df96ce3fc1ae75635ec2a
+2761bf9402fa697a76e326afb8d7ae5f7d375d260ce60143a38913a4e085381f
+ae19ced0d207c80cfd0da23b999fa947408f31c00ed5046f48187ffa4a9dc8ee
+1e410719a9e1fa29f15ab776e8013158bef0615e6960273646af89da0fa935c1
+8a7a1b67df2b0fd72b42cf02c5e916b0761e89550303ff45560bcb350b7e9ac8
+0422c68d1e34211212999f5a99800c7b103afdd7c8c5e32ce86d906031418297
+415ecd921c44e3d65759bfbecdc688167896374b5cd91a64cf08e3c224b9f22b
+06d26bd1bb52679fb75e2489027a7cf868ce7fec588def5493f6570d2340a2d7
+61a6272402e9d29d02546929d02ec741a4f54c94a25ac1dc87b8f2087e4d9275
+32d6afc9597fa33166e593112001404b15e1691f002afaa8a4fe0fd803adaab4
+ba64ffbefea2acdb3059dd040b2305997fe7a6fd78db6e47e385af2cfa834af9
+3cff1bd3773d6b6d2f303ef4950825d6c3768532ba44a111af37f8b632cca18e
+7325171953b66ec7ad79eff3b69219cb31201f5bdd8da70a8bac1df11ca37519
+5658f07151fdecba4c1e14c375de60affcee4998a0a377587f54271bedd8b226
+ab3c9b3638b4e1f4b633120aef8e4a2989e2925cc84c7281674e7089b71bcd8f
+da8d1a26cc2181ba716d80be532578d6798584c364f9220f1725670c78007454
+0ece6487869cecd8e66f8adfefcf23cfc5fb8f1ce23a5e1e7159493a78742f05
+660bb55526f65f17eb255e84cf0a5c0b6b98f1d54d7fd00c1f8236c60c596f6c
+28c7adc584b38671839b5847e37a7017f4f7857e31c3108cac29640fe9cc2ddc
+9165273404769a556bd04c3bf5f9d76627603543947cf885de71f44c65760076
+a889b5b2d3511219936476ffd38e2d5e86fdeafaa17dd36436ea3dd6914e812f
+b1d5d0333a7ebbcf6e908d334ec8dd335357546e241e6f3064251323b2fd746e
+54d67d5e2d5b425cbdf89105b0c19ab4cea931a557f2d7d488009fa8685a1c78
+57f58bf6f62a8c71eae1b8944d78df9897dde68b628df2706d7aadbeb2d4a4b7
+561810d4f7e1988262f7460684575b38f13c4680c3dc254c7ad5bf5bf200950d
+857556ee412c33b42820f740ad368a8392ad0605f16692e2c06b78ed542729e6
+d399a052823686f83e8c3f0e0befd2e7405c52e1d083c13e9283b50b4a834249
+6214238f65e52491566c183d5ff36711e6a99eb49f5518efdae0743467dcc32e
+4d4c92ff8de5f8c649152d48106228117f5c160fc8bd2c69b1dd365337722742
+2c59cd0589bf70287b487799df5e698698f4ff13bf4618c63c2b0e000e57aad3
+dae45bdae9bbb459fd55584839dd185f48a75687a8192f3a63d2280648e9322b
+02b9625144e67c6080f0eebb7c741968736caaafc151c4a9a1c1395f31b61ca9
+004eb39a789a9ffc247a249991106df6e1c52e40e4533f0fb6087a59080bf55b
+f197ae35804870900129d9a941004273ca0065c42ecec8cb517609d01405ef3d
+b500f09d410e230f680e12e5cd607cc0d1edc63e1845fd1bf3d9ba07e1b7a5c2
+eb0accd178e1c0178ef55eceba43a30599a0477fa4f8ca397b69ae64f6b6310d
+9fdb875a00545daa9f0dfc0ff9fa355a9ad4b08d08ca7bece29a5d3a325c1463
+8f38dfe12ccd5c942a5aca6414a7b04c698c9adae4d8d43732e41dc0f04da349
+8bdf6b9f3a58296957d88f217d5c46f45c7c3a433ff9c355a5ccfa3de6553bae
+ddc411e40b9f5e95cd6e9f62dae79353c5e1b874f6677bc593d2a2a07b1c3b20
+9596abb07ee8f71212674a53b3a604f5af5449dbcef830433acdcf845e44d879
+fc619fe992d5f6ea9b0cddfc24fe223e7a357cb0d5771cbd12bf50851f400b49
+9e55a3efcdae3b6d6cfb72ca9e96f9355234ea284bf70abc81d29bfc928dac80
+5eaca5ca15e3b8303bd3710a1eba32e3c13ef7641c7637afb5cef5dc59902e6b
+aff4c7ce9fae89977d96691d306dfb9a54bd8b9cb8484ae4b50f7e2ae5ade3a2
+9076c21e036a78c3d78918c529b3eafbcffa7c842dc917f162fe68179bb45b4a
+75ccb684f7c4b5a6d5db3cc876537e4b643e447dc64bb76d9e74a3d378517880
+9896262b00893cd7474bb605994df0ee028f4a703b4ac00ec95dc3ccb3342835
+418a5a158b4e6072a657a7e3d2728c0f64242be6fbcdf27d5093139fd7bc95d9
+89320f2ac958c06cec8fdcf56596bbb45b18abf2c6b5b891236063441ba7331d
+613c0f1de49f50a839bceeee1f2ea065fa879f50918c6a3280bd7bbf1e366e27
+4d5e9e0a6bda5a8768f72b811252f40704a1190b5887bc5c586b1704ff1f7d4b
+a8ea68ab335a506771a681f80dde3b8aa04ee1bd6cf881217b829942d0282c3b
+e262314736557d8a43e8686f3280d4c9aeb9fd78262834ea5569a233d459abf4
+5e50d17d666d3e9caefe4f8d74710aaf2497a405d73fca9e107a922d0a9eea59
+6de7f71ef541a0a5b141f786dfdd1861f81651f540f82505b3fe83aa32067db1
+13812fa761a7f3b28342bf1ef531746b033232fbd06a12a0b80a9e23571fc1d2
+184a850fcceb331ff7076d3a03e8ed50991837925efc91e1ca33491b36cb1aff
+2c1c58338fccc4a9f0e298e781afb718990cb9eb141c6ebbebc79e437b52a17b
+0b4a8cfd0da2f9401ce9370a1ef04986793a67fea0fc96d8eb325cf73ae51c14
+c5030d9a8340b8b33986ccc325f93903c0928af3fb3766c8428fa690cda94c97
+939fd8df41abc0652e0ca8e82e048b133aba600278e3a227c4eaf8b557856ff9
+e6d2fb6671ba75b779e90f2bee7e349ec394ac76e16c73509b5e99d44fa10636
+ac63f8d546d5eba3c6bca7b738d4338134d2b79dd8436932d6ee22e2183a5c07
+a72f4742edfc2ed11145fcf8a9bb54313894709b28d214fffcc4b917e0c389b2
+590ef307e3ebbf0e42570de893328dcfe81e7edebf72b88ff1a557e2f038ef1f
+8c25c68dd7c037226f7dbab36cf73ee00b92ba6efdbf9a9e0f4a7a91853394b2
+a02077209350a2b61d4771f338e739c5382c07e18386e398b2946ade4c3f0a44
+ff6f333d550ea7db004654aad32542571febf3c31271cc5d167976c6407c7570
+115bfb3f838701b58b320496c39c59553b33fdd7fcf135bf4d849b130fa58739
+6a983a93344bfd37afdb01b18232af9f65d5e3f5131bdc40063d7916a2b67ade
+09e2fed7968e96c6ffdb6b41b0a7bb6b8e93c2ac4f1b466f16e219321c9ad83a
+8027101b3d007ed0e43594efafac9dc0e4764477f6328ef0936444bbb4fd20e4
+2ae35cc3090ea9ea8ffc5a85236a4597f661568964f6a839650470f79eac69ab
+e1794cc369c4183f66715746b9abff4c151ab6f06be72ae63f41f5f806b3fa05
+eee4f22990464b7e4950f5654b36b7e3158a7b4ecd4fc7d5600c831ea7521df8
+e61c5f5a3942eeb82e06477461ddbbbc9551ed095d78309f0dc9465f88e70fdd
+7cf599e493318efbb07aef45a81f2d36111838a736b422891c562c933005dc2f
+99a44557c978e4d9da41d3e2c3820e4a40fe28a8c268cb593960058f0fec18bd
+c23102a74807835b1244daf8bb9a7553961977ea5a7d0e8a59f58b868c9daf82
+5c4cca4d84f4f37484b6bf8c1da54c5e089d3cb47377a99eba19e3e81a13d2f0
+59c22952aad0fa4f3a4d82478a06a1913b8f2dd141e776fdd43d76fc9d28fb93
+d198f84310f271baaae7e44752a4f8fd1ab089d3555f9d7083999bdedafca730
+50598a2b85541f071716d335c793b55b007392a1f757469aae1436de65d00f07
+66e196c47010731b77aa457876cb8994b97e20991af4e7b47bdf752f59c7e6c1
+27f3043e2b06dead3d6fd024aeb2413d1dc2ac897aec8c2a97c848d09b102982
+06f2dc08f0f0ea755c68a0aa8062521d34f16beaf87d359db9cc79faabc3d256
+3cae31022f3d4ae7dbda58194866a7a29dd34f17a4d9b7f7987e01a6db74bf0c
+5e0c53bd07af310a2756f22c1cb9bf2a0e1a57aba6753d037887c4b65a661f70
+b2d10fad50c5ffd365d3778295d880f498ff4950257e5ce855161796989611b3
+e793c99f4e12e947b51643fa80b129c6664b283c100b5694d2d8bf60a3213157
+a17b72080842b4ee055d0860a801d930d63e5354f599854b5a142f11f6b78717
+8897d66f3f6a3ff20029ed1870d538076c1d4fa4ece214b0075ada5cbd5f4087
+8975b21a79d8a67be6d73f8777ba26c18ae3b8613387b25f43beda7a2c9f6692
+667015b80847d0759ccf2ef45aeb00a02d63f630dc6823d5f61e91b410fcbba3
+677470b440063d43bf734b16ab45a42b698037ca3cc5b460b563467d9b6f9453
+d31b47822f6fc7fa1604e77d00ab1f172bf8f4cd9bde3635703a4166293494b4
+37f032570a165604cf5b8b153da54077095196e55252dfccd90a8ebc09bb7f45
+ada3560fd014a844b2d9bae654a533733ca8ae359bd7537c60b52997e10ef2e6
+f9a2210dc484431af83a956af66f06646d918d2832ecd425ff9cd8e9268557c9
+efc84868728aff4d8174e6f85135c11a6968b13f7d8346334dba97a1a3f0990c
+d1d2bac3aa8a5c294e4d0d67e893a609189383a24cd3bf86c22720d320d59a6b
+5e543b799bb5d45fa9826b832a809026d104293f363200780278d973c9c63492
+8576754d19d28c0e4058684cd1a71a919e5814346bd40fc15eeca1591e0a709f
+f083b86841f04cdf8aadaedd95f26fb807bd09886a011443d410466fc6bbc56f
+95468646ababaf39887f25e485776892c89a6cb7bf5d20b6c4e8a0bd65c5de90
+7a988a3e2e39aa721dd03ca69608d59cc1e8a1d2ea706b0426622e4dc6c94013
+3f979e5d141f06fd2051a54d84fdd4c7170a44f81ac786d258f98893bb4559c6
+ac59332f599dbf644367e28fcf01da3e8f33b14f8f83052d9c0af2061465b059
+5a923f4567274cefb60341ba793ec77e5d8c7f96bb88b63591d8cbeda48642dd
+c631a115cba7694f64506c787271a8c9cee064d9af1ab39cd121f7a1ff1c812c
+903aed1a0d13791c051ae21ba92b5db47e72e7d6001fb31535a385202edd815a
+40bcb9937ef719f7cd20e30fda23fd356476d22ed6c206f15cb2a78fdb45389b
+6cfa3cc388cddb82886b8eedbd9447a48844921a51d4cb19921c3d42e58c0173
+5189ba388aa32ac9b75b5ab43e428b20957bf253a117efbdc6d9dc0931099af7
+eb10790dbb14844be86d80c5e1ae17d0fa0f73703f180effa98dc52c5a8a9740
+869fa97d0bce9fb59b36a960315cc1d7264f5fe730a397c6ee121021a3c82cf4
+bcd40e844b78f99ac28c27d9c4f4c90cfe0062736be1bd39d45592020daf0b9d
+91efd616232a6aed299c9a01388e0460b46ec288d4e1cc5b08769ac88002094c
+9b680abb4d3e0823b0832d0d60d08dd7e1a27531803b26f74966f6f4fa2f4117
+2048782c3e3ea7744dc52e7113d3e290d309881514fb40773b1ec9bd7932f7b7
+42c3a3dd44ad5207b89125d7a11aed1a249863a6e537c887b00161ffae6817d6
+269d93d4752375c48ad0a598d2486f1a173bc55562bc889eee0afac3a4b12fd5
+cc2a57080eb6e3880e1e2930fbbf7e6629c428a1025966b47ddba3d48874e8b4
+ac01d33158f31760d82a88ee60be24a62fb5ad9f24254e2198d968255d79e4df
+206d37d52891300c669855a59e666dbb4676c4ec6fa988d6378ce4d00892c711
+3fec62c89dac139cc2b77a954f6fe953064c52a9345df309d8baabbeb1cb7910
+5aecc2f99b451c1b028a81d62927e2c932805d38cc8ab7aff70fc84f944d3fe5
+12694fe2dc46e548177bb78e4a51ad574c30f29145be59a24faadf91d34e9750
+1b1bba9cd1563e7e339e9d8795a3cf887053f1ee7062fe98bbc2257e8a6a964c
+d8542df1129217515f38beee762ca45f9ac85c4497afc6b7200c540287d85c40
+7dd6cac0ae86a4d324d1b3fd2f249d3613772ab3362840a78ee8633b3f89b46e
+b6749c008a46eca09c3ac738011772215d2e61e59e2b35b0d9bd012378c2e8f9
+7a58b7febc683901bf086f32282a9e539f2816acd16d5b5a0d7c26372e1b7ecd
+d64464cbc518da0ab436329afd5f6e12d0ada7cb9bd84cd2b6eeadc4b966e2d1
+33dbfa0ebef84b45019f47c668325dd037f85dbca5fbcc54ef9aa710d998f3cc
+df1e64c0809bcf0386de718201cb3c474fb5ce9cb8054ad3394bb6626f7eef64
+f60626142f6939666fcefbc920932d389770acb70180dae25a1e06fd6be4f45a
+07e382d0c055447a99dd7e0952f24e89401a1c15a277d02dfb6d31f41ebcb754
+2f4f608640ef289ce4147dc1cab77ce02b1823a87bd46f0c00cefe0191d68573
+6af019569e89ab388d3d770af3f61546fe350adac3337713bbc091318cf92de1
+7b61ccda6e28c314cd2d6a27e758e48dd6038fb55c6841dcede725a1a30d5fda
+87a37ce7461be94135e94dc72c976fc326a32194046979c351a53fb544d1ea5c
+dc87382f3e020e019e03ab3ae26eaa4e64256cd32aa1eac38539d04ae425e1cd
+e4548d0c352b680b4922b6b407856f1ebf6f3ea6ae68eb941d914d50642dbf74
+fa2a8df6b682e0d53f3b21ffa7225b105e359f49488183dc94cdaf1d7aa849d8
+88849fbd3bf680dd02198a557e768668eb2fbbfa23cf3af7bcd76940e1329b04
+bea1e1da8d71850e11eed811616410e2f7db0557aad2d531e1d3af851329a38e
+96b192d5ab246fb72788f99e07d22f9bf6c35bd15036db0fe66364654ca2c34d
+4aa14542eab8d979311725ae045f9af9790b26f13d0e447b3f58a8b817eaf26e
+f9ac8938054c130583237c616a5f3be196c9252a0621cd35183ffa748b0c4e23
+869a58dc345282066b213b441823389934fea2e40aba453edbb936ef4695b6ca
+5734f49bf4c21f0c66287df7cd723cd1d0e6d1ef02514a89a0360d6630f41781
+e06d5d12f793d64c257de894f58a074c06e4e18a5991b46af372e7c1065a9132
+f88a83fdb08a4a54b50d4218785e15b6a86d90b7ed621302558b510c0c205100
+44c2ff47efed6ff373b46f1b1188f96da0b38b0a9430e571d4dad21eae98dcd2
+8d36affac5ee640128374d182d451b3aed3f2e2080a1531a2723ef40b496ae2d
+f0ba56582870fe7ddd397548d59042d981da2c5b15c2d3cf8ac934d91679067f
+e601409c220b8a6647414d0b73afe5e96fd5106f9e5c9883132953083730f75e
+2947bb0a0ad58d73cf0b0ff80481d2a152262e1b024df4ee74d22e9933f1f222
+f389b8367ec1d5200c70f7f48079fc559a6610e483fd55df95a8621c4e42774a
+0e978ad53ca8466cdfc66e1eae6ea5df41dce8369b40c6aebe585f4e9b3b23eb
+19139f371c9565ee85a2d8eec6e0c48ac857ee2660ce13eb5cb81616649953e5
+53e23fc2fcfbf98a83cf202cf6ec7f3816e9d70a4f86391e51327e047315f653
+906b49384b9c069fbdb27c84521aaaee74e53055897cdbd167a7b5d33b8feb24
+aff3e22b5d9da4966788ced11943e209ddd32f3085aa28f2b631e56693a390a1
+74b5cc902bd67a2c3959dcd84a08f8ff2dcfbfff5f75e2ef01c9fb619117fd97
+f8682a84f0e718362659df2d082fa8d9428010a76268380f525c4a3292cdaa9c
+475554ed161cd2c43355af9350c3fbc654993a3f8901391308af9bbb1fce0813
+7592a1a4b271cd98d094d77ba038314ea68a4e64d68b0165bb593b873b24fe3b
+8a59f22ea3714023825140e0658fb050e8c4e32d85e92cc0d2eeeeaff00c353c
+c00483ad71627438a50b4dade6b0aa1caac73ce846581b858910486820ad2190
+33a9a108a502fa6ea60b8c85d1ec5c816a325dc802f213ba6bfcbfc65306d07c
+3202f0d42fdbb7b62fc6eeace31837cc024965695c529e274ea638ea2a24847a
+db9eabb31283bb13944309c17e3615256c7b95d8f99f86feddc2c7a566e4dc87
+bb060d88c661f589080fae039f4fa4b5b611caa6c65455dcd4bff9ff89b63f60
+5dbbdb21c07404e02688b7f92a8373ee1a6a891bf6c315b793a9fe81079c1ef2
+804238c72039924ea63039020363282f24f969f6a1aa10ef2d9e7cbcaf53c9cc
+1f0925637190af45c7aa731e7dac0f091dd22e5ddab9a8dea87a0269d5343ef9
+f8297d133a037679c6834beb8741396754f2dc5a89a1621c13722289eded1880
+c9859ce2a9739d003bb874f747abe3ab28d79930680ca53ceba6db84aa3eb869
+0830eff19972d32f37eb6eab0723478f881127a84536374025a782a7cb24f90e
+892bf7b8b9a44a237779c4cdbe5325d3e67cd11ff1a58b9d69ce962a659486ba
+6649708daa2f7a0cb144f6b11161501464b7c7b20659f6e5ab21231aceb813fb
+b30f9d6c6afd24d638ece5dac5259af8eeaa46abfc1d6f29383188b4ef4d8e08
+fc13a8cb07f61f55727619cde4f86944b879e23dda590ecc57f312c9d6d6fbc6
+8c875291b617f8040f4494323cb93e84f2100092b5a0527dbf02fe55175a1b8f
+7b8b130de1ee8d39589b7489f2d087e6f865cb536b595c552534475f1bae5981
+807cfd89532d91a6316154dedc1b8fb3f8fa0270651107c5ab24ad8310303d98
+39b801b2f0158aca98833b79846369993bb4a3a715afb6eba5a2237652b17d60
+2dbc21281bbecab7df65812e9ba59b78f6e3f6248d5cbebfedde90149d823635
+0ad0a8cda735b701534eb682dd2261883e19355d6e31d980265351f1895cd212
+a8a006a6953dff2ff7ba40d5263b6547b6cd5a5317bd77c08364987424033bf5
+bd9dd20c0ba69bb3d25df2ef5fc06092981c2f57ac3d38b93976eced658544a7
+942cb27ef9cc649835863b2e14a4390fc3e5bc2c0be8fe2a67f1ecb3d4734636
+6b29b60d7261a743583915fba3c8f832c4c41bc1dff13178f2d61832fdd13853
+db2135b4d92b1628b80f3baca26481a48a9e0d0847e4c53427e5066816b474f1
+052fdbf71f550738c13f1c3ab27d1fa12a11194c145d90f61e4ef2a1fae0d25e
+6974f191d3e1106c406ce4fba266c952faa505954f6ddf00068422eb0cd4baa5
+34b06c1805468d7fc0b88a491b48a393c55726d4941ea7d869b239a69565a892
+82e020721bc7ed828c88681584e0c49452fa8f2bb88914491074cf0ffcb5fdb3
+628b292a0f2fbb488e96767aeeb4b5b738ddd65d50e143aeb6e37fd6110e0dda
+5e2120fbd267c00b1b90626ede7eb1fd37636e7f3b01ffc6f7216b396bc1a5a6
+04db15b065c9a030d08127a538f69779a5bd73a6d6f55d2da851afb9da60bbba
+d782ffc339762792fbf66b2d139b11e76a28e8b483aa5d858ad779e905456574
+8c2f59a94010ed153e90a961b45a225cd9a131315d2691f6fcce8b321078deea
+6582997e824705703029a8aecbfce61d427d476b559145f5ee40364c49ae13ee
+bd940e7104a70a5501f5c0a632ca1902b75598d3ae606319d0ff4f0bdae32c70
+b1e4d5065157499c15fae5349fd68e77acedeb296cda2c912562546b9d1a966b
+e7959a20045af835d9e7348b5b33614005f05fa26f45c3de3397e5ef9cf02036
+37b89a5085099eedcc025785eb5fc2130988a19570a1661772be2497d4c8ebdb
+fde6619b3af0fb42f4d4c3516db21d44941a62f62f087ae3731735beab50f7c5
+27856884c3c4c01b83a40af32613638be95390c4c66f71672651df6bb4c4a0bc
+a9533884c477f6ae268cf7c21fb12678c54e4b3fd8ef032d3e3c2d3651bd905c
+562e643f42d81fbbdf819a28c81c9874100a65e70466226d90662ade31127e95
+c7f5052cf24df0a04d14d1d8a5d5d9b89d212272860db459e07731b3a6537c6a
+83c352d2d1f09389fd4da36da0cc25a57f402976cf3a473ff27b6554faf254c2
+ec8e52fe59d346baf8b1fa0afb13289b6ab06c8d25afaf4f218ba88565cd3acd
+f1fbe77f5209e6d5dc361c18d9152ea6a5267116769c987b89dece4fabec70de
+3adbac8e5e7cffa82f4b7e2ac41696dfe89443c64d8fc50c5699117d094ce852
+c2d7dc91ca9f1e764bd117ea7cdcf98ecb87648e82c64d93f58daee619f531c3
+615ce9df8abd7a63b2957eaa3cdb10e48c379a96334f8510a1f04a7c433f3756
+81c565d30f7c7d9e05f4d6f249d203b286c45eaf851190ac74822c5a6caf4f04
+94520c329c09cc8e0f5ac803219b76c8be672d5eeb878eee0c1116ca9942b133
+0734efd931c8e5d54bf9f22d194475e14eb923a453c9b7f4ebba83c80b4d36e0
+617ba6de745ac02953a63a9d73bc2e07c52a6fb3182e001c021916ca4993647f
+8be57505636b75e2013b781c2ee65591694009f1aecc0195349d48d780db2da2
+dc8c60410235e5c3c7b4542873c609f921a62953760759d81b718500fa45b09c
+504f9cff973ce3e32a35b426c6f3fb2cbc366d980e133fee86f961dff6a21602
+d8e14529e91e10105afe9f88c9f65052db9b32cc2d88c7bbd1248d4448706d3a
+defe4dd13ed573d1e8d31bfaa9fdec9fb1dd2ecba1ec8cffb0601523d86b8187
+e1a1526c20166314bb65cfc266d01bbd562c79f00c390ff40c42e424976ba541
+ee3860910e852c72b4c8de0cbc50679ea9434f4f21940480f148365bcb9f8889
+9c6e39c309a84f63a0b01896d4165395618198fc9ce773a874074a2667120e7f
+3bd73ba915a657c161c04ed2ad2a25d81c286f5ef0e0fdf91f6c0a1da5da4d13
+56a4df2f14c4cc408165ad0fe1c60373909ce04828b2c333569b78f35c6594cf
+11becc7df1fe0d9980cbfc9b083e6ff52fdc26cd74e64b22196586f4b21768d5
+2fa9107d541384b53737b872b0d7b0f931eb1525e118a45764bb1945fa8b91cd
+944fbf3b63be737baad604e8e93d08521e00f8cc93052e4692f1de842505c39f
+054d49d7bc49a836c724d887c0905d218fec71b710fb0a5fd2dd736354c6b43a
+4bf4cca989e6deb360b45e8a353b712c2745da57934d5629923523686d471c60
+6e09b539c13f1c803dd11cdc41a41e8427a513e4808dd4f787114870b3bf8ece
+f68fcdc6820e82b4d537534cb2a7c81f4e6878bd0cf435cf5845fb8402aeb47a
+8be7f8658575e7fbcce387587b6563eac02d806aae5a83c185627d9e8857edea
+2fa37ab90a1aa2cacdacdcca8a23a2be13b1016b2716ff29c2a660d49d4d2400
+dbd73edf65aa2981dd46641d15f2b5bfd40768309c570d5b5bad657915ae022f
+7fd0805df33437b8ed5d961a0c65b33a6ac36a9ddbfe78169d4a6d2cfaa5672d
+9354964e4c756a20b65f1550064e435dc5b0b8a53732a97ae91c337bbfba8e1e
+87d2ce72b4577c8474a0ef36cd33a3dde20e650eba05a242065b8d1fe3c702df
+ce2f7ed86080bf5e0efd27e162ebc4471770197c13ab838945cc2df10daee14d
+901fe063ba909462ade61c130d7911c73be3d448f36455ed30c21bbc2527d0f2
+5bacfb066c0c45300752906845190fb3e76db5fa537fde62a1e7dea1af35898c
+3edf2d9e6bc359439e38d363fbc37f89001e3dec0a7b6a4a7b112cd687e7c5e8
+4b0fa42d52b534b2d16ee3ac26b4dc3fec6272d1867a390ff9fe074d449b6cfc
+93129a45a6624d91800c7e638c32c057c99673e7b57c5fa81e60e1b7a0604424
+458e1bdf2f7f77d1fd1275512e3f6d5411cfc20cb8571ef52c16adc6073a9542
+da94754c931e74704ac639fb69b227ec66d4446231bb566792321da24cc3510d
+89b59bff3d647ef55be28555e93318d9eb888e32abfb2b0276ad9483ae8bd4b6
+5f2a3876c094f8e22056b9a26dbda1b09b0ce8cdfa085c8803151de9187d5c63
+406d87c00f3ce085b9bec794b99b2365504e7274aff81c5032db910e31c8f2f7
+c647a598171a4e8b19d05b5a77f1073ca0d5f58ebef2be11c6f390af9c835f90
+9b726589c9e75687dd9ba33beb8a42b5bb63076a5cd2cbfbbd43498934b67f6d
+35556aaf2341c4d3121ace6338238cf67a76ad99ce7be30040484133b44d08f6
+e0d6acf93cbb3d7b3b860e620efafbb0c644fbf90228a339e9a5e99632238e99
+b1644b65c08859787d58988cb9b6b792aa43981c2eba7136fe17c9fa4a671864
+d39bc4ce208357e4e178a4c0ece034f01955199d38dad2b9e9f3c47ce7c652a1
+29476742bb9219514553a1bf9aba5af84a3530746f5d24795f7d79a1927f1266
+4e9e31e2d86be65a64463db3077fbb4d8ba24f577ad99d58220c09d15d7478ae
+7360e3f048ce8bc4e7c171f86d2055c61cb4e32c8e28e52d3e8a87109f5b9c43
+b3948f9e62356c4f55797f5c349ee40bdd6e6d98406e361af69bca61870057a6
+63c08cd95a45c1d49e5ded4ca719b675f8fecbbf6cae5bf666352afee85c48da
+e728a4672cfd6a669015d51c96e74ef3d42233fa3e5271134aac9354d289e7e6
+4d5d7be4433a62bbe54900fea32c6bb7863dde80c914a4d50e11db676af24cbc
+6161712ed2e4ae8f0d7407c83889a3e52486a3d7b1e4ff2676a80039d4217eae
+febc054ff700f06cbc565353cb08267b50276528e5da421aca6d458c8d381645
+3d524f1b7d143791c3939bb177dedd8000a364a4fcf6d27912cd57f8b1aae2e1
+2f012bd0049675d4b47fab974f28d57b151bb0cf1290aff5fa25e4219aa3e1d0
+e6007552aa41b3446b5624135d748405749cc1db00a6575685b8f78856607a3d
+db56c0232cec6d31608342c785fcf455898832088ae4e608d6fb63c57ef4b2c1
+055ba60b0945c580292adeb56bded15de6311695fc74c35f311ebf99702b54d8
+77a677ac6df01ce3fdc54c527c543d3bfd19265fc579c4fb688ba9f1b7233e29
+ae0355e5a94f2a25fffb4a8fbd74f6798c863a2f318b4ce0c826f738614f5726
+2a2dccf3d5b00fc8140b153f360dfcadc36873b124a035781afb7ed41fd3d546
+79598d5030a8661603465eb2ac7a615c3537ff96556dde086f16040935e04a1d
+32fca9256ce570eaa1ba42b53032bea880447949fe73fe1633a552346c930fea
+020e94d8f22f115b195d0d8ebab7a07cda81497b824bd9961b660836709e0c9a
+a7e61662b0684e925e4dd74fc97b675b79674aa3f6d16e96a1c86fdd2c6cc712
+1b00906b4d70c2b27390ad840962c2aea555822be94c6a46dd5a09d0ef16d346
+32e6e2d9563d10762b2606ed532d652b7949ab11efe383cd0afd2a92804998e0
+7559a8140c01ae4b4fb4eae2c147464cc71403bbfb42cf1bfb2e009327d29ed8
+65feaf33fe93c081568949c382806f465e178142d956b250a827bfa2c39f462f
+5fa069853ba2e2d18ef99bb53878c78ae61d3ca268e5123ce3706d0aa11c336e
+f5d8cc7036c9ff0e73c16bf7fba5d692847cf0016535be27b2119efef09c8890
+ea845adc88dae8bc41a11a861d31e86ffc64ea135aa53c1823a4e1d4aa8ba507
+9e5df4efce318b6d3f486b385377ebe65bc916f13609cea424f9c42e8921f757
+50a6c10c6c13ac5f8a5b629fdb16ce10200ea8f825280a115dd641cdf1763f47
+acad9790c90657ef835ee1d02f3d2e241b7d6749c9e4371216931887778de4b4
+60d0ce386aa03cad70c7e8a133ac4ed3ccf31d91dd0986caa5962c6312679d01
+359dff0b5a357fee803d61922367be5e97abb50f4048aaf6c76b6c65410a57eb
+77bbdaaa596833dabaedf537786063320d766fc6e6826be6fed511babaaee583
+7b0643306d7cdd12f02e183d46350bb7c25b94b7d4166666c556d2f3adaed6af
+fb36837cd861192b7a36e91b295cd832a67094197972348f44d0ce3870a0891e
+0ec2fdd4413f67060f412a3cd31a509028d6361353a5e50058fe4ea46a6732c9
+98900dec7fd4b20e6d6fec5a30fe1f4d743d0deca667b5fe871a7c68143acc21
+ed2512990fd94b7ae6fe738dd326258f6cd6060f243df995a3af97687a78eed4
+271f3dc4f79ed18dea9a84694cb969ef7221be2485c8ce9101e9b1dc59af6222
+02a8ff7225ea661d01d5370b535c33ea4202b6f1a3ec6525a56e1ea71611491f
+47bd21ca86ce6d8c924f28c8d5e5f66b9222827382dad72df22ea440726154c4
+19aa0517d06423294bcbc7c6f64940c999d9fe1c6f514c73cdf05acc4105bd2a
+764b69f7d17204e9291a1d13be111261c53b5aa1fc255a44acfbd6a3e27d20ba
+0c2d9fc7cb0fd2bf3a9cbfbb53eb0b03c6ab2cd636bac90076e486ce9ee10b8f
+e69b03cf6aff7b5a3fec75294bdf32f198d65680f6779a9c6ef6b8edd936ce66
+e9cca6bbdcf4cd0808f33175d40c3f2ab76a02d0051ab512e32adbc595014767
+1ff6c57a77b8303e50e7bf024c8af22a2c7d5adcb9ba42ee6565ffff73212f22
+5f69eec749413749bd663976da9e39c7f2120fc475427a64d578e332cc5d2ec2
+84307c30b92e8ec05500c8635a069294902bf4f2969dad5068bac49cac00a307
+0e5203b294f7a42c2db51b4bf3e80bbecb780b0adac425db4e167f893ef4d257
+8aca20afe2773ef6fdc6e07c20ed0b0b4620ff0d5dc28edb59f39ed3e976c510
+e339c4a0fc9f56524df0aaebc9d985fb6781da97e651df018635096cac944d58
+ab8634a3b02cf1376b1c5fad86ab57c5ce85435ace10216a9eb3bd3deb8ee5b9
+acd93838e9c5b26f6f02f5541ecf8e94ff33839b93e16b740beba88c19410250
+301614938b4a69ba7b9cd47d79d9f58649225ec073b8c86503ef606afcbcc79c
+287c9315cad03844d0a465ace3e83ed75182fa6de0166ed492659d8e872e5f32
+c3043b055e44b91ee3bee18777b28169dadc7375fd25ea67288c8a7919fb6b90
+e5a805a38a40f1ebd4bebbf6e26b5d5ef18e381970be0753c35de24b6253f9d2
+4a77ed4532f5eb2f464e946babea6cfb2450543655fdd5ba1d46894538dcaf49
+d824b3dcf5484ca5098cdcd138997398a60b711568f26ad6eb964de44038fc32
+07c85ae853ccf6406906bd00b9af16f3eb2246d3ae95f6944ff39b64453b5a99
+5592ab75507ebdd4a7f5ed567eef5aa2697e973e95b512549da9190dafa3f3bd
+abb118f5a81cda2103aac289be4ad5dcfa566bc51d8ba2d5b19b7bb31a484cd1
+8a84c5dce3c7e50429d637c22b29d8f750b60e65e2fe3a7316d458557c1e87c9
+c6f8105537aa3b5bc789cd7c370b741477d21af453ea2781d5ce29d20f2176c9
+9418ea8db195cdc57bc982e91ef665411741fda4f9fbfe4bb3966ac00b76e517
+c7c0988339bb8848e616a134f95078a0a5c1a295bee68499d1428ada2d995633
+39d92748fe4016a88f9d534b35c766c9f970903c0b466bf4867cf914c570ee9d
+f4ebc45e058759a851b8aa472d14c305f6410fc4d05acc1b11b431fa438490e1
+816475c1a6316c7635226b49399563b3dda5efa5b38d1a32efde767ba4a0fb3f
+3d93da113a8e3e32a33b5fabf64917251122ec6dc8165f26af07005b2da6aacb
+b7c8e299cd7cac6e3251bb812a600ffb1a028d36ce351b3216a7d23a195a9afe
+e3461a40159201f1a89b8ad3da7fccf08906310fd84eab1ae0a76589d69e13be
+ee3ba496b5c3f34757363c4f5623f975ec58c115901ddd9cf4b8e135f1d996e9
+1a438123b50d453656a44088b62fa37f575da53a2a42bc659540bc759ca7f24b
+d7290430dbd53edd3c602f2decc5d5c2e305e8c9ce56626fe5591ae3f6fc9b50
+62fb69b77e75d7e0808f984b7a062383d2514b0d682ee525b39fbad9b84663fc
+3b74f5ff1adee4775b8060b227c1df3c3b2fba8bd36cb216a5717ae6d6629a06
+c099300377b741877ef1db03047a531dffbf8dc6aca4ce0097b9e623717eef1f
+b40465ba5b56d6d618aa3e837cb2791736223243621b3c0f36e8cf3c5fa3b815
+36815d1a277411b9026a9eb2b1d1591aacbfc92b49babeb49269eecdc24efcbc
+7b0871150cb009a229b3d1665d05a796f84dea63d9c2e23af3a999f11f53aede
+953dedb336ff92e7839bc7023f8f4b868b5171e03857ac0ad870c8dc42c6ebdb
+cc7d51d1484f93106fbc79a677e97db44190ca4478f1c6e3ed2dd57eb8098798
+f3f163280a6ed0a87e57581fd663917674328cd2d1c6f720ba8c01f8ab79d4fa
+7c08f1417a9a9eedfdbc6d7c84bb796f3be629e476565a34885dfb7c4164e821
+529be9d79022b6e1268cf6517a0fc2864e4b718f4018744984a2376a822dff4c
+0344668df1caf06b38c229dabc5a8f51ba120b53ff9291b99340e46786c809f1
+912c2d910700252081efab3d5e1d96d8933b6709ec5198b27fe6c511209d84e3
+cb1aeaf13f364a8dc62bc37803dd71c663f7cceaee35e5b39f87bc7affc1dc97
+16f93f931ccb3164490787b1432be66246f8e8d2d01232ad3ecb7962b9ff256b
+886ca9f86af2956dd082491d015635e02d867a199c481d51f8751c6c9a3efc2c
+7de4970992cb00d45e1d71522ae651b177c2a5afa2ea22a1c083eec3fa104296
+b5b9b74464f293e394fe8ec2d4bb82b7a27b7b4878eb9ca6d8fda476b76c872f
+5f030a803c7b1f1c0483a96c6fbd43a97302e17b5e0e28eacca87be1015d91f7
+0ebb16c26c51a8d7efcf50cdaa4d7c3449b1134c30d181932ccd93ec16b44993
+329b44a6c3638b23521eefdd79e7200228425465b0c3df07868818cdf53d9945
+a740ccd2413b8d53b52f3f6b435f960173b0b4cff2e52cea7bf54e5002d254ed
+c7ed8f69fbe4fa8de61973454ca0a0c9ab2c2a9882f346d5ae456b074f367547
+54e4995e568d48065f9421f039c6b36d2b4baee80bec636374611123995da79d
+54a8e318f034d9245cc5f2fcb4096743366a1b6ea840461f45a74129ef2be0d1
+ab028681ab144f68f02aa84cd5bf824f06d7ebe88d55ce185e9defa29b55592f
+3896f917281ff282a252bc9e725ffafe2db8374c7c34820944991671ebc16393
+cdf712bb663640cfe1e700bed129c5b3b37cdc5b725396e471534e75acc23657
+12cb452bb410347fa3de8f13d2ae52de9e4a0291cff3f45bd1387e0b0717b0ab
+c7998d15558b3c80adc7f8d1dc3d3565a506050f3bb187f49ee8019da0b49917
+124e925bc05e633da732acaf407477b47dd9329dcaae0c3af728c577b5402521
+887d20bfefa2028e1ee7ebe6f5092cbf0493e4256dfb59d62b9f26ebbf5e3b96
+5be553da8a9e3ffa7ce4de7e428d8e02795ff5d7fe6dc2ccfde9c708233201cc
+b433e0fabcbc498b649af575066ec7a198ebd6d6bde12a1a5fba108bb34d316c
+03ff8b0368fef25689c95d3f1a1dbfdc891cedd92dbe22a037a2f3054897eeb4
+ddf01d1bda48d1eba76f5441354c342963da0b9b3f86b0738a8c58f4e798fdcd
+04882c1a3f18075e31b33c78529f4ee2fa66f09ff4c3975df960c6ba32ae0532
+3719cbbb5702844f3a84478c7e234341a02d44cc60e4390f335629b692832ffc
+ea98f9ccfcad11ea232a22998660ffd04320d94dc276af68dda0050406307287
+5e1af46fcc2859f6e91190b15e68e7aa56cf02593dc4e76631c3dcb7fc9ddd64
+6c7ab907941ec4348ff95fe5dba5d6233696b3ed777eaab40c4eaea7766f4856
+3679565c66ca57b9bf714be5504191337b4c9ac40f87b0255e22d24e4adce456
+3502c29ac30988ded18faafc96bd0ff8a268b0bf5830a6ff11cddda5929659fc
+ed4cee41b07eee3229d1241f8b0a3b9b1757e1a6cd2b53b29d245c0409321e53
+7f2c66df8356115857782d74dccbd69190852001b7bd6612511ca1c458c0459b
+1b1afc2dea548cd9984cb34af196b08d5147ab49aba60c0014077442e5b39170
+2c6c265b25a215fc6bb84931dc00156975908b76cf1fb54996cd798ea06d1840
+937c7e1caf0f5c595bc038d3689e7d4af6c15a3943bd3d5fa645bdc0fe0c7fa4
+a100b71d2b60cfe9ffc65d2503372bd5d7713cbbb0dd9481c19e5b24f987ab34
+861beac180f61c7e8ee68846db98c9c88aad992d8875d7b44b5de8e8c780722f
+4849ebaf90244b14d141264fceb58ea3ecffd9ca893f44a1d5104c6c9c4e63b1
+ced9e015b9f415690d526fff0be7ed7a93535c5f823ac753dc074d9cacd4a3fd
+c03879f8216a892924cc696b1d256f0d7f8f24850db602ddb9591126aa067343
+d61163475489a8ce2a4643c5e3db6a9608c1c506cfa2439991d807b8a3d89a5e
+711969217daca3bbee9289e474a0de12e6ccff0984f10aa965e6a38725f99f0d
+881053165dfe7f8a1bab8b24b9bfb7a382298cb7e9b625b78e53b731de5e5938
+24f1d53d970622a380d913c0db2b4f156033aa2c3a4423c5ba3be0261808c000
+a42bda95a6768ada32b929234e4c3a9d0c212f0b197615695f84c9a12cf77a02
+d446971dc5ffd7cbe68775d948fd9c9aca5d93438b7b2f77e83cca3d3c3cf885
+badbc9166d3eeff0473edbef598a9ddc5e5d50637fe9cec73e695d38e413fae5
+7f11aa2c1453c9c56d3a92fc33ad60e0f79e3da79b49c8cc77c40e4c82f2e9e9
+3f7eed0a0b488dcec44f8846f8a58a970dd88c12114ea58e1cdd105c656c4c34
+3d01f7bb5a35e18d730c3967c5b0e711009f14574d947ef626b4ed333baa9537
+ad0ad7d4bb5daed5af9903990bef203a529e310e5fee72aa629cfbaede5bde1b
+7a53c7cbf91a177fd3a40fff8a1eb215aa5ac9ecba228b853d55d54e5205d0d3
+2d68813164ff61cb3ed67387de44ee9d93ab2a082b50aa39050f11cb7c894914
+3850b334dc7db907ad6b5e7f3f89bacad3771604f0f1eb2843a1a5df85dc6d23
+bdfbaf5665edf79e652006a822f6b9dc0692af54a51860be74678b1c57f16240
+8c18a8efb515987fc1ef7522224dcd42450dd4a195c43117885d66850aa1f701
+54af834ee208ff22b3d49667cd64b18aab0c528ddb3363c1fc375f1f6c133f1a
+5e5ca1e83aa6cde513b11d0d80ce36bf7562a35643266996e4502fdc85ba05d3
+c1a7e39f62276ff85deaa199c3e357757f547d77467886ef66af72968e363221
+25b228b05df472afdf38d64dd4b846df73cbbf2ce1030d8ae964b330e8d7a3ce
+52eb0aa4f164d44d8683a6270e952f984dfc78a720775be462cbd41bc8d56ccb
+383daf0007b58e6fde73ad44e701c23750fb4a2afc174905b2554d8d95aff499
+713856cbb4cbd369455ba97b123ecde74a928afc03e2e11235b0c7373293bcbe
+6193a86693f5db6c6aa905dd539c19a5e953222e1c68007068f5d6b7e232fec9
+4ba203a44ec7318ef6a0db9d255585b7589f0d3e08e420f8ef7b612637a8ef0a
+2692f52d9b094d32e8c197d8c3aa8c671529a4012453bf23e7ae1def8600f056
+e715dbebf72bb7eeaee282c926f4ae871c495282c6dc98f191d03380337c8881
+07e6113ff0fbf360a225f354f0ed4ddf9c97d30fe5d4dd03dd929ee799714ce7
+6182e399f53969cf4c8cc2df7332a9de56dbd19a8704661d842997f06f3589ba
+f63999f94b4c00cf23d1b540818fe68ffe739b1773d8ac1746e51d8606e06400
+4eaed25d729b5168e8f4bef2c59eb953fcb9956d3f3e733591a3978700510553
+8f9c1721aa7cf7552188c2ee521116b1cdf4852ef70b0d2066a2b2e5dc194fe4
+c14503d57f7d7802014079f915f3af34d0fa4b3fd4a0be01952933c8e939eb95
+dba63306dfa0f2ecefa6f8a868e9b513f5ae04d8fa9715e04daef3225ff5dc69
+b6e455f4c8e8670f7b4c27f62f0c9c3083945d538b46edd1c20cca764e370178
+fd0e0753664691abf1be81c197fc26aef772e4cd6e3b3c08b092e84b80946788
+1a00df4bc03890f5464d453a683d59eef81734570e635e4c2ddfa34b41a6736c
+b740641e676556182d98b7e50c56f8bd57020f515d73867fe1a24e098a656833
+38039c76da859391f701e404a0218adb77abb649687e685821f0f32691a82b07
+15ab96d0bdb3baab325089911ea11f6112b69c1e877cd8f2b8dcfe55108f0fd2
+12efbd4f7954149e64e1aa1bd0d0b709f13d032d1ffd5c68bd4fb14fbd7721d7
+a9f9355767875dd2dbf55d85ff6f0378d60e81b3e37ff6bcb6031668ffaa0ecc
+cf414fe67e9261eb0f791a092f7c97dc98f9fdfd212a363bf7473fc107ee5fac
+0790704f6fb87246143e0a53472593157180617772180173d0b0ea6949dcc49a
+3f8ffc3f033bd306ef4b6a9001d34d05e7a8a200016afaaf6a69b5a10e6fc316
+5d9341b62bbc709ab8424b84d25965834f27750afae63468b791cba8dfcc32b4
+158cec29e8112e70bd237c1a180d078070c6feaabb7d35f5167447e54ce4c6f6
+0f55d9f97ce8f1a157af6b49567a16bbe23233e68bfadba699b69b22d70b94b3
+bb551113de57d25e2cc22a3096eae40bea7e353034231ec33554fba3920e3f80
+3611e4dc2d2bb7580869d0bf7cd3a0b24ba6a2cee9210164bf0e14e76642cea9
+bed0b279b01007826296ff79b3fdc11c24beac7857667139a80f0243b16b153d
+a927925575ae6315fba5e54d5f9958004e31a4acdb315b78d08188accf9a0099
+e8e706553f926d14afbd8c39cd805eff90b3993c96405990aa9d189fd66acbd3
+123e94f66d3d1a5f4061b0ae1a032ed9eb5010307a368c1c6104767bab95bc46
+86ddea0248d2eb0a886115e22304e81deafce6e1dfca09a8a8c93e7a44b6baee
+d222d3cdcc5a8b1661b9a874c1bd871a4cce7f9666d0ae94735fa3535a44920d
+6740d5e6dd0372733bebbc603d89cac8450df40be169d9bb90e3480f3659f28f
+5b2de56f7c596b3d3c2b80dd82c1f01196c301855d6217b99bfffa93b9d40e43
+1a22f7a9ab31462d3f14b5dfb0795ca0675cceddb8e7eb010b1521406689b491
+9d264bc1f084564e6a564a76437c6bd4317fcfedb47653f191d6c6b649270aaf
+d083771031afca63850e7a552dc8cde90b1c6556d49c09bcd3284956ff555e57
+77365a2ab6d7e265a1a4f29de8203a2a15756ae595a48e11b958c6ebc478ecec
+29e0ba3a7cdba448c7abc3bb6e21d21bedc9e49e730a5872082e9b2ce588956d
+b35eacbc2f3f41485b0b0403029b7a5405ed1a40d6d0a7a802c0bc4a0b1fa6c3
+32450dc5b1cc97378a2edba812918155e84e78267ec5e73f2c0bbdc96604294b
+3f840dd3d7159999fe24b2c6f297adff2074ae10a9087e2222af7bfa938a2058
+4dce0e6185a493b9c177b8238af099bd737ee30fc40381833e6a08176f3cb0a0
+504270ae6e7701a47a721d70ca5099a2c55e9558320285e4584e64d639560d37
+50993e0757a1419eeeee61f8186f8493034da3997e6000354bf0e07d948afd88
+ed9fb679b76b39b0b4b444bca762ffe47103bf812b63a2b7c26e56ae4b8c73c5
+58d0f7d6971cde60c257de6688d38bcda59d35ccda430df05fb25f2a3c75d377
+d88b1f22eb46731b7b36f8d9ecfdacd3f11f9fbbc1b4587141c4b3b9b0d0a9d9
+bac41cf6c67a995491d52d695aa14aedbbe48d49126e55b2ae43d0545d031e05
+1d52602401c68d41fd21e4faef1cc86a15dcd17c1975b2d841556017a8f39f26
+b6c0c9cf80e7b5d9488f9d4b08564c8ff25c52c16c3d11ee55e18dd3462f9436
+ce1e4bb7eb090929b4816aea6150fea2659389445800576aac75dd801524bc6b
+3f8852f9a999c05c7b29820afd544071e9432ef5cdea3610acb574cca130ddff
+aca7b6ac5a6d86758edcc686be2a61b6f5f2a3405faf7a9b06edc40df07b74b7
+409e2a172430b1d504b041a5c7b0a4107bf60581e1fd5e43a5732ad62a2301d0
+07c3a3489e8ed7c963cb5ea2bb5f4befeddce251c8fa914c424764311d16c7d4
+1e855f4b2fc2d4237ab0457b3a1752971bccd0e321dea65684d0e44a73023a19
+4a9823950a620ea838eb5d98f9959992f48d5ad23be313438385a6ffb29362ea
+a69f16b642066ffabd919bbccf49db54550299476a7143d3d89a6c1a18e94db0
+656a51dc9d77dbb58568b3365ccdd85d0e98097342559bace953b33cde10d826
+8a3839a28d7c218bb4b0a31d105c5d5464c2fa0ae31f69994057717892369191
+3b67d16584ae555dfe1aa32d4b40d144cdb9584cc5777d1e30429f09e0fc25ad
+c87af39d21328dca6c31be361d2382b2c20435e255c6f3014f052e8f4d305a06
+7b3866bfed5eb9b9434909f117bb602c1b743496c9ea13dfe4558b443c347a96
+6665d7e5c06125a8aa4e906f7cc0bc04118a26e9129fc23a63fb0eaa010deb73
+76fcce5d4a3d4ae626517d2d77550c50f7a45b4ade74018b746f5aed001cef52
+fec5e73534a2de1256ada58aa26e5bc0d9d848c8d0a7f42dbd7ecab47d6be1df
+71b7632ba5fdfc28fad09fab97fcdf3ba729973d39a5cffdcec0d1173be7e7d0
+85a59a4504617981421dc9600b2b65fc122d34ee2fab5640893e34ed50f53037
+102330d718f7c2bb7b9f50d18643e04af5c860215f7211271fb2075e0152d85e
+c440a933a87d20a0677ea2717baec45e8578a077805615c7d492db713259949f
+0392cd91480059059dff830b24e2e6e5c715efb4aced3dc637f88cf3ce57ca45
+2e96edc986edce66a831ee92e6e284c72b1307ce4ac479e57014d5114a9054f9
+cf9fba1ae66b0f83e173f31378fa74e8b087490bca05f77da7842d7167d8964d
+c59089eb222f1a33963622a9a17c3542d9940baf4d1b8d68cd4d51f16acf31f2
+91570def9261b67da9222acbeba21a6f3fa905e081be29befe3152b326902afb
+bc6a57c110bdfda8a5c4ce8e272eabdca671be16f4eb7953d1df46e250acc0b0
+674deed452de3b04f258e081110c5ba20a934fa9180abc09806e257abb22041b
+836ab3b73f5ea8dd1dd8cd56216d1bfee9a8ba78d83e2cf0935586c0cd7340cc
+5980e5ca4989174cd46fdadb86398bbcca75c9580310422f692812bbd8a91602
+4f28a49d35ca1c80d5c6c7b879f14d037ce98c0be18eefbca8acccec2fb01923
+69154709b772b7a13d2a9737652a1bea28da5be3815867a952fbdbdb8c45c347
+afb7d7de74620d97b52a6bb27d2b25fc3d0515129eb92be62f7efcadaeba61ef
+88d82df3daf2d1b0a117493075b501ad84ff1650bd7196a1e481c393de324dcc
+c50ed36647ad32f2c8c1820aee9190126c1e622d1f686c71855f8a50062abab5
+54fbc041de1cb0f6163df274e0b148cc24ff66aa16590447b5018474e5bc20eb
+77a870366b3ba577a6f484ef97c606cf71f15470905be27bac6fa4375629f80c
+b8aef8957b856abe4f885c47acd94a7a32b2d81cb5ccea65a52328820404f5bb
+512ae315f257f6196effbbb484ae7c877ca278a1f086e771092915d375667811
+f874c618cc6a117310287382fecd5fad1bf3656f9d41c6b798cc1f6e6f7f789c
+8eab2b1634cc255f999a72ea0e3e201a4335558e1dfc26a8b02a8a1fa55cfbca
+d1bc49f81b8f7837a29d7af533d9fdca050cbc2809ad4cb54699131364977a03
+775148adc0957ecf2d7317ee6b582592baea2f25d926a1738df65b37a519ab95
+a99bab4dc07e56df6e4ebfa0a8e4d8f90693f8de9d1cba77b3f11dbf7b1eb907
+a381fa256e2e943d68e773459c7ae4e7ff969b43d5693b5b612fbbcccc4c5411
+49a3dd6f01ba7bb3d11a331cfe3734ba2b9e1f8a353be0f7a6a0567e1f0acccc
+0e5f27eb18fd60d809ddea01e8d6d1e8c5f59b9554910206ec9c56e7c483478a
+1e4a4cbe7bfc97c7c96f119726e45d23b337e547667281022269e8b66dbf241b
+d74d3cb856da025944794c90cd5196bc8f3a57869c9f9647a0faf133263e96d1
+78ee01be0f6b67db214b2e9c3f10aefa22e7e0dc1ff6d463a2cf77d66218fc1d
+9a0f754c3681591d66f8c455a85e3e7d396fa1cecc04b218a892da24ea8dedf6
+2084965bb34be5e897a6a6c7818f0cc42be52edd5cd7c45cbcfaf89c94569343
+de0c4bfff1f6120684fed8652a071b6587f37aa970b5be3e8574918b97cba372
+81ec8e16a93fd111d796dc5833b76f3f3254a1c126fe28e103ce61a0eb89ef8c
+9202f06e04949602431a5cf57a940c059876b3724e461b7cba19a398427e0d07
+8a88c2eb0f9d12b608d777cc1558522e5f0e96362ee59cf1ee03b1ba9b864bc1
+d186152f24e7e28b92e3a799b23c2416afc0820ecb57a0fc36f44b73aaf6ea3e
+3b952de790af21f21a273a5c0f86ea671ed2ab467469a0ebba738b6257ad34ff
+499b8e21a77a5685dc1de5f21f8ae20d85c6c921f1ca434b67e876e9d3bc0136
+dce9e8d97c769e07e7535ede8bcc169bd38b5f3881f94fc7526ffa91b7870255
+f62e423e2ea2946faa96353152b56ae03010a42f15cea95dd27c89a4614605d3
+636f44785350a920f63f0cbc5566aa9fdfdaf29c109dc83169b3a64ffb0a7c58
+084a88d385ade5c3273c02f809e3dac76dbad71e76462d909348c609dc0bc7f5
+0cbe05994df34bd38feb8b5876cf5a50989a44792c36f0a989084acd73cb700b
+64103a290df126dadf1449a45c713a71353969d41bdda892871644f895fef631
+15b63299dc61de72c7201d8392dd3f10b819b792ef91f1bcddf9f04bf3b94c25
+ce81c6edced5ac26c9cc0e304683656c9ef456b1b666bedb3fac1c597bff1d9e
+932d05de433889db501bfa1abf7158b428b24a0d49b467b3a38f916601c9d36e
+22374c7c96dbb04a5de8ee1013ec7d8515a9eba05c41def2b8af7bae6691abad
+51b58b489b3b20f3276878bd520e6e70a9005ee6cd9651199e71269a9dc9e1b4
+cd24d01d37448b25ea34d6e0ec921c4c1904b7c4deb93a5c6a0c736a501ce7b3
+5e386a065c1f5df136c8bb89103febc7b9b3de8f74e0504fbd7ff702c5c7250b
+881e83499ecb6d80b3f9d9d17aed8414a6e723603c8762a3409f6766be726bc2
+79f70435a56a0e096209231f5b0c134bdb7aa6cb3db9895720cd02b9dc0476ca
+90c7a9ed37ff0b380348f66d85037e9abc95499cf6a3006a40c3874fbbc4efbc
+93b261cde619c9f86a4203dcb642e59c59709dfcad0d15235264fc7923227094
+ca105019b1bd20e4bfe5f3ea8edbc33a7bcb6ebc523533228ff682e8a1150aca
+fbee38b10517877329be50b45bd87df864b02c6c8cb5125b01e79c64048ddd3b
+7bc817f3998adc78819febb2d81c7cbfd179908cd36b7e5169218b449cfd1a1d
+d0979eee4aa2c2f664b9532775113f6d528c0e7c1ddf7f92738b1ec1c903710f
+9543712c0133b208a9bbafaca0a5ec4a087a1e5de118fda50b4b79a658e73a04
+ea556ee2b9891278e414984da3dc6d739ce951cf058e2bd79d671877c4d5680c
+2a757744cfe0cf31bf587043af1acf031fe82e85f1199c1e606232edd4501949
+dc70516a3c30b356f35c2b4e14fdc62abc3cef6b7703937a1c5561694b2e2022
+457603ec1210cfe44401a8ef8e43ab2c2244428c4f8138828f2a7646a8482b49
+00a7fca8dbfc30cf7dacbcb003d06c36644d61e901114cc5348ebf805af2f70f
+84398891f59cc49846e23463d89ba78dcadb3efe0f1f00f4b45c40bc0d2f3cd7
+63c231487557e68ebbb01cc9f96f246e9c2634cecff4a381dd27814e07cf1eef
+75a68378e92e56ff486dafb1bba124ccb1f2e9dfdf43c5fabc0336d8f237f9c8
+c0a656c7691c674c0031ec4531b6e7adffa69d634c9af413fe014cb6464b4ba8
+649cf7bf0b6ef90126d312c361fbf4618957798879d967496ae34ecdd53985be
+1bcea07ff99f19a7bf3dad5bc31e1941161e60b98a7f78316b95db862315bd25
+c46474a7e5cbe24c54f54fa23b299901cc2488480d258db25713dc97ab0e4b5a
+d66bc1131efb7296f2edbf6d27734cd45603cf5c1e0915dee25996df850dfa31
+13e23c722aef49ad2be71db794c86926c13ad66f17d8b8de2a4b66d9191afc44
+2b4e95d2cd04cf23374f172a183ba3d6adf8b2f919f14b4ab794f53531aa0da7
+fa0d5ea6f4c30e24db53640d51248cf1001e7330286c1668a7250d3a0d34f84a
+7acecf297caec2198b56eb1b9263ba531658d06a3d9902c3c96034cf79f4038a
+2859c9e47088ed6025d59a69abe6c86ffb467b4d8b44028fcd099729e37bf8f9
+5584763055731e7d7ac8b76765955c5c73c00bdbd426950f69ac97f55f9f9f2f
+bc217e166c442e627ee893965fbc7200396403b1f52693f6079886eac12d3553
+a02944981ead829e3bef62ce5d9274e039e305c48b23c604a86e6d816b649faa
+ecdff4777472f32094796aae48960b0f3d74e40fd1e4e72bb37e54322c126341
+039c3b3fc15a137fbee162877a2b1a27dbc2cb91b9b8cecad345cb00d519c051
+f46df1f96224e95646e02d4af7739638c5a261e36e46be731e3425d8f62d25ce
+5a7f1e72abf75237bc7109d0f6503b684c728a888ce8a67d622a298fc09d08a0
+39b7508db67d1fad998be17d49438696e9c402bf6ca7e5ad7ca7a1450e04d17c
+2b0fa2feb0b9d5af5dfa5367faab7b3e2f104dffb5da896d00b890764b83543f
+70d04bf1fb56fdf0a5873edc4124e42b1546dcd43f4896a2b313617c5675e84c
+1fb558d1b944084e431c29a8b1b1ea67f22bca2ea0e503c422f13d3d18c16dc2
+2f8215c71346762875d4864e20c107f50d9b42963aa2185db553e60af6c6a6a9
+886ed38b48052b0cce1e5b3f2f5d96975849eef01399553443d2722f1fbe8266
+aab7b8c1d69ee6926c1f916c62dce7a293589e10141173e30b5790f18763f1d8
+e1168cac05882de798bf361b1ec201c9f5fc970e5bba72515ecf3ddfcb14a6d6
+2f38cec6004fca8ec75d0b2ae1fc8bc94d4bac124c97b770759987d4322ae181
+55c7a1d7c0d3005385eeae58b8a07c556dd439b676b496682931327031d68f43
+daa631dff340b8531a8f1cddc1f7061dafd88109b78958d52acbf458dd3ee2a1
+fce78a3e5a8ad9a5314464a53699701ce17dfba2e176e9ac0f34e1692224926b
+61782f6ffad4c6278a32b1bdb73cf6b8b2f5646dbea6de31b2078cad5a87dcd9
+04ea4e60601f7547c1544b863ba383ee95ceb4a42ce89e815740e0abf8d3087c
+637a29e0645de6f2edd027cdd56e8b558dbd26e9582186bd5b4792500926f8b2
+5d0dd38c7af1ce0e2d99942b0099757140f9dfbd514c38e3d15e502dbbc34c0f
+61e3262e48f80bc3b6f93ac5aaab909cac3b776094c2a101b0ef5c891e28ce69
+9ecbbb70fa9a71bab19a9a4ae49f9316b7dbdfd63b43db9aa064df4092cee544
+7507275807a430358ff0df98f2fbaf716785742ba18888994a8e2b54aa0f6fdc
+07feee88c4c98951495943bd42b395b92b545cce05be3db4a0ffc43ca064ac4a
+9baad9d402c97fb79b8f70184e011f78d8e96538f4ef66d45950a80d186f3b96
+56ce530f019a1269ecd2249f6514a6b77e486198ed8e4c28760a6482b1d3bd8e
+8104a8c964154d8d23fb33f0028960d92c5bd3dfa6bb390f6dc708a9f4fec7a3
+7327a97723acf7b62070cd7dffb6aaa77505a05fb292c54baaf428f9b81e5ded
+8c92353e46081e4fc0edf39c53ad0e64a29e34aed73d18e25d0fb73134ca03fb
+382c1f2d703e241986e9bcb36f4a9a45bf183e5898364d95369febc1c8867d7a
+77b76f92c3668ace60e180b685698d403b86e27cb9725405955fecf496aaf327
+e4f5e75ff5123402b23b0e8c5f7cf7ccf1364fbc8d9bcb94f0f3f018a2902f61
+ce2e4992a5dd21e2432a6f412e77b13cc968355f9298c0fda7bef338086560a6
+7c774e05aa3e6d1f0a3b61bc1ea89cbe39423ab0c3bccf10e091a76563ff8b09
+c509f9f69409b7bbe8ccfb574ea7a80cb1ff7502388b4969f91f319e0c609bb5
+c3ab477330dc67d7d900f2cae9890caf14312ba25c8722f73ab0d9b0c884aafb
+b548a565b92af0ff6940d36d40442671cd9513f93d19107cca2462d5432696d0
+97f56f580721b0cc583aee69922f3d0209989ed6e6bf55e70e293c0c3e89f0a7
+caa0d50b519ffe06b5e86068deb84ae448d5041d20e03955b5bbf0d78ae45bbe
+2e548c64687229f1033fe264d1eddd5437e2519aaf8d1f7f2b8acff36cd650a6
+3cac39cb7c215c7b34e29b58fbf5d8493cc1bbc9e4dc92d8df9cbc28cb3884a9
+b5031e13c12ea77e63e74504fb6609b446ea85900cd8351853e71bbaad97ef8c
+14c8691ea4a1b39c94b7ffd97605796e7125ea5a6ef7cb631ed1d7805155f6dc
+d64e6773ab236b4c9eeabdae300229d4710af35c88f3a61fb3934dfcf4756533
+bf1a27c68f82bba998f2abd86aab3b741fa09837a3f32cfb5f0a35732c7df553
+1277c984e1c32777107cfbe7962971b6bc352ef17179d94a3accaa0730f3d360
+ccc9e41a9d019759443990c8ad253b2726872bb53a3e6217b3ecc57242068b70
+1116465f90b58a16e703ad75c3da86b2ca6f6e2f88ce00a60175843321e6e6d1
+8e1fb76455d729634c196a8048ba1d7e399b7e5529e312610cd6e38e11de0500
+47bf6d8e7fc899e90a40f639ab3ed83e4f8614245ae7929da6a8872a15b3b6d8
+c0769d8468fa9911fda9fe875c7f4532d2cd641d8024b1b43de1d9fc0cf0dd55
+0337769be1a0da6afa42b12c8b3d21ea450df447196ed23071ecc65385cf7111
+d025b3d4d396fc2ff5f814fc7b249b88c012765d66402a3cf926e3769522c650
+62336560b22fd0865350c75541ddece29da646b137d467aea2b14e0c8a9d004d
+c914e1e36247f010d5acdfc06a5a86477392e6b5df8bfa0201bd160c16edd42a
+1611d8569db8d294d64cd76f9132970e961a57cbc9478fa37507f44197d73490
+3a6aa1706d0e0b4115e3c6bddbd4cf4be087fe8cdde3a8d764b299fcaefbf13a
+2f480e9385aa9db1d4358cecb9958ef760f6dd14459f2a93bd2d49e354b7a672
+8e6bd45f17e2c6d86512f24234f1e8df4e5ebfab6c094504041adaaea0139028
+5b4223e9cefe6e67c7a055c9b56402202fc3964989ae3d435ab73b071c022aa4
+6c2226809e1c38f8436f42d3ac3a262c94cd71383b50dca30ca2d275ec463405
+e7de48bbfcf53a152083382b8426ce804c958b9aae95a13f2575658991c91988
+e3af9fc6d24986700798775ed0edfe717510f248d3fd68a5e229c9862f7e5850
+1527886041988bcb5fb8e76b80a5292e982f25d51a97a594c4bc30a0cf3739e5
+6381ee320c7a2908da6732b29184ba5312b24e0935fdea476a5aeaa75df407fa
+4ebf181d5a15a5ad7d0561ebf0a4e55c220a9beccae6fd830c0fda764583cc5f
+f59dd84c9c2486afd7039eb22a657ebcb48c5ca9053f9c6671cc6cb448897f01
+a33036fc2438c9afec583261506e1ce938894644af8d14b5af8b020f0061502c
+685d2919d5babfcbb1658002a2b5b6f5fe8636f4addbf97dc7c28eb93dde4f93
+831cb9aec8d2d3f6173f16d4c31ae3871e4bfad4b652c0f5e11af2270c983cf7
+47e3d8ef4619c2d76db67ffac11988cf3e7f6a2976305532a2a2174656a926fc
+1e9dbc981c19937c1fbfd11871ab3b96599182227ba0f840f29bf8533d33e4c0
+052e60cc0a266a97857f7c6bdb412ea779b4094a5ca237960957dd0fa2a37c66
+97db26e03b892bbf9ea9b6e39d131f8ceeaced68e12b7f2ce9ac64dfc61009a6
+a4959924651f7158dc9c25f3e603629c979aac5c308fb57a460f4229ec663d88
+51e6947cc40678dfc56e8472f021d3264c06a3b76cf1f77f4b4d0b1886039d3f
+e166d739bafe7cbfd72331c343fdd7395cb8cda3a85c735bb35a286090b6055b
+62808bd999289763d9e433d22573fa3f069f40aa45df44cd9e17e843e9841828
+d18b10f2325aafb6c872e26486f9c059476a50a4a3654ea8ae9e7f6fb7496a4a
+887054819293cfe78eb82ce32bdaf4c291faee9b01a04ca557cb09e614d0568b
+4fd1eb0cadaa3014aeb0325eb68606f07885a5234e54e76ef6d9769b80e640c1
+8b21a8f0d80f7029228c704ac5d747edc455493f33e844a3b450f36141417a52
+0677fad5d7812b65640113f8daf1c45aac012f587719d26e84e8f98cdbc9e3cd
+a9e148ec08d03ebbc9e875b21ae8a3456a032d39fb1e726effa0b5039871cd72
+6faf210bc4927de53fffa0d987c2232bf27dcecda650bb7d7ebadaa7036079c8
+0f067aa09ade73b0f22f92ca57ab4a1873ed901c7eb934fef1524824604092ff
+6f255608a2ad18636b7f69d842764c672d3392b35b7e12ddc355aaec9023f8c2
+730ddc4e7b49e754e7550c70a21b5bdd7acd0bde562b34faa2ecbbded4956cc6
+45747aaf479ddf5269180428781e96f3589ab47bf5d772b0a6a01eda4dbf27f1
+49cc8d9fc57e95245f9da122f1e3b6a31ca21d7bce9e846b3484bc32310902ff
+acbc3b5e1191ce9a65878695678a3ea186d03fa6cd13b1743f327ab65a848d30
+1dd0abd9aac5b38e9f727365120dd5024f3da20c5cd4f6105490e3e45df85ac1
+300a041e5a5ea1d8d882bbb1afd4cd7156f19a48f43ac58cbab60674b4daaf45
+7528e5c201d32ca7e06ff8719b79b94d6c571a99221e5ef00c8bcd7d82ce818a
+e2c4f8b426438788ba84c02e7c9395b00f9780b7770facde25094236e106c405
+14913fcd39c2d2b0d084931cb5b4ebdda3d91d8a25ecdef23154779b30a277c9
+509902c229d32557328bfb32b20561dc38e88fa16a9ebcd74377ee96dae69167
+87481289a5d322e9c34dce09c833d7e479200afdd9998669b16a30264e72ca70
+f34e79f2c544ab96c323dadda4c9900737121d68aef6978feac182fe1214b1e5
+e2f3321680eb5f0e6fa1f2bee600b07db3d9546954fb6137443956de879d58ad
+cb7d8a870f12a58c5475ab59790565de421f59b39e643ab54f38a94336cc0f6e
+c5c54c6fe6f61d6c6121aad3b9b1d66350061d1679e795c252dc429647c83d29
+c684a07d0b41c72a62be91564cccf41edc5b079b2f06e6a9236addad84d0fc4a
+b5d3403b42c9dbe80d119de3855e1b17498a9c2e827f4a8b85736371e39bc01f
+7d7d14421219d136d182783f5d6f8aadad83109f5bbe65ce504735367ca9b26c
+40e10288fbdea5d46532331eeaabcdd5ea937f1537796f43c7a3ff713ea2c86a
+2f325acf06ef921222eaaa928a4dbc95a9a720bdcab273737e7753294e42a8ab
+3a27daddd130ef705f2702702f39c84de8e5de998f507bc54a69de88621f1968
+bc135bd14c3ce226befa65d8e117c7dad682ce6dd839c443e5368b1dfcbb8cb1
+7357d126b021a47bc4b3eb4967183f4a5ad66bb4ced909993a13f365604432ab
+42bbda92d93d132d4194a64717c65d6bdfad697e3e2c1b2962382d43cfdb20c5
+ed6a61484b3322140ae300953acaff45077505701f7c161f108b902734803f93
+b6e89a2cb7640388248e2c263135f0dcf9d6945f8b648c89ffa5d39911bf196d
+24dd049e561cf823988605fb0b3b2b960be8448dafe7cb335dfd8870f2fed3a5
+ad8784ec4c5835f2aaac6a5f7fdf8f15e1bb5f28296f6af7f8fece5ba290030b
+885e30d245ed99db82c0d669ac135390af59032d6b44b34cf43df8b7e3434bec
+eb8794acf138dbe43d028c6bfb9b27f2690324f201352eaa4f3ae5601cfd4802
+81180cb05c6bb3a90948f261ae1e31edd7b34dd8f1dd2451eff15bc7b881e47e
+98183454b03554d5aaf541f087bf56ad3087681521bf3c7b84c8047d3c23b02f
+b80a55f6a26ea426feb545f7075a84d903e1ee6386e8bf13deef625de5ebef1e
+5d693c8fde15c264113394b035931bfd00308cb7fe9b84d4b18df639a65f454a
+9136598ad3f5fe4a71e2d7ee28ac54ab4735957cd1aeaf5b91247426a706415b
+1f974db500682c188fecb8ec291a81647d09c3326e05a671e6f406140d94a86c
+edccd20538009a2867c3fd9d8b8a703de2f9e2dda4a495f6c8adf2d8da8e06ef
+9f21cc11807dfa35a9732f9fe8bdb8f2406552ab3d67621d23bf5f425b4dbdca
+79a2796dd74cab6305c5a8459e54f5cd6d8ed1768676386d19e483f257b9fcee
+1c8d9a0ad09997343314ad22fd713a49b8a7da1e2302ab57360ac15abf917f4a
+930dece02b588a0564271e207746e4e7c330889a3f272e85120c7d1348379a95
+80f40a595b1a0f939aa46b7eb17d07981136851acc677e78e8715639357668fe
+fb162d0bf9e7b58eb0a0a8e3040de3cc5d6ba9236cfe5a921dc2342167ec9d9b
+1a6ed1b7da0da8474ffed17740221cfeb7de9aad199e58bbddce88ba826a14aa
+9b11abcc6f897453ddca5565a096a79125a192cfebe71ed37b5810f13fa07e1b
+286bca46d8f8c1e76c7ea100c2dcc5501653c7cf156582152bb6aa34ae041580
+6292f09c7c3ccf7581a1d1828a99866f5148de42ab060932d1366c2bd8104053
+f5d3744c9d825171812dbce8ef600b937ec0430f4a6129d69fefbefbd94aeaa5
+12673d7ab07f05528c529d4552ed20f00e8bfe54721cd959ccbca7f5c1850168
+5d5ab669a3aabbf6d431a429696274df399f3cf3760a4ac9453d407b211b2bd1
+b8416c1c23b3e68343ec28cc5e520fc2dacff7d41274aeb8f77beb6174560e35
+fcbf7fc20042393bf52b5278402ac5cd9f2e1ecd5a9dba963b9acf08d78cc5ec
+33bf5c620b6f5520cb781a48f247158899cb347b977cd62c54cad66c7e6cfade
+433e5be9ff8af602bdccec15daaa4316c8289921d19f809c20e68fca2b71db82
+df2bf6f0ab9d90b9830d399d838547fecad841b07fc429abdf02d544b41d5e31
+7655829d65c3c4eceb08dedbc1b2db023ad5e524ac9cfba00bbfc2bb2df14d60
+954d55560fa6e2ff31786acf9277e5e2018ea2f839873e3a4cff611d83a1cf8b
+7ea655c6bd34c7904818fd857cfcf4a9b39e5fd5cc551655dd75602a432a077e
+653d3096ebdfe93e0c7c7fdb0e52ac0145a5857d5bc46daccc9d9bc048cd6e56
+1ac2f5cac16d537abcf20dfe3c6aba54f400573da27a781dcbcee2bae55db664
+18f8f003aba535662a2c891070ae99839b4e0fcdf5ff9b7201ff83bbc9a5d39e
+bb61de440b63d08fe86bbb51e251a65501f8ace59ec1907c1268c639012974f0
+d8c09babdaad610d2c065174687fa1d783dad6567f1bc9056b4e7f90796ae034
+529735bb2c26f89166b67385c0b3ea8befaf0d3c5d2021c5e95f332659f57c20
+7b74c058fe712f9f6f7bba2afa24d922819590e2fdaa7b44e2b7a401fad4d28f
+05ab33026864f395ec60b658d42784533883b1cf6d5c32baf24262d835be78f1
+eb7b30ab043abc420085fc13622e1eea3438d9736c4ea328912fe1f587d4252d
+bac29ae8f47e156b7183c1b79034bbda31e08619fc106ff4ab743bf8b9d8bfcf
+a8ab6b810c1e1f862201010a3e3da3ebb9457071f5bfc811869fdfa0ea873a0f
+fc47d86b663bb3309bce467a81d6f757f62a195f7c6962a729794666635c8703
+db1e2d71ae67d5dcbae3cbf5b948eaae1e7e7ee478a0e4584ed70a019449b463
+9f812dea0ca7e191c9698c48defd784c76b4692337749365039b13055ad9114b
+1a456477c879893ec3bdd4d34184ae5f3e7bbd8b8ac1abeb315540673a792b21
+6473e225bf2b925d77276d0741cc50fb9ffdd34459642d337c044cb4d58c123d
+652dffe4dc9fb7df87bef364cd7d62d8fe7046cdcf4b387798754c409fea2867
+c3c3e9603e79d53c02465ed8b57b752f76477dbbd1c8945522901478aacf4871
+c3dc4aa90b10c08351d608a02d168a5978fba8eebb48ec93653a8e178d44daba
+e42ec334aea08544a01dd57943e050e87fdf8200d2cc475c02dff5ab8f90e58b
+d3efce06d49ea472df3f149b629059dd9e839536fe014503ebd1e6acc1c35637
+bee2f01138614af7521c7a9b0ad46187f168e387a095d795c067660f71b43521
+7132c561f442b12f84e183d1f5dc887b3e38f904dfa73777e7bd7d3ab6e91fe8
+fed58db8f42e4b65856d633c75e5c9f27fa20a7c352f30d2ceda868f61fc0548
+f2ef8ebcaa18fcbb4439ad597a2f2d89ecba33181e770d5e8b89d95ac0c9b137
+a8cabedafbb3a2071a0484c54dc24ba3b00314555dd3268690308c839cb0f0e0
+410116d5bdea76dc4e37d89e5262df1a2d5214bdc3287a67e7657cf5b5f66892
+0e5b549eb0929093bc809afe32a60894d7d5dbcc546b0664cdea07442c75ad9d
+8bcd96bc17af39925125ccad1a305829e9054454ae7db205fdb3c459836ae540
+ce773785b55b103bbb400a6472c14b125d85426138c06d67c5944cd0cb27b224
+e1dba5ac9631e37b2a3ef25adfb0271f729f6c4b9402771e0dac6068e12a0df7
+7dfe8ff7eabc22211bdc35062527e1ef26da58a905331a5062c66abafb46feb5
+05aa06ae1a4d7e88143666dc8a7add3f049fdede30f7785f968dbb0ea2de8bf0
+a08da2f0c9473885963b710f96fed7751c4ce01b4f954e486325488951d4bf83
+01662f9bb15f48623f8b61c7bf7dd47fc35e73669f03286ac8b1d714dfb24415
+7083b0340c5535e329f6a114070d01df96675ca34d6a0280ae26e81a64ce055a
+8261cf5db776cfae5cf1fdc3f8bee14efdd08b832ac93ed7094b7e57a02b86c1
+574ed345d13e9f9ee91bc99fff620fba401de9c6265a1ab696a581e15a3e5d56
+dc74fc5c276b7d1136c94e1816aa95c0f3207ee25769b17948d9a4aa60f7968b
+e033c5e124780bc4f532ec96f64e542a0851a75664b073dd16034e76adc06931
+5f5f2492fb97859d59cf2f6fa3a6053caf4013c4f47469b9bb1636e7017a0c3f
+5d90c8d94894613b51895a11f37e9f31fd98a37bbf4cc1e01e5420556160dcc1
+25bd769c66ebe645bfdd4eaa9c53d414ab9757c860861eac507a612a9309e5fe
+e75c85868fc360a41846a8fc79b60a175606bce995b584b5d645f9223b7b3a3c
+e4e249600921f783ec8f13abf5aaa3b5008389c46da5721dc304f56d7ed99f40
+264d7c378d96a6d1a960cc2e412a85fc3a2c47c6f70edf2cedede7cf39d8512b
+9679bb345fbc28716239bd95246b59d21a9d8378897e9c7607af76ce59e66ca9
+d07928d73ca7744dc5cddfbdac11468497cfc89dfa1a24b120753229598af48d
+0d2e6332a73189656cd1f584496e6f3b1552b04cdd6e51a8388dcaee3dcf8c88
+072651ee9cd44e3cf938172570ebb994bb821bc04e1a7723e16ea720bd8cceb8
+af7ddbb6ac14a58e144ebc37819ce9f2e78c0b8e5f68ed48a5529e47b2d9a7d5
+b381bbbf863a318d719ac4e678cc1092f721b5ca28430dd1b597004390ca2a34
+a54e73d1d8a556c14c4f04eddb0adfa43ce6ec57c8fd98ef7abc2cefbe991786
+a58ff65ee2bcc0ac940a0f07e367c032f0a306375948b54546c7379053ce25a5
+707d9c54aaeabe39fadadd74e1710f617d751f7751d396c5d054d28f93314ea1
+0e6803b627f3e1a1b2a2c4f14a8e9f9da3a032f7bfd872f86470b2f50e7247f8
+97ac03b1a4fc9ae5fe6dd088171746706b39432cacff853edf1b2fe87208a44b
+c1304b88b88f70b2c7596cf5ea33d9d11317da7dd2b8497aac6100ca08551dd2
+a57ce4efd1522a62f292caf6e273878073c82f48a6c9f10c42be92c439cf49aa
+8d4302a85a9f6f313ffeeef59e54aac64b7dbaa8265613f46e62d9a93b3e5740
+9eed68eea907fc84a6ad170b8e1ca16b62e46b1067f28eb6b35756c71443e0e7
+f0ea70df14dba384368a68baef33b7b2105085d179980d6622db47c1dd4f027c
+0fb25e8b1ed04be113f6961107c235eec39afa4db672fccd5c11737b22ca2a50
+af750d838462311c1111d31211fc41b76a96197ed5f62505bd627efc44c98cbb
+12d82f9969735d8f4f36e097ef1cc66629b901cd7d530d74521decc86ccc0707
+07e8a5208fd26e0fca9dc03e4b5b5bb6ee8d899d439457f139f7d2eaa4dc6676
+a411a40412cf8808cd1e9ece8df377dd30af8dcb28e8635c0596c9f68728a877
+671ff5872c81635145e98e055dbf7eff602a4cd98986e49a26c3754b8a264b99
+b53dde3ac4448ba76a550df75661094484ae263c8bc0613152890d17b1a09774
+a16fec15c779257a9038a6b81ce29162050e1fcdfb9aa0c88dad199a74470938
+21b4585d40e628823c42a45bad0c67427ae3dea6367b69d76820550cf3b9820b
+043f6c646cf42d20f1241d0ce8196b2c652e9e10278545cd289bcf37c813fe83
+1a437c6e4991750aa4c7970e200b86ec8e26657deaf058e633011946e8569c27
+fa7da592b78df95c94607eb1779393c5c2b4de50c425384431505a93cef43c13
+e042c12a57eeaa2c48d002369aceedda1b7866c865c8f043bc4e50d824d2e312
+04bd8c1a4d01a688bb2133a4ef37b858fe1de27fdf8e5382e4a3f323e016e151
+8b1da5df2706b1d76b72db59fd5753e23b41306070650d6990cc2e7ba3014d2f
+fde0d8e36ddc3de6917bf3ef03c84f8eafdbfa7dce1c3007bb57c3860f65e991
+13cbca57852b97ed21a7a55ea9f0803d13accab45fac1e4009be81d3da217dae
+45b421d6a76150d7997de83c0aec3d7c09d5e0cacc5678f2834e21c22aeb8ab7
+aa20ed2112bf7d3583cb287740f2e6f828f9d07385eafed61527d19055cdb129
+e1e5f1326edb56d107250346184e4580fe83bf44e2343cac0f366e04dc9aba72
+0261224f33ea17f779ca8532400edad7ac6a5868a0251b030360b1ec93aad4dd
+04caa52edd01246e2e275f007849b317394f9ef09064dfa18fd3c0b32ab4073c
+855cd08ffd407d937292f54b0857c281b0d7dc4ded8070d8616e92faac8fdecc
+710a60c61f88319733d888795c8b45ed895fd29863943b6632336aa49c2fe365
+752b6463cefbc1fcec8e8ace944ba4d35a4dbd87a519eb5ce02348e106c3eb9f
+a57577ba58e7d357e55259f182f72c426eb9e12daa1cee7210f75cd7519fa1bf
+80b5b2f3bb7eef8348e39c270336f4c96a4e458f71699be5eb94f26d0c7ae5a1
+b815789c4c038cdc8285c6394b442ae05dd2322f5aa943badddad4449606020b
+f0f63647645d0c1cefe0a0e4964c2f2c9df5754c5d671182d8e15774752e06e4
+f9bc5cb91fc5d31fe4fe2f9bfcb53b920c22ce8d0c244851c9433f2d8f9d00be
+47a03a8d4246ed7a9a9bf27235f7537d071a6208b6eaca1b2b6c7c43e4354a23
+b97ba4ecaa7d93fbb73a4bda88c406a9a1fbd9861252f03b2385c3517c321411
+2f968c018db6e3e3de8246c05da9f4dfa2b5d0f2cd2fc3ccc8005486b9c1d1be
+e1e8c14ffa77b18c024a98dc5a38b6b0e057d80111e7d2296414e87276c13da0
+e550c489381ec3da91eb4dcca6affcec210ecee0c8911fd338b9dc590d17896c
+9f96dd780503bb15e0638db81ce3f26b1857c3705fed78959c83ba079e49fd0e
+08b3a60d8641ff061398f779498633441313aaad5d54e4b3fdef4e0ce6013b20
+f17c6b7b1bcf14289c52cd55b0c456a69960f32fc06077819f32bade75c69b23
+bc97b0dc78532580f9fcb1a7d13b2dca90785a7e958b579d397a5df187d4b0b5
+f76282b4b00e94c92cc3653a047ea5a003a72f2fa4053e3e9098f82944131394
+8c471657767911fafb254abb22cb50693c8c644096a922069e2e2498d230bd19
+4d2cfb8086eff1761fa2b13a0ea61d6ee0923c7c4d31897eebeb1d73982e21d9
+66f303ae3ec4dc108f8ff4da3bb381be3f4050d101d37042b11b314535a1c370
+1b3dd8305868241efaeb394ea2d3efbfbbf3cadaad79416bcd47f8c19cec479d
+19bc73567bfe6d55713ad85a42337200752777a1af0bdfe46bd53534c4fee290
+c23aa58863bc3d0f1afc9622c886244c3da73f429f8a130510505b554f1688b7
+709b0282c94e36468a0af39d04fe42825369197784a82d13011cb3d193bc40d5
+5ca5393d2b32c57abbed3a0bdcf6f5dc0bf26c97aa46daa4269d6e2e104a78a5
+c96a39b4b121958a0e643244f646480b348563e417335e5703c368081c178054
+293bd69b1d6de52d140ac2dba03cf45c83dcadcddc046526b004373d8aa9d2fc
+bd3b354ea1136b0161a7de8ba06051f3cc22b028a2f76b6780f74c5509c49268
+0ac5e71a1578e344dfa147d4136d57a579bbae76a482d742b44ca381a96ee349
+613690f1a6f8bb1e8af1df6b2555681485122512162894c2e85b2b9a3bf5628c
+2a7d925e263edcf66eda45022c012d0ae52faced11324543ce842b5c6349a832
+d91999d162fdeea3451b87671f83bfe321726a6244d6f948088c0392d140d0d5
+5931ea72d9f6ef32bfbddc2fc6ed0c50decba208beefbe2da0f1b6d918f55ecd
+be29f3017d358dcc4a3daad96fbd6edce1a0144dcd3bac07d163935f8580040b
+426a9434da7a234354e273f62500f88237cba7771d4ca0f50a506f07b7eced7a
+6704154a7eb342bf879fc79f7bc6c488a155fadb56df553ac0b5546cdf3a167c
+528b6ea5f65f0e02c863a738ffba4c4a0fb67a788609bcea6e504547c6e66cd4
+45ac2c76dcde706679ef2d509f7f45624afc984e0a3f2bb2e1d1364b038d214c
+11260ea09afb681a4fdea1df8aa130bfe08ecb071e7fdc72c90dc1b114d81935
+92e0b7a609fe06b2c805730195748a02b1879ee6e350c7837c109063628d1f76
+e39e0b4fd212b2da075c82e052a9f66be9446c306a956f412a39fa903e430eba
+d831d86c7f66765f92137a8cf309cefde84580726dd922a4defc48604b992684
+a793c7a25a21438dc7eed60e0a0c82007888457ff63cbab3184d5009ebc30428
+b5c1359c3fd71ae27bccf2be4eeabd9ad709ac34bc6afeb60c3afecc39358bdc
+5b56f65b2768111635621d7797882dd1db755d26bb2e0f03b1a8d80f902c3960
+396dfc9aeca6ff7d42e7f6b4f83d1f217a68f3affb54e7e9aa1d0524e1bab538
+3843852ddf61b2ad706af3d609a8d1f841f9e9d12b1b0f92f3b47b0dd0bc5f04
+866528c0f63db3feab76645cde31f0283fb8c711fc34504697fa0cfb8cd7b150
+172cfc93d64ab6a1da1bc96d501d49ac87cc6a04db1635c167a1fdb58eb9e3cb
+94c90d2f9046890700513232d5bf1678651012e2f817114f46a1019e72220d7e
+d216d12712bb8ae73140aeeb48a3339dc1d5a6efac1e8f50c92b58e32725424e
+ba86033bb1bba686695cedd75be66d54275c8ddecbdcbbdda44f595e6b686af6
+f6117bcef51df5e3c98e90171952a23f445c2bca9b8626400905fdce9e0464b1
+c1d241ae619844513e9cc3a58a6f978089d209bd775438d7b87108a342c76b62
+8c3a6a28b9d0c42e696f3d5908cb2c70d8d3ead811ef4dd19023faf86ee053c3
+014ff20983774efe8e26646abda4954ead06c80c7670
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+cleartomark
diff --git a/e2e-tests/cypress/fonts/Type1/UTRG____.afm b/e2e-tests/cypress/fonts/Type1/UTRG____.afm
new file mode 100644
index 00000000..d5fb72f7
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTRG____.afm
@@ -0,0 +1,1029 @@
+StartFontMetrics 2.0
+Comment Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Wed Oct 2 19:10:44 1991
+Comment UniqueID 36552
+Comment VMusage 32987 39879
+FontName Utopia-Regular
+FullName Utopia Regular
+FamilyName Utopia
+Weight Regular
+ItalicAngle 0
+IsFixedPitch false
+FontBBox -158 -250 1158 890
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.001
+Notice Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.
+EncodingScheme AdobeStandardEncoding
+CapHeight 692
+XHeight 490
+Ascender 742
+Descender -230
+StartCharMetrics 228
+C 32 ; WX 225 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 242 ; N exclam ; B 58 -12 184 707 ;
+C 34 ; WX 458 ; N quotedbl ; B 101 464 358 742 ;
+C 35 ; WX 530 ; N numbersign ; B 11 0 519 668 ;
+C 36 ; WX 530 ; N dollar ; B 44 -102 487 743 ;
+C 37 ; WX 838 ; N percent ; B 50 -25 788 700 ;
+C 38 ; WX 706 ; N ampersand ; B 46 -12 692 680 ;
+C 39 ; WX 278 ; N quoteright ; B 72 472 207 742 ;
+C 40 ; WX 350 ; N parenleft ; B 105 -128 325 692 ;
+C 41 ; WX 350 ; N parenright ; B 25 -128 245 692 ;
+C 42 ; WX 412 ; N asterisk ; B 50 356 363 707 ;
+C 43 ; WX 570 ; N plus ; B 43 0 527 490 ;
+C 44 ; WX 265 ; N comma ; B 51 -141 193 141 ;
+C 45 ; WX 392 ; N hyphen ; B 74 216 319 286 ;
+C 46 ; WX 265 ; N period ; B 70 -12 196 116 ;
+C 47 ; WX 460 ; N slash ; B 92 -15 369 707 ;
+C 48 ; WX 530 ; N zero ; B 41 -12 489 680 ;
+C 49 ; WX 530 ; N one ; B 109 0 437 680 ;
+C 50 ; WX 530 ; N two ; B 27 0 485 680 ;
+C 51 ; WX 530 ; N three ; B 27 -12 473 680 ;
+C 52 ; WX 530 ; N four ; B 19 0 493 668 ;
+C 53 ; WX 530 ; N five ; B 40 -12 480 668 ;
+C 54 ; WX 530 ; N six ; B 44 -12 499 680 ;
+C 55 ; WX 530 ; N seven ; B 41 -12 497 668 ;
+C 56 ; WX 530 ; N eight ; B 42 -12 488 680 ;
+C 57 ; WX 530 ; N nine ; B 36 -12 477 680 ;
+C 58 ; WX 265 ; N colon ; B 70 -12 196 490 ;
+C 59 ; WX 265 ; N semicolon ; B 51 -141 196 490 ;
+C 60 ; WX 570 ; N less ; B 46 1 524 499 ;
+C 61 ; WX 570 ; N equal ; B 43 111 527 389 ;
+C 62 ; WX 570 ; N greater ; B 46 1 524 499 ;
+C 63 ; WX 389 ; N question ; B 29 -12 359 707 ;
+C 64 ; WX 793 ; N at ; B 46 -15 755 707 ;
+C 65 ; WX 635 ; N A ; B -29 0 650 692 ;
+C 66 ; WX 646 ; N B ; B 35 0 595 692 ;
+C 67 ; WX 684 ; N C ; B 48 -15 649 707 ;
+C 68 ; WX 779 ; N D ; B 35 0 731 692 ;
+C 69 ; WX 606 ; N E ; B 35 0 577 692 ;
+C 70 ; WX 580 ; N F ; B 35 0 543 692 ;
+C 71 ; WX 734 ; N G ; B 48 -15 725 707 ;
+C 72 ; WX 798 ; N H ; B 35 0 763 692 ;
+C 73 ; WX 349 ; N I ; B 35 0 314 692 ;
+C 74 ; WX 350 ; N J ; B 0 -114 323 692 ;
+C 75 ; WX 658 ; N K ; B 35 -5 671 692 ;
+C 76 ; WX 568 ; N L ; B 35 0 566 692 ;
+C 77 ; WX 944 ; N M ; B 33 0 909 692 ;
+C 78 ; WX 780 ; N N ; B 34 0 753 692 ;
+C 79 ; WX 762 ; N O ; B 48 -15 714 707 ;
+C 80 ; WX 600 ; N P ; B 35 0 574 692 ;
+C 81 ; WX 762 ; N Q ; B 48 -193 714 707 ;
+C 82 ; WX 644 ; N R ; B 35 0 638 692 ;
+C 83 ; WX 541 ; N S ; B 50 -15 504 707 ;
+C 84 ; WX 621 ; N T ; B 22 0 599 692 ;
+C 85 ; WX 791 ; N U ; B 29 -15 762 692 ;
+C 86 ; WX 634 ; N V ; B -18 0 678 692 ;
+C 87 ; WX 940 ; N W ; B -13 0 977 692 ;
+C 88 ; WX 624 ; N X ; B -19 0 657 692 ;
+C 89 ; WX 588 ; N Y ; B -12 0 632 692 ;
+C 90 ; WX 610 ; N Z ; B 9 0 594 692 ;
+C 91 ; WX 330 ; N bracketleft ; B 133 -128 292 692 ;
+C 92 ; WX 460 ; N backslash ; B 91 -15 369 707 ;
+C 93 ; WX 330 ; N bracketright ; B 38 -128 197 692 ;
+C 94 ; WX 570 ; N asciicircum ; B 56 228 514 668 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 96 ; WX 278 ; N quoteleft ; B 72 478 207 748 ;
+C 97 ; WX 523 ; N a ; B 49 -12 525 502 ;
+C 98 ; WX 598 ; N b ; B 20 -12 549 742 ;
+C 99 ; WX 496 ; N c ; B 49 -12 473 502 ;
+C 100 ; WX 598 ; N d ; B 49 -12 583 742 ;
+C 101 ; WX 514 ; N e ; B 49 -12 481 502 ;
+C 102 ; WX 319 ; N f ; B 30 0 389 742 ; L i fi ; L l fl ;
+C 103 ; WX 520 ; N g ; B 42 -242 525 512 ;
+C 104 ; WX 607 ; N h ; B 21 0 592 742 ;
+C 105 ; WX 291 ; N i ; B 32 0 276 715 ;
+C 106 ; WX 280 ; N j ; B -33 -242 214 715 ;
+C 107 ; WX 524 ; N k ; B 20 -5 538 742 ;
+C 108 ; WX 279 ; N l ; B 20 0 264 742 ;
+C 109 ; WX 923 ; N m ; B 32 0 908 502 ;
+C 110 ; WX 619 ; N n ; B 32 0 604 502 ;
+C 111 ; WX 577 ; N o ; B 49 -12 528 502 ;
+C 112 ; WX 608 ; N p ; B 25 -230 559 502 ;
+C 113 ; WX 591 ; N q ; B 49 -230 583 502 ;
+C 114 ; WX 389 ; N r ; B 32 0 386 502 ;
+C 115 ; WX 436 ; N s ; B 47 -12 400 502 ;
+C 116 ; WX 344 ; N t ; B 31 -12 342 616 ;
+C 117 ; WX 606 ; N u ; B 26 -12 591 502 ;
+C 118 ; WX 504 ; N v ; B 1 0 529 490 ;
+C 119 ; WX 768 ; N w ; B -2 0 792 490 ;
+C 120 ; WX 486 ; N x ; B 1 0 509 490 ;
+C 121 ; WX 506 ; N y ; B -5 -242 528 490 ;
+C 122 ; WX 480 ; N z ; B 19 0 462 490 ;
+C 123 ; WX 340 ; N braceleft ; B 79 -128 298 692 ;
+C 124 ; WX 228 ; N bar ; B 80 -250 148 750 ;
+C 125 ; WX 340 ; N braceright ; B 42 -128 261 692 ;
+C 126 ; WX 570 ; N asciitilde ; B 73 175 497 317 ;
+C 161 ; WX 242 ; N exclamdown ; B 58 -217 184 502 ;
+C 162 ; WX 530 ; N cent ; B 37 -10 487 675 ;
+C 163 ; WX 530 ; N sterling ; B 27 0 510 680 ;
+C 164 ; WX 150 ; N fraction ; B -158 -27 308 695 ;
+C 165 ; WX 530 ; N yen ; B -2 0 525 668 ;
+C 166 ; WX 530 ; N florin ; B -2 -135 522 691 ;
+C 167 ; WX 554 ; N section ; B 46 -115 507 707 ;
+C 168 ; WX 530 ; N currency ; B 25 90 505 578 ;
+C 169 ; WX 278 ; N quotesingle ; B 93 464 185 742 ;
+C 170 ; WX 458 ; N quotedblleft ; B 72 478 387 748 ;
+C 171 ; WX 442 ; N guillemotleft ; B 41 41 401 435 ;
+C 172 ; WX 257 ; N guilsinglleft ; B 41 41 216 435 ;
+C 173 ; WX 257 ; N guilsinglright ; B 41 41 216 435 ;
+C 174 ; WX 610 ; N fi ; B 30 0 595 742 ;
+C 175 ; WX 610 ; N fl ; B 30 0 595 742 ;
+C 177 ; WX 500 ; N endash ; B 0 221 500 279 ;
+C 178 ; WX 504 ; N dagger ; B 45 -125 459 717 ;
+C 179 ; WX 488 ; N daggerdbl ; B 45 -119 443 717 ;
+C 180 ; WX 265 ; N periodcentered ; B 70 188 196 316 ;
+C 182 ; WX 555 ; N paragraph ; B 64 -101 529 692 ;
+C 183 ; WX 409 ; N bullet ; B 45 192 364 512 ;
+C 184 ; WX 278 ; N quotesinglbase ; B 72 -125 207 145 ;
+C 185 ; WX 458 ; N quotedblbase ; B 72 -125 387 145 ;
+C 186 ; WX 458 ; N quotedblright ; B 72 472 387 742 ;
+C 187 ; WX 442 ; N guillemotright ; B 41 41 401 435 ;
+C 188 ; WX 1000 ; N ellipsis ; B 104 -12 896 116 ;
+C 189 ; WX 1208 ; N perthousand ; B 50 -25 1158 700 ;
+C 191 ; WX 389 ; N questiondown ; B 30 -217 360 502 ;
+C 193 ; WX 400 ; N grave ; B 49 542 271 723 ;
+C 194 ; WX 400 ; N acute ; B 129 542 351 723 ;
+C 195 ; WX 400 ; N circumflex ; B 47 541 353 720 ;
+C 196 ; WX 400 ; N tilde ; B 22 563 377 682 ;
+C 197 ; WX 400 ; N macron ; B 56 597 344 656 ;
+C 198 ; WX 400 ; N breve ; B 63 568 337 704 ;
+C 199 ; WX 400 ; N dotaccent ; B 140 570 260 683 ;
+C 200 ; WX 400 ; N dieresis ; B 36 570 364 683 ;
+C 202 ; WX 400 ; N ring ; B 92 550 308 752 ;
+C 203 ; WX 400 ; N cedilla ; B 163 -230 329 0 ;
+C 205 ; WX 400 ; N hungarumlaut ; B 101 546 380 750 ;
+C 206 ; WX 400 ; N ogonek ; B 103 -230 295 0 ;
+C 207 ; WX 400 ; N caron ; B 47 541 353 720 ;
+C 208 ; WX 1000 ; N emdash ; B 0 221 1000 279 ;
+C 225 ; WX 876 ; N AE ; B -63 0 847 692 ;
+C 227 ; WX 390 ; N ordfeminine ; B 40 265 364 590 ;
+C 232 ; WX 574 ; N Lslash ; B 36 0 572 692 ;
+C 233 ; WX 762 ; N Oslash ; B 48 -53 714 739 ;
+C 234 ; WX 1025 ; N OE ; B 48 0 996 692 ;
+C 235 ; WX 398 ; N ordmasculine ; B 35 265 363 590 ;
+C 241 ; WX 797 ; N ae ; B 49 -12 764 502 ;
+C 245 ; WX 291 ; N dotlessi ; B 32 0 276 502 ;
+C 248 ; WX 294 ; N lslash ; B 14 0 293 742 ;
+C 249 ; WX 577 ; N oslash ; B 49 -41 528 532 ;
+C 250 ; WX 882 ; N oe ; B 49 -12 849 502 ;
+C 251 ; WX 601 ; N germandbls ; B 22 -12 573 742 ;
+C -1 ; WX 380 ; N onesuperior ; B 81 272 307 680 ;
+C -1 ; WX 570 ; N minus ; B 43 221 527 279 ;
+C -1 ; WX 350 ; N degree ; B 37 404 313 680 ;
+C -1 ; WX 577 ; N oacute ; B 49 -12 528 723 ;
+C -1 ; WX 762 ; N Odieresis ; B 48 -15 714 841 ;
+C -1 ; WX 577 ; N odieresis ; B 49 -12 528 683 ;
+C -1 ; WX 606 ; N Eacute ; B 35 0 577 890 ;
+C -1 ; WX 606 ; N ucircumflex ; B 26 -12 591 720 ;
+C -1 ; WX 860 ; N onequarter ; B 65 -27 795 695 ;
+C -1 ; WX 570 ; N logicalnot ; B 43 102 527 389 ;
+C -1 ; WX 606 ; N Ecircumflex ; B 35 0 577 876 ;
+C -1 ; WX 860 ; N onehalf ; B 58 -27 807 695 ;
+C -1 ; WX 762 ; N Otilde ; B 48 -15 714 842 ;
+C -1 ; WX 606 ; N uacute ; B 26 -12 591 723 ;
+C -1 ; WX 514 ; N eacute ; B 49 -12 481 723 ;
+C -1 ; WX 291 ; N iacute ; B 32 0 277 723 ;
+C -1 ; WX 606 ; N Egrave ; B 35 0 577 890 ;
+C -1 ; WX 291 ; N icircumflex ; B -3 0 304 720 ;
+C -1 ; WX 606 ; N mu ; B 26 -246 591 502 ;
+C -1 ; WX 228 ; N brokenbar ; B 80 -175 148 675 ;
+C -1 ; WX 606 ; N thorn ; B 23 -230 557 722 ;
+C -1 ; WX 627 ; N Aring ; B -32 0 647 861 ;
+C -1 ; WX 506 ; N yacute ; B -5 -242 528 723 ;
+C -1 ; WX 588 ; N Ydieresis ; B -12 0 632 841 ;
+C -1 ; WX 1100 ; N trademark ; B 45 277 1048 692 ;
+C -1 ; WX 818 ; N registered ; B 45 -15 773 707 ;
+C -1 ; WX 577 ; N ocircumflex ; B 49 -12 528 720 ;
+C -1 ; WX 635 ; N Agrave ; B -29 0 650 890 ;
+C -1 ; WX 541 ; N Scaron ; B 50 -15 504 882 ;
+C -1 ; WX 791 ; N Ugrave ; B 29 -15 762 890 ;
+C -1 ; WX 606 ; N Edieresis ; B 35 0 577 841 ;
+C -1 ; WX 791 ; N Uacute ; B 29 -15 762 890 ;
+C -1 ; WX 577 ; N otilde ; B 49 -12 528 682 ;
+C -1 ; WX 619 ; N ntilde ; B 32 0 604 682 ;
+C -1 ; WX 506 ; N ydieresis ; B -5 -242 528 683 ;
+C -1 ; WX 635 ; N Aacute ; B -29 0 650 890 ;
+C -1 ; WX 577 ; N eth ; B 49 -12 528 742 ;
+C -1 ; WX 523 ; N acircumflex ; B 49 -12 525 720 ;
+C -1 ; WX 523 ; N aring ; B 49 -12 525 752 ;
+C -1 ; WX 762 ; N Ograve ; B 48 -15 714 890 ;
+C -1 ; WX 496 ; N ccedilla ; B 49 -230 473 502 ;
+C -1 ; WX 570 ; N multiply ; B 63 22 507 478 ;
+C -1 ; WX 570 ; N divide ; B 43 26 527 474 ;
+C -1 ; WX 380 ; N twosuperior ; B 32 272 348 680 ;
+C -1 ; WX 780 ; N Ntilde ; B 34 0 753 842 ;
+C -1 ; WX 606 ; N ugrave ; B 26 -12 591 723 ;
+C -1 ; WX 791 ; N Ucircumflex ; B 29 -15 762 876 ;
+C -1 ; WX 635 ; N Atilde ; B -29 0 650 842 ;
+C -1 ; WX 480 ; N zcaron ; B 19 0 462 720 ;
+C -1 ; WX 291 ; N idieresis ; B -19 0 310 683 ;
+C -1 ; WX 635 ; N Acircumflex ; B -29 0 650 876 ;
+C -1 ; WX 349 ; N Icircumflex ; B 22 0 328 876 ;
+C -1 ; WX 588 ; N Yacute ; B -12 0 632 890 ;
+C -1 ; WX 762 ; N Oacute ; B 48 -15 714 890 ;
+C -1 ; WX 635 ; N Adieresis ; B -29 0 650 841 ;
+C -1 ; WX 610 ; N Zcaron ; B 9 0 594 882 ;
+C -1 ; WX 523 ; N agrave ; B 49 -12 525 723 ;
+C -1 ; WX 380 ; N threesuperior ; B 36 265 339 680 ;
+C -1 ; WX 577 ; N ograve ; B 49 -12 528 723 ;
+C -1 ; WX 860 ; N threequarters ; B 50 -27 808 695 ;
+C -1 ; WX 785 ; N Eth ; B 20 0 737 692 ;
+C -1 ; WX 570 ; N plusminus ; B 43 0 527 556 ;
+C -1 ; WX 606 ; N udieresis ; B 26 -12 591 683 ;
+C -1 ; WX 514 ; N edieresis ; B 49 -12 481 683 ;
+C -1 ; WX 523 ; N aacute ; B 49 -12 525 723 ;
+C -1 ; WX 291 ; N igrave ; B 5 0 276 723 ;
+C -1 ; WX 349 ; N Idieresis ; B 13 0 337 841 ;
+C -1 ; WX 523 ; N adieresis ; B 49 -12 525 683 ;
+C -1 ; WX 349 ; N Iacute ; B 35 0 331 890 ;
+C -1 ; WX 818 ; N copyright ; B 45 -15 773 707 ;
+C -1 ; WX 349 ; N Igrave ; B 18 0 314 890 ;
+C -1 ; WX 680 ; N Ccedilla ; B 48 -230 649 707 ;
+C -1 ; WX 436 ; N scaron ; B 47 -12 400 720 ;
+C -1 ; WX 514 ; N egrave ; B 49 -12 481 723 ;
+C -1 ; WX 762 ; N Ocircumflex ; B 48 -15 714 876 ;
+C -1 ; WX 593 ; N Thorn ; B 35 0 556 692 ;
+C -1 ; WX 523 ; N atilde ; B 49 -12 525 682 ;
+C -1 ; WX 791 ; N Udieresis ; B 29 -15 762 841 ;
+C -1 ; WX 514 ; N ecircumflex ; B 49 -12 481 720 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 712
+
+KPX A z 6
+KPX A y -50
+KPX A w -45
+KPX A v -60
+KPX A u -25
+KPX A t -12
+KPX A quoteright -120
+KPX A quotedblright -120
+KPX A q -6
+KPX A p -18
+KPX A o -12
+KPX A e -6
+KPX A d -12
+KPX A c -12
+KPX A b -12
+KPX A Y -70
+KPX A X -6
+KPX A W -58
+KPX A V -72
+KPX A U -50
+KPX A T -70
+KPX A Q -24
+KPX A O -24
+KPX A G -24
+KPX A C -24
+
+KPX B y -18
+KPX B u -12
+KPX B r -12
+KPX B period -30
+KPX B o -6
+KPX B l -12
+KPX B i -12
+KPX B h -12
+KPX B e -6
+KPX B comma -20
+KPX B a -12
+KPX B W -25
+KPX B V -20
+KPX B U -20
+KPX B T -20
+
+KPX C z -18
+KPX C y -24
+KPX C u -18
+KPX C r -6
+KPX C o -12
+KPX C e -12
+KPX C a -12
+KPX C Q -6
+KPX C O -6
+KPX C G -6
+KPX C C -6
+
+KPX D y 6
+KPX D u -12
+KPX D r -12
+KPX D quoteright -20
+KPX D quotedblright -20
+KPX D period -60
+KPX D i -6
+KPX D h -12
+KPX D e -6
+KPX D comma -50
+KPX D a -6
+KPX D Y -45
+KPX D W -35
+KPX D V -35
+
+KPX E z -6
+KPX E y -30
+KPX E x -6
+KPX E w -24
+KPX E v -24
+KPX E u -12
+KPX E t -18
+KPX E r -4
+KPX E q -6
+KPX E p -18
+KPX E o -6
+KPX E n -4
+KPX E m -4
+KPX E l 5
+KPX E k 5
+KPX E j -6
+KPX E i -6
+KPX E g -6
+KPX E f -12
+KPX E e -6
+KPX E d -6
+KPX E c -6
+KPX E b -12
+KPX E Y -6
+KPX E W -6
+KPX E V -6
+
+KPX F y -18
+KPX F u -12
+KPX F r -20
+KPX F period -180
+KPX F o -36
+KPX F l -12
+KPX F i -10
+KPX F endash 20
+KPX F e -36
+KPX F comma -180
+KPX F a -48
+KPX F A -60
+
+KPX G y -18
+KPX G u -12
+KPX G r -5
+KPX G o 5
+KPX G n -5
+KPX G l -6
+KPX G i -12
+KPX G h -12
+KPX G e 5
+KPX G a -12
+
+KPX H y -24
+KPX H u -26
+KPX H o -30
+KPX H i -18
+KPX H e -30
+KPX H a -24
+
+KPX I z -6
+KPX I y -6
+KPX I x -6
+KPX I w -18
+KPX I v -24
+KPX I u -26
+KPX I t -24
+KPX I s -18
+KPX I r -12
+KPX I p -26
+KPX I o -30
+KPX I n -18
+KPX I m -18
+KPX I l -6
+KPX I k -6
+KPX I h -6
+KPX I g -10
+KPX I f -6
+KPX I e -30
+KPX I d -30
+KPX I c -30
+KPX I b -6
+KPX I a -24
+
+KPX J y -12
+KPX J u -36
+KPX J o -30
+KPX J i -20
+KPX J e -30
+KPX J bracketright 20
+KPX J braceright 20
+KPX J a -36
+
+KPX K y -60
+KPX K w -70
+KPX K v -70
+KPX K u -42
+KPX K o -30
+KPX K i 6
+KPX K e -24
+KPX K a -12
+KPX K Q -42
+KPX K O -42
+KPX K G -42
+KPX K C -42
+
+KPX L y -52
+KPX L w -58
+KPX L u -12
+KPX L quoteright -130
+KPX L quotedblright -50
+KPX L l 6
+KPX L j -6
+KPX L Y -70
+KPX L W -90
+KPX L V -100
+KPX L U -24
+KPX L T -100
+KPX L Q -18
+KPX L O -10
+KPX L G -18
+KPX L C -18
+KPX L A 12
+
+KPX M y -24
+KPX M u -36
+KPX M o -30
+KPX M n -6
+KPX M j -12
+KPX M i -12
+KPX M e -30
+KPX M d -30
+KPX M c -30
+KPX M a -12
+
+KPX N y -24
+KPX N u -30
+KPX N o -30
+KPX N i -24
+KPX N e -30
+KPX N a -30
+
+KPX O z -6
+KPX O u -6
+KPX O t -6
+KPX O s -6
+KPX O q -6
+KPX O period -60
+KPX O p -6
+KPX O o -6
+KPX O n -5
+KPX O m -5
+KPX O l -6
+KPX O k -6
+KPX O i -5
+KPX O h -12
+KPX O g -6
+KPX O e -6
+KPX O d -6
+KPX O comma -50
+KPX O c -6
+KPX O a -12
+KPX O Y -55
+KPX O X -24
+KPX O W -30
+KPX O V -18
+KPX O T -30
+KPX O A -18
+
+KPX P u -12
+KPX P t -6
+KPX P s -24
+KPX P r -12
+KPX P period -200
+KPX P o -30
+KPX P n -12
+KPX P l -6
+KPX P hyphen -40
+KPX P h -6
+KPX P e -30
+KPX P comma -200
+KPX P a -36
+KPX P I -6
+KPX P H -12
+KPX P E -6
+KPX P A -55
+
+KPX Q u -6
+KPX Q a -18
+KPX Q Y -30
+KPX Q X -24
+KPX Q W -24
+KPX Q V -18
+KPX Q U -30
+KPX Q T -24
+KPX Q A -18
+
+KPX R y -20
+KPX R u -12
+KPX R quoteright -20
+KPX R quotedblright -20
+KPX R o -20
+KPX R hyphen -30
+KPX R e -20
+KPX R d -20
+KPX R a -12
+KPX R Y -45
+KPX R W -24
+KPX R V -32
+KPX R U -30
+KPX R T -32
+KPX R Q -24
+KPX R O -24
+KPX R G -24
+KPX R C -24
+
+KPX S y -25
+KPX S w -30
+KPX S v -30
+KPX S u -24
+KPX S t -24
+KPX S r -20
+KPX S quoteright -10
+KPX S quotedblright -10
+KPX S q -5
+KPX S p -24
+KPX S o -12
+KPX S n -20
+KPX S m -20
+KPX S l -18
+KPX S k -24
+KPX S j -12
+KPX S i -20
+KPX S h -12
+KPX S e -12
+KPX S a -18
+
+KPX T z -64
+KPX T y -84
+KPX T w -100
+KPX T u -82
+KPX T semicolon -56
+KPX T s -82
+KPX T r -82
+KPX T quoteright 24
+KPX T period -110
+KPX T parenright 54
+KPX T o -100
+KPX T m -82
+KPX T i -34
+KPX T hyphen -100
+KPX T endash -50
+KPX T emdash -50
+KPX T e -100
+KPX T comma -110
+KPX T colon -50
+KPX T bracketright 54
+KPX T braceright 54
+KPX T a -100
+KPX T Y 12
+KPX T X 18
+KPX T W 6
+KPX T V 6
+KPX T T 12
+KPX T S -12
+KPX T Q -18
+KPX T O -18
+KPX T G -18
+KPX T C -18
+KPX T A -65
+
+KPX U z -30
+KPX U y -20
+KPX U x -30
+KPX U v -20
+KPX U t -36
+KPX U s -40
+KPX U r -40
+KPX U p -42
+KPX U n -40
+KPX U m -40
+KPX U l -12
+KPX U k -12
+KPX U i -28
+KPX U h -6
+KPX U g -50
+KPX U f -12
+KPX U d -45
+KPX U c -45
+KPX U b -12
+KPX U a -40
+KPX U A -40
+
+KPX V y -36
+KPX V u -40
+KPX V semicolon -45
+KPX V r -70
+KPX V quoteright 36
+KPX V quotedblright 20
+KPX V period -140
+KPX V parenright 85
+KPX V o -70
+KPX V i 6
+KPX V hyphen -60
+KPX V endash -20
+KPX V emdash -20
+KPX V e -70
+KPX V comma -140
+KPX V colon -45
+KPX V bracketright 64
+KPX V braceright 64
+KPX V a -60
+KPX V T 6
+KPX V Q -12
+KPX V O -12
+KPX V G -12
+KPX V C -12
+KPX V A -60
+
+KPX W y -50
+KPX W u -46
+KPX W semicolon -40
+KPX W r -45
+KPX W quoteright 36
+KPX W quotedblright 20
+KPX W period -110
+KPX W parenright 85
+KPX W o -65
+KPX W m -45
+KPX W i -10
+KPX W hyphen -40
+KPX W e -65
+KPX W d -65
+KPX W comma -100
+KPX W colon -40
+KPX W bracketright 64
+KPX W braceright 64
+KPX W a -60
+KPX W T 18
+KPX W Q -6
+KPX W O -6
+KPX W G -6
+KPX W C -6
+KPX W A -48
+
+KPX X y -18
+KPX X u -24
+KPX X quoteright 15
+KPX X e -6
+KPX X a -6
+KPX X Q -24
+KPX X O -30
+KPX X G -30
+KPX X C -30
+KPX X A 6
+
+KPX Y v -50
+KPX Y u -54
+KPX Y t -46
+KPX Y semicolon -37
+KPX Y quoteright 36
+KPX Y quotedblright 20
+KPX Y q -100
+KPX Y period -90
+KPX Y parenright 60
+KPX Y o -90
+KPX Y l 10
+KPX Y hyphen -50
+KPX Y emdash -20
+KPX Y e -90
+KPX Y d -90
+KPX Y comma -90
+KPX Y colon -50
+KPX Y bracketright 64
+KPX Y braceright 64
+KPX Y a -68
+KPX Y Y 12
+KPX Y X 12
+KPX Y W 12
+KPX Y V 12
+KPX Y T 12
+KPX Y Q -18
+KPX Y O -18
+KPX Y G -18
+KPX Y C -18
+KPX Y A -32
+
+KPX Z y -36
+KPX Z w -36
+KPX Z u -6
+KPX Z o -12
+KPX Z i -12
+KPX Z e -6
+KPX Z a -6
+KPX Z Q -20
+KPX Z O -20
+KPX Z G -30
+KPX Z C -20
+KPX Z A 20
+
+KPX a quoteright -70
+KPX a quotedblright -80
+
+KPX b y -25
+KPX b w -30
+KPX b v -35
+KPX b quoteright -70
+KPX b quotedblright -70
+KPX b period -40
+KPX b comma -40
+
+KPX braceleft Y 64
+KPX braceleft W 64
+KPX braceleft V 64
+KPX braceleft T 54
+KPX braceleft J 80
+
+KPX bracketleft Y 64
+KPX bracketleft W 64
+KPX bracketleft V 64
+KPX bracketleft T 54
+KPX bracketleft J 80
+
+KPX c quoteright -28
+KPX c quotedblright -28
+KPX c period -10
+
+KPX comma quoteright -50
+KPX comma quotedblright -50
+
+KPX d quoteright -24
+KPX d quotedblright -24
+
+KPX e z -4
+KPX e quoteright -60
+KPX e quotedblright -60
+KPX e period -20
+KPX e comma -20
+
+KPX f quotesingle 30
+KPX f quoteright 65
+KPX f quotedblright 56
+KPX f quotedbl 30
+KPX f parenright 100
+KPX f bracketright 100
+KPX f braceright 100
+
+KPX g quoteright -18
+KPX g quotedblright -10
+
+KPX h quoteright -80
+KPX h quotedblright -80
+
+KPX j quoteright -20
+KPX j quotedblright -20
+KPX j period -30
+KPX j comma -30
+
+KPX k quoteright -40
+KPX k quotedblright -40
+
+KPX l quoteright -10
+KPX l quotedblright -10
+
+KPX m quoteright -80
+KPX m quotedblright -80
+
+KPX n quoteright -80
+KPX n quotedblright -80
+
+KPX o z -12
+KPX o y -30
+KPX o x -18
+KPX o w -30
+KPX o v -30
+KPX o quoteright -70
+KPX o quotedblright -70
+KPX o period -40
+KPX o comma -40
+
+KPX p z -20
+KPX p y -25
+KPX p w -30
+KPX p quoteright -70
+KPX p quotedblright -70
+KPX p period -40
+KPX p comma -40
+
+KPX parenleft Y 64
+KPX parenleft W 64
+KPX parenleft V 64
+KPX parenleft T 64
+KPX parenleft J 80
+
+KPX period quoteright -50
+KPX period quotedblright -50
+
+KPX q quoteright -50
+KPX q quotedblright -50
+KPX q period -20
+KPX q comma -10
+
+KPX quotedblleft z -60
+KPX quotedblleft y -30
+KPX quotedblleft x -40
+KPX quotedblleft w -20
+KPX quotedblleft v -20
+KPX quotedblleft u -40
+KPX quotedblleft t -40
+KPX quotedblleft s -50
+KPX quotedblleft r -50
+KPX quotedblleft q -80
+KPX quotedblleft p -50
+KPX quotedblleft o -80
+KPX quotedblleft n -50
+KPX quotedblleft m -50
+KPX quotedblleft g -70
+KPX quotedblleft f -50
+KPX quotedblleft e -80
+KPX quotedblleft d -80
+KPX quotedblleft c -80
+KPX quotedblleft a -70
+KPX quotedblleft Z -20
+KPX quotedblleft Y 12
+KPX quotedblleft W 18
+KPX quotedblleft V 18
+KPX quotedblleft U -20
+KPX quotedblleft T 10
+KPX quotedblleft S -20
+KPX quotedblleft R -20
+KPX quotedblleft Q -20
+KPX quotedblleft P -20
+KPX quotedblleft O -30
+KPX quotedblleft N -20
+KPX quotedblleft M -20
+KPX quotedblleft L -20
+KPX quotedblleft K -20
+KPX quotedblleft J -40
+KPX quotedblleft I -20
+KPX quotedblleft H -20
+KPX quotedblleft G -30
+KPX quotedblleft F -20
+KPX quotedblleft E -20
+KPX quotedblleft D -20
+KPX quotedblleft C -30
+KPX quotedblleft B -20
+KPX quotedblleft A -130
+
+KPX quotedblright period -130
+KPX quotedblright comma -130
+
+KPX quoteleft z -40
+KPX quoteleft y -35
+KPX quoteleft x -30
+KPX quoteleft w -20
+KPX quoteleft v -20
+KPX quoteleft u -50
+KPX quoteleft t -40
+KPX quoteleft s -45
+KPX quoteleft r -50
+KPX quoteleft quoteleft -72
+KPX quoteleft q -70
+KPX quoteleft p -50
+KPX quoteleft o -70
+KPX quoteleft n -50
+KPX quoteleft m -50
+KPX quoteleft g -65
+KPX quoteleft f -40
+KPX quoteleft e -70
+KPX quoteleft d -70
+KPX quoteleft c -70
+KPX quoteleft a -60
+KPX quoteleft Z -20
+KPX quoteleft Y 18
+KPX quoteleft X 12
+KPX quoteleft W 18
+KPX quoteleft V 18
+KPX quoteleft U -20
+KPX quoteleft T 10
+KPX quoteleft R -20
+KPX quoteleft Q -20
+KPX quoteleft P -20
+KPX quoteleft O -30
+KPX quoteleft N -20
+KPX quoteleft M -20
+KPX quoteleft L -20
+KPX quoteleft K -20
+KPX quoteleft J -40
+KPX quoteleft I -20
+KPX quoteleft H -20
+KPX quoteleft G -40
+KPX quoteleft F -20
+KPX quoteleft E -20
+KPX quoteleft D -20
+KPX quoteleft C -30
+KPX quoteleft B -20
+KPX quoteleft A -130
+
+KPX quoteright v -40
+KPX quoteright t -75
+KPX quoteright s -110
+KPX quoteright r -70
+KPX quoteright quoteright -72
+KPX quoteright period -130
+KPX quoteright m -70
+KPX quoteright l -6
+KPX quoteright d -120
+KPX quoteright comma -130
+
+KPX r z 10
+KPX r y 18
+KPX r x 12
+KPX r w 18
+KPX r v 18
+KPX r u 8
+KPX r t 8
+KPX r semicolon 10
+KPX r quoteright -20
+KPX r quotedblright -20
+KPX r q -6
+KPX r period -60
+KPX r o -6
+KPX r n 8
+KPX r m 8
+KPX r k -6
+KPX r i 8
+KPX r hyphen -20
+KPX r h 6
+KPX r g -6
+KPX r f 8
+KPX r e -20
+KPX r d -20
+KPX r comma -60
+KPX r colon 10
+KPX r c -20
+KPX r a -10
+
+KPX s quoteright -40
+KPX s quotedblright -40
+KPX s period -20
+KPX s comma -10
+
+KPX space quotesinglbase -60
+KPX space quoteleft -40
+KPX space quotedblleft -40
+KPX space quotedblbase -60
+KPX space Y -60
+KPX space W -60
+KPX space V -60
+KPX space T -36
+
+KPX t quoteright -18
+KPX t quotedblright -18
+
+KPX u quoteright -30
+KPX u quotedblright -30
+
+KPX v semicolon 10
+KPX v quoteright 20
+KPX v quotedblright 20
+KPX v q -10
+KPX v period -90
+KPX v o -5
+KPX v e -5
+KPX v d -10
+KPX v comma -90
+KPX v colon 10
+KPX v c -6
+KPX v a -6
+
+KPX w semicolon 10
+KPX w quoteright 20
+KPX w quotedblright 20
+KPX w q -6
+KPX w period -80
+KPX w e -6
+KPX w d -6
+KPX w comma -75
+KPX w colon 10
+KPX w c -6
+
+KPX x quoteright -10
+KPX x quotedblright -20
+KPX x q -6
+KPX x o -6
+KPX x d -12
+KPX x c -12
+
+KPX y semicolon 10
+KPX y q -6
+KPX y period -95
+KPX y o -6
+KPX y hyphen -30
+KPX y e -6
+KPX y d -6
+KPX y comma -85
+KPX y colon 10
+KPX y c -6
+
+KPX z quoteright -20
+KPX z quotedblright -30
+KPX z o -6
+KPX z e -6
+KPX z d -6
+KPX z c -6
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/UTRG____.pfa b/e2e-tests/cypress/fonts/Type1/UTRG____.pfa
new file mode 100644
index 00000000..d9fa0e78
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/UTRG____.pfa
@@ -0,0 +1,1126 @@
+%!PS-AdobeFont-1.0: Utopia-Regular 001.001
+%%CreationDate: Wed Oct 2 19:10:38 1991
+%%VMusage: 32987 39879
+%% Utopia is a registered trademark of Adobe Systems Incorporated.
+11 dict begin
+/FontInfo 10 dict dup begin
+/version (001.001) readonly def
+/Notice (Copyright (c) 1989, 1991 Adobe Systems Incorporated. All Rights Reserved.Utopia is a registered trademark of Adobe Systems Incorporated.) readonly def
+/FullName (Utopia Regular) readonly def
+/FamilyName (Utopia) readonly def
+/Weight (Regular) readonly def
+/ItalicAngle 0 def
+/isFixedPitch false def
+/UnderlinePosition -100 def
+/UnderlineThickness 50 def
+end readonly def
+/FontName /Utopia-Regular def
+/Encoding StandardEncoding def
+/PaintType 0 def
+/FontType 1 def
+/FontMatrix [0.001 0 0 0.001 0 0] readonly def
+/UniqueID 36552 def
+/FontBBox{-158 -250 1158 890}readonly def
+currentdict end
+currentfile eexec
+fa444f2716d92b815f58ca9049c815358e22e32e73a3e6a653c538ee56873363
+67713b8cab082730570f5b5efcf34c2cdfb6f8dd2b7905a37c1924a2424c16e8
+711db76501f564506b0f45fef10d83c3e3c6df2dc0af7802e7f42c81b4243697
+ca09088b868d983e79e4b2c3e17321993cc8837921fd4ff7b92a1294c5ba33e6
+8fd40e63df624b51865721e034f71bf57bee0e0c2a9c169c7a626496540d45db
+585d2bc93f83e6829eacb859a194d7a57904cfae75e6188c7121003ae40153f0
+0fe6fb52f339e72d4aef38469328465a3bf0ca1ef58dfb447612eef4ada248e3
+ca525b0884971b85099d73538152affcad51d03eb4060a5c580f453ee78e5c4b
+991f77c6e9600c135395d56335a7c3a75c0341979d6b404ee496214a5d20ae06
+e17d80d37bd3540b8fe913bae1674afe37d0c41e3d9836a7b5147be32f9ba01a
+df1a73b89d30bb595a4b279032e37c8230b6d4933065947c3e9150d2c7c4d439
+d60197eb69991ef059edf886ec0bdd28158a71a2df96dcb67fc0bf81c34cf27e
+12d6ec950a75993af99fbb040af595134198151e7361672d8538cfadb4716785
+dbd48da442ccc911ae9157b14e433559dba1a1c5619e99b57274d6998eeca3a4
+7e804d8fe3a664f39f33237831385949a230a21e52a2dda733f2f89b02ef4ce2
+5d603c9bd2f8e4c0443070940f0380dd1ac60ff462a9a71965a79d787e0711df
+0a69bd86a4846965ac6c77f2e1699c4620c1bd9f5b2263e13a2213631c55f7ab
+cf60d2e63a7830d5011d9b9934bcee2103f4a5d56942ce0f590a15dab480e5e5
+723afc8e1d8d96f3e0d8629b88ee477ea6cb8121b0adddf20405f8a32756004a
+8a59b2cf922a6dc0747bc15e99079aa12ac0d9fabf8b47c5a61ca5268e01317e
+1f0a38c468ffea597a392ae3399daf7c03126987750258b5e077d703cdbe05b9
+a5fc4d80e8551c2c06a2c579272877fd462fac6569a7a9f964797df79628ba7f
+f77447fc2aefb25089ce946e0aa5c5ff21370f0cf84d8e2affa8b30891081b16
+6fb86fdfe65e7e02ee551c6b3e2e324cf7e18664883dc538018ec8cadf7093f4
+a13708013d6ca2afdf644c0155342917c2725598fcafddaf8807200682fcf04a
+4efd5919c21ed2ec2f7951c2db953ed292a54783f5c541bca68d305b05df5c5c
+f17b9af31035dd8a77ed8b54d4de7d684f54c58a28f0dc55e7c692cfe444216b
+18751c24ec1c8a00a5cbd458a0ad54e7c13f26b4c4257fd992fbd1338845fc69
+6b97a2ee6a4d4f03fd387e745eeb8e97eb67e40bacbdf1d6c08e9fa2e317d30a
+c4234559096affd5b2e65602ad9c4868e24d7f1d35e8b6679e436a010be5ebd1
+52666a26ba7f7b68a693add1d21138ad64425e9202a043cec98ebc182c152b93
+9f88cb8c89069b72f3c264e2bea7cea9aedf1a8736beb55cc77b02e3989e7013
+b4fdec2767194fa85b1aba185aa01fc8902209068fadfd1d282a59f8b1d7e30e
+3829383779fb9a28404759c595b986deb7c59d315023e542b461327e8645f440
+3d1b571403e30d988d5d17b09590d644cdaa885cf36881602f220a7891130e31
+2ed47ee703368c5beb240427e1b30446dae522143ffeac91f3bd6a3ea5c57a63
+f81919870daf71d9b522529438d5e51173973f612b3afa5e4c5c042c8a8a4ed8
+27d00b1f5a102ba8a11ee7d942e1954cf35b875abb184df026b1f600b27aca0e
+4dfea33866de9d8b6b8829c128876a2b882b1aa1518285d87de3c39bd74863aa
+229a0108f3070908664f4f77a62f0e67923d844c9ef91a6affb8a72606f5085d
+88c35fb9e8eeeb663742369e6b0ab87c82e3eec3dec4adb2098cb25a35d543a9
+70025ebe038e7057a8d0e8f7b2eb86418dc39b494884bee8c92c87f1af4cfc44
+c14a177f200389c0a23c24ef1c59e9a143756c1ac0ae50a12a82228faecf86c3
+4f7ba61f010a0d285063676097c7c3fad3b19479c8c50d712b48ab99e4858096
+a1799936df4bfc4169e5544adddf343c7fcb63e83e2b2b6eb68c8a33edf201c6
+5d89e5b0c3d21d1aad02bb07fd463f763c09dcbbf7c216b1554a9402a8eeb5cc
+9de4edf0753d8882ad183a9d14e2de51fd81daee2ddf53745a076dbbb790cdda
+4b25bcea0f98d832886f26bc15fdae9ebfe42f0a95b7d02c0f65ebb1fa0d730d
+a1f6d63c3da03f9ee0961f5e93cf943915e0dae3147e4c100fc97859f1db0e39
+a85a06f140305e9cad70247718385e9151a091aa9a09552c199e3ab164f28270
+fc60a08b30ce11e5d115577e54b57c73b2008fe0f9679cb7d60c3c80b8e63cc1
+046a093b0838c832125c9084c9d3691fdf6236f1e4b2cbbb4ce8d898d4c27a97
+5f4eb9ee7e3c2d1a694465a632356ac7c06abb42f89eb2791ecd146d29fae767
+2f132e065ae5f5432a5705414049f68b871b55c409ac3f02cddb56deb21c8d4b
+202b93e8a5f22d09b287fde695e9beab9fb790fadf1250dedfc8188b0a3975be
+db8973fd1ee42841b2bf6ae539bd46d0339dee6804a92a0c32ca204ec9897f76
+95d04cd16c3e09f37306109da8b55b5c69660682e8328724655f88a08f11f9b0
+6d3f1629240662f0a0ca024f16265503390d9adf8b521d971d419a4cbc15d322
+1eaa0b79e4a45d2f0bfc9bcbf135e44b937e4bed0a4ea896da90e40bd793ff7a
+d0bf85fed6224690f801cd8c0100c4a79be124833774b86ead4d58bfd2598e52
+6bb5d08264df1c4ccd29a918bb27d7c75d0fea288e066a74236de1828a0c9fe9
+6b1de344a4f57ff0997707f9e83c25cc5639ffcf2cd01152d9f676a80c637a69
+5cba153263f8c1d93bfc03642796263c3b460b1fb22fd4f63e742064a337d1cb
+c12ad7b5c36cfb0a62341c849e97ed8a6b5e3e93911dfbe3795f9debeb3cf7bd
+6311ccabcd1d80c76d608e0366e23725b4c27210c788f25726bcb13149c781b1
+3e77341630a99a22cd95717254844bfd4221df25ab740978011a301861c4f0cd
+cbf77e1b69b90ecd4aba88d2c0298bf525c4f6f0be9d34d74371517e485eda6b
+d38374ebc2ec4abd9b7969b426b40c356982b735289f80583b377ebca2c14068
+77d7a6516135878a18deefa530a88685cf0bb9ba03c45d283510a68b17853cbf
+c1c16428a598e078564a8d50bb1171771ff483ee395ed074d7f524eb8af23061
+5b30a01122d440365c7787ff477ce068ad1c8e4f9a409199138cf49a435326f3
+645d5e093179b96831b7dfc926ad11a2b9bf2ed3a5f4ad135278dceabe5f8aaf
+298bcf406c9c11cfb55400fcae95910ea7c09e607a1c2447ee82ce7e3faedbdc
+2b1827ec539eae6ae25927a35be1aa6b7951073746e5f684f758fcabea3cac25
+f608e033b3c92cb05b7c9738d3a6d2cd5e0c2d29b968ed7c7bb43d7fe734a959
+7636ca80ee97f6d88550c39d36db458997a98e8bef087c1000add77f8c6d24dd
+2e0f6f4772ef61c5bd5148c3d67dbe77df27a300e5d8c9e4b325a6d8cbbe8675
+15ad15966b130bf4499ea1923bef7a9047eb1b0d35acbf0b8303fa08b26c706b
+7368332adefb42b66dd9ccf61fdfba00b5aee231c2232e8d752703b0af6ae850
+ab8274563f8fead4bbe9fe860dfd95a6a7e0cfa3d8f6ebd0e066cdfd396d8c57
+f8f17bce0c5678649034a62418dd2463b8f963b43ca02d011a9a1ac4aef9c9fe
+151ef8c726e376158dacf1131f0ee855156d95730d3626003e51983624b417ad
+c258164ed5b2bd3d4e562f16da8a0b69871787cd9142d297ea3fadad3c2c6dfe
+3491c8313c2608cfb1d6a6b21d9569049031b0cfc1d9e96a7186164e04d713a3
+2f1350ab08811688619335e7f331cbf390039502cfa7f68ebcbd75937862add8
+f0c7f498d6695e9d48c4d3e07753062a992675f1eea3cbae77c7bf35433bf424
+45aeaae7876749942982520abcbe95a7ceeaff412a02f6922a1e6c05e9a94d82
+11db8144984dcec1d1fd6418934a57338e070b19ce6d45c0f861e429b3828c9a
+c98821dc4ec4d5b7f64cfef16380c478bc5a0ea0c4b82d94e3e01067b3027c73
+e93899ee5f37db55e0be88844dc7c5a538e43510a65716320dac3e01e164fe26
+3406de45c19c250cd2a9c3bb3bb7cd5fafe16d9eb7e00d012b65668104a68eff
+bd0c11b55497378896ac50abb47a6daa32f7c21cd34b19f3f797d883fc8745c7
+db21a910abd7e9733239c6751f71b126bd8e26f7cb16bcbe5a066cda9fce63e0
+a9d40a9527bdca47211648ec231d0aad9f4659cad640181b76d6b02309365348
+4e7e328bb1f7280ed4f8fb571d05a7a55842ad1dd294cc46cda410160f2f7c09
+4a34dbb0aca809a6271c8fc97f4103bd83b701f2f474e0bd1e7f37fb7d898e16
+6612f9b03e25bffde2a8d6ae1dac8b7c28b67c371ceaf8020ffc9e1e35973dee
+63cfb07a6e3eee03799b4841d195eba565c03e10998fb80fb279fa123c2e8dae
+94644927b473c414c1fd0fa0b359a279310e23fe44104f0988de3345d9aab3e1
+e64dd94ec8a6c20556d785e7e4f4bec46c0de73185fdf9a84c6f46ffe7c52c1d
+c7d62ad792987dccc225abddbb6fed504864476f1e8d0e2ae009de5f4c50bc6d
+8d3902c6c169cc0c2008dafeade6308daa473bc4f8af398ad4a8300f6b989271
+dd59f6d52ee210bab4028b4160979401ed9f626526e43b2362ff92b208a29b04
+8110c2801db3a59f1cbb89139c80bf025d45d61b2bbb6fed4456089976a94d82
+a094aa8201027413899a402050ddc0421afe5d357a8d8e0ab398e01da601a0e5
+1c6bdb86ca2d064b2d8af3ab7a8d39a564d2c2cfbb74f62aebe4c9cdb668909f
+2d83bf84894edf23a3db2d3b4ed77d091f21ff313c67a0abab68e57a541942f8
+58ebe5334687a8dfa0bc1eccfcb1b793d5aff3d7479a72ef70b3490f7ef3dbcc
+1beef6d171dc7cf8acb25d04181009407b2d61dfb7de55cc433272a46e86a74f
+a8a5bc69419e54c39220b7b3b716c5c4cac5313e4d8e1389a2217de9ebc0a4eb
+f72dd28b7b2e7481457123167f2a138a2a9ab099706c81b83e3a02a9f9e2e321
+c9658c611fba436a649d72ab9be1707e3711d20e15d778bfeeba66b09286be38
+2f903ec637a8ea274ef8a8c488c5cfeaaf616fa0e090ca2c7a767048b6086dc0
+ea0e3bbc8ef9d01a74cb264b3ca824f0d6ad786127c61d0d8ad13c31e5a25555
+ef5e64e1d19d150d24d1459e8da3ad799fa554dc7877a417f51dd1a3a02e7341
+51b6cd7a2c6915127017f748942865f30fe048b7e749bcba0e015761c0a9e1cd
+e1cbae4bb1807245d7547f062a05c1f13a753eb6fc3473caf620347338bae987
+1d2bf08764c309b32b0c915b4735481e25259f53c53c144b8ea3e8c2a4dc2725
+85e197f684936c0690e497653a46a7b86c7e57b8f4c890319279034cedc18bfc
+ebe1828d23cae6d1fa854149d1cde376470869f9383e83ccab2af11c2d35eb8f
+1bc9273cd399c7e8478ecf742c2db209479460e310f8013f1a64a385209d2e30
+000d71f5fcf62f251989a3d852b9c36e57273a60d565aa387236f61162e46821
+9f755ec5501247ae6bd14644106c04f36662afe7c3cf741664c6a4789f3f8ce5
+baf6e347f6564be8d6fd92ba9bf04ef31c9fd60a2650b1086f9a244a113d85e9
+405dcf23e29d7b46281baf01728b7355b8a1e50febf4443c2d1a2560cd47d652
+cfec6f5cbe060f8c1b20fd4720ed00186609c40771ec8db4e7e4434eff8aec89
+007816aa98863f8f8fd3ca454bc228bcf4d2522d948ab346b5137f1cb52b5a52
+3877acaca92215b9fba427112786c2fac9e5b57879d28249d2d71040aba21fc0
+f73498c856cbc6c5a5f25bbcf19ec97768b8ee9aad3436536a9ff8f22ff72206
+8555d0356ab16b1fce02b79220323bfee1ebbf156308bed2d91febd3f3a13927
+be4d619c75ed738dc1913971fab9a76bf697c6ce9db692d0495abb1034fe31c7
+23eea2feaee80aa07117c16008a7b5e00a13fe2dba4c4e3174762c28da98a191
+0d15eeef183cb994c829de35324614301a6e98311d9f5f282d28da1b485997ec
+41eab629352822a5583d307d6d417b869f6dfeb158523a581557f6acf91c4f28
+ba0af9e428099b152946769932a190410319fa3cf1274c6745ab16df56afc3eb
+4e67f521dd52f3e3b38e41d0055a8bb6f957817850d693f76c2237f78ef9e660
+9ba5ec79bb0cbb08f461991a7baf70e3f8c8d619c0ec7d3d7aeb964f2e8b7113
+8e9868c98d8fa3a5769147fb72bb82ae844c1331ab285dda9df924cf36f2200c
+b0c4b28f19aca7c4591413c8e89ce410d469a7844af4ad00b6089d1efa64de2d
+49069a4094f2af0552b68fbe03655686cd845e46a319cebb984fa717b7fc2565
+4cc9843d079512d8ad6d58d55332a962c7b20514d1b53818b47d028b59ec1176
+43d3571e690531d6980c0bbf2150f5afadf3198dfc48995afc745a8fd0b40b7b
+d48c00010ebce60dcf13ad056bb53ad3e7a3d0984dc28b8b4aa109ed4d62bbf9
+9f7be92fccdaab5c52cda9fdc972e55a933b2c4c1d2c54ed5d88ec8132d0241a
+91fda05af2a736547dbc1da85588b04ac56602cd9335d9a31bcbc50a5e13626f
+f86b9cf6bf32d2a5b6c4a3754f9447e2eefdb2207e1c277794bfa5defc88cc63
+1ff2093a0100c2db33baeb231846754a10eafd8640674dc03d737446a345eda9
+fa03143bcd01de7aa1486de001ff31f1f7e67681017a32dc46f8a9aec6cc0573
+4a0253dab3476519de29344ad7b0eee93d0e4438bc852f4ff4d018a690781924
+1730a369bd2312732816efa8000f0b30e8d19b8551857eba645f76f38257d96e
+4088b20e11f5485dafce80ab03820127a291acd4ddfadd039c03962078aee0fa
+6429e748bd3ea0290ac67f1c14eb8f63cd5b7f4a3e61b91f3428a73509151784
+3ec501946e468e8b68374ff8b0560cdd38f2eeb263106ddedaf7d54ff1ed7c65
+c9910de2993171eb6f5d9a4f0fc3368d6f6b5e57e280fd9c5c1969cc814d46ae
+81c2ab001f03e1aaff8017b6d7e84cdf0e3a747daa3d2e1d674cc49c8049e8f5
+8914f54313f1084396beb5defd89dfd0d73ff6ba76ece9939b988bc5353f221b
+e4bb40b79c19612319d196938935747e96030a5df12caadc7afe24878a2d6e82
+5230548f931259546b44b4460eded3188fd76d06a2d0392e2371036401e9ba72
+a14e50d6aac474d148a40872f222d3d3ea322c37aed0d04cbe2551e513e40943
+04b30dfb33710c66f3df8e24296604bb84e3e498200d942bf52c452f79b855f7
+bfc47420a44f80ea16be3f8e0716695d20ba1d28739ae88c04ed2848b70d7369
+46734ecac041a14ce87397706f1ddc57144b7a61231a1d0e458d3d6d014ce66a
+b6e37b46774a7a9c74d6a5b636303d3002151be679abfbb7399a0dcae5bcb5d8
+7c84bbc66fa03df92d4f5ae78e85313acd62522caf4fafd187c553302e692133
+f7971b24b92b9e0321f93e7b747bd6ae5f51cdbb247d36255740752549bcf8a0
+09e774a01bead6ad931c11453a179e0ae2d23c09acb7b2bdcb16fbecf07515da
+0ac8a4b2728b3d5c8945aaf10fcf770008a0aafd33c402b7304f6b203d0d6a7a
+be7a27608b10f9d67c39f5fa2c09ba5900f0a6e6c6bcea1a0ca14411ada0a36c
+d19c25ddd2d29ab25d4cb7dc3afaa0f5aad7e22761863cfb427fc4fb8c991251
+31cd3376c227747a6d3afedd341a4e6474ba273e687640eec85bae3bc83d762c
+3380159c2af1dc118fa1bd2382d339c5d213bcb89e89fc8241707e9bff40ec4f
+85a889421643ce7a87953bab67fda678da5111da8a95f29290ef70d672f0ef5d
+ce90ee85275392766ca752cb7a2bb9eb416ffce5166a955bcd54fa2082d942c2
+89a0fbac57b9cde759be0b98d0e6f5b51c7dbd6331730a15635edf1a3287c679
+61c0a248578b53559a3d040f8847813ad1d76480c43f4b674bc0659bebf9f1f7
+e8cd4acdc0e0fc27104601abbf25304f8d5aa221e02e73c37868baea6106e069
+88a7b580b091e6c4ef56e0b52bf9055039d2c3de83c3a6d4f8186646a6e1ec4b
+5ee53764f122509d63e339c51b15fdfdade4cba2880c66bd607977684ad01aa6
+d405553066de3d2bde7eb27b332d5bd0371881eb0a9d2c2b7728f827444a13d5
+758591eacaee4121c2a00c05b58357d8053d2223490b5124fa646965a0ddfcde
+0f99b41916f8f80d81970753daa6079aa0014e800c54052e01d0861a7d3add7c
+2b542695ad2a3d6c56190a170d23bb34829b049e2c02c69e79d597fb5d2f9ce2
+713d681f072435c8e3740fb61127f5d3bf4cf1adae5e99e66a0c472076f476c6
+171660a613a35eae2d353cf78a06dbe7dc227dff30a501a547241c6cfe8123ae
+053293e9ed0c1bca42ceddc775a9200420a37ef81552b721c077c005afcee2fd
+fb2e9088451e0260fef0be7dcaf9d0e4eeb3557241bbe429583e4fb813fe1992
+1ffec808630e4b3e64cdf9d2e664802f3f0846f0216b8422245a0363a2a6ccc7
+3f7d8b39438246611428c6c32584976b10375003469fe0efd968e21319e382f7
+50c386b57d7b7e8e4b5dd8dbdb8d816ce712bb7ec4d3a2ccb6fc5784c066f79d
+f9fab8f575b42bb18f22a7f2919364c61e81cd47d0b121bc0352247ad8d83ec9
+36891e106f85e8fe25efaf00582095dc7f16dc48b3cfbc34234aedfdd357643f
+79cea8fb1fa81b91b49f8c063fecaf8b8bf0fddd8f2063eaf6464de3e963acb4
+33a3afa3944fdcdf61597fc7d6caa8e7538a784024ed4cfd497ac35c6796fb14
+65aefd0498b994fca5c74c003c6ea1d6c8f935df6ef5cebc5ae7024986be09a1
+3a368d9da9061aa8ed1015a60778e63640addc44dcf2e32bb1d017f10a319f44
+fc8aaad5cb149ebfafc0dfcd665b07f6387f4e3fa9273815a7c0e87390b89964
+18fe077d8330190a04f348648e1eb790cc3cdb5005afa7dbeda6e102d848e725
+ce4d4a9a9e7dc16a9035892a73bfbbe130b8d059e0282693ba7f062ee031cd38
+edbf41556bdc7c34c6f2547797f2b4b202fcea7d6c1f6d6a890dc850549e2d9f
+d4fed66e6a66f93ac27cfbfe938832d17c171729fb0eedb5befd2d07fee6db06
+ee7d2e5302904d0ca4b21b40e3ed346e6976024f6ddf05ab1b7a339c42d2989b
+8afb7447b2b772f3cae03abfd55b1d14f412109e7e99057c66ed1fc1c842e336
+8f8ad95e67626219b6d538fa55e6146f7c4d2819b6ab69628d3f918d5c21ba65
+6101700f2f9b5d5399a1209e0fe5cf62cd4dd8b065abbb2024b7766478b78d89
+3ea80e633d445a240e27c7671018dbb01e4b38beb85ff02de34a4c06a092467b
+2409c7a6f7d94461d55073a87ced3a6048ee3b688042bcdaf6d4ddbde6edced1
+7c0b7a2317e60410453cba8646e00976bbf1542aa0614f93a6a323de99774176
+bb2babb068c8c29bd4e4cb7f8b3646ceaddcdb611215a5ee9e61bad78d8bd5ff
+f66177e2069d7b4f87c6fb13a61f2772e8632f158b4f1cadb8e3daf55e5df78e
+9d50a27bdece942f579490f570d3eb34cb69203627abe7bbb6c5b2323fd8fa7c
+1a9271f36f727ae3bd9bf660514a052c86625b1245e190fc91df37b7e6960fb5
+1396405a4f2de27f7146ad45f9e5e14755985c189cda794fa21d336354bedd98
+14ccc92cdecbef77ee962b78d053752a22f5ef66879af4d77cc798b2d6579d54
+9077dafbf4dcbe07c70ff56070ffcd869bbf2d96271e461c5432aacb7935ac07
+fd239929e5d27e34c91a785fdcc479b84a99dd3db6711f1026a613d24a6f5de9
+00e4a410ea22a81b1106f6e11edcf83a3a9f3adba5b22b129e3d8f30807303ed
+fe50943d68951bc743e7391d7aa1672fe0dda7fc8a6c2e87aec891a6d8ecc050
+03fbb9bc38114416c4e330c5b7b43ace475f0c8edc9abdf5fc137c9e07f1f8ea
+bb13365b3f7e3dbce2afc9388e9279136ec6e0edb289beeaed74370bcb453884
+3ce1b3afed75a47bfb50b7bf4d267b014f2480cff2d9e21685a64b5a6fad8963
+1d5ccae56079f4871aa6b9b6a7ec8982f266b6eadc060087f2f7af187dadf05f
+c31a47b30f2a250fb501f4faf7e18c3aebff55fa08c74ef27d96aae869a26b2c
+45ba8c84b958db1a7679ad0e6bd09c498543e6d20691b072d84c6760c70e8e90
+ec179e6d00f684e6c2d070f894b98d495cf2d2978b96ea13cebf5c8b9ccfbf05
+72a00358a8faa812b00c06f2ad40237839f0e2ecff57d359a2c2055a1361768e
+f816ffec8254c512e3d57ebb23da610fdfd5e1e16b97e1fe97e10a279a203e6b
+b52f0be0ae9742d1a7faf2a0d5d6e1560d6a3e8e1596fc57a3288fac6a541646
+5ad8c50485e42cb5214d4dcbb82a16c2a15d406698ddefbb5fa3dab291c4020d
+52b76c9f0c91b5516143d99d82611eeded385015cb53e07a30bab7f27f22ac73
+95210586bd245bc0d2366952c1d85ecea90e68e189fc313823433321d6ff1785
+0d8eb6f1e4f2f6153d84b4b0bf198ecca9c87a80dfc33977a9b06e994bd86800
+4ab2f317727243b9c9704fbd88a0e6fb5ab8c94f38c7e20daa078edaa9878c8f
+3509b2c135e9801c79500327adc9537318d0f6b700fe99166d4e7c8f6a41bd45
+6bda6719e0ea158b8aa25e73731e02d215294d91f44985ba21d4045b1f8dcd99
+69a0b36e07b3c7534e9401960683ac1696459b2a45e0a4133d630e5bc81024f0
+b1ed05e19a6991bcab03482ad446a1d542249cc9e1f13e091508eeab9f744a67
+85e3d64fa0c7c79275292a4284f62754d46c0835e432c3a1776396feab389c20
+775f2f94aaec26813eaffc4d1e6b4df16f7b25ad9cd3e8645dff53a354718283
+e6da4a788dca6de2521295f51bd4b41d7b1f06c02f9cf60ad1136425c7996c55
+a00798c6f180ee8bdf52c71c01b48b3798eff89600b2281d68fb2e8567d61d22
+9b31ddd64b49fd662131d12bb500ba609e2f298c237b90c17c4a29e1c4e75ad5
+c1631c7058f7aa486b9c19c760b2bbae67167472490d537d14c9c70437d8a964
+fa40effa6af2862db37b5ba62da47685971093e11103228b85a6a0acc3168486
+cb4d94f0b1b01bb35ff617b5b36a120f82f783752079b66cfbfe2088290ba9c0
+cf8255443a6230274299b82c7e0078a9435c2a48ab298954043521391cdb627a
+efc038208a97a62fefbc2284290e83c78fce56f1f6aa31428092800897d6adc8
+ba2c0bdb767e9b684dd51cb44dafbdd3f084b3ded0fa1af187bedd1e7271ba2d
+9b040ce2d98fd7a56f82191756bb8f582cb83ff0188350d8d5451671a2bb3478
+97ee71a4621d366d810945c790ec3bf4126eee53265a2ddf2bfdb8cb8d78352d
+8032e0b87fb87323d4defe5c5c974db8cf06203d0cfd1470aeb474dc0fa2d32d
+e1ac57cf43f5dc2d2751c84e10a5055ce00c594a0dea8c1f65268de077f06ff5
+c8e083d70fac84a5605eb80c81bdeb92a65fcda58979a93566b29c935b3ad83c
+a1303afe4eef046c2ed3646c0ced2e1066d4eb7f5db11f171b2440a0ca362253
+20098d9388aac930db7be1cffe5d6eb05dca2a6355df026393b99ef5e3cd70c7
+c82ee1fe5cd113766bf011bba63e78b2ddf4d86519aa68bd991d782713ccd2c1
+3f2eea70dc273576aafa2a0d2ed22e35f246e92d88c907425ad6843abbfd75e5
+78e1cbd1e59969cddfd6d26f89e617005718492b51442c55fe617da67d8ebe5f
+bef9bf22357a125d611e04d9e5e28d5178c83e2c3b073c205d7e3a792d4d0232
+c70e596680b5947e6504a7aa48c67795a15160d49d40174fea72dcce9f97edc4
+75d030d02347961b7d725b78c7024f98aa0d4fe953807b39632f0d4531c6c1f3
+7062320fb655447c355e55dac7faed0643d33f14cced8ff7398ec85fd93bef6d
+e6032c7689658a96cb9ddd856fb6c0a4be0fb15bb46c068673374bae1fa73bbc
+20dcaa9398b526128991d68eadf0b90bdfcfd097b2f858363d819fc6ca7fbb47
+6aa237cf38daa9653b5877aa322e3039f86bf54ca078c55a35301fdcad322d14
+8e65067a0e48cd62cecb0d7f50faf272b51c6d0a22c402f0df1373ddef2d3c2f
+25f3c636f9f5f8710bdb1f5998f2040b7bc4bf6dee428357a695e5f177feedc2
+be6ca3d7456f4fb7b97803df6de76aa4bd622cf080800628d35841a838371511
+04623706136658fa3cfacfc116a30542f8825d80532454675cd5c9ef6640dc79
+aeb9c37a0691d14bd260f58995b7ef2cbb236da7a93c042450d940df347494ca
+6d5df95bd04d3077b47926e34610583984253fa1cbaf4b0d544e71d5f7405e49
+41445c23b0a1c3c6882254602d0ae6582f64dddd079f2d112b0dbd31ca685bf3
+dfea6d3f310bea2b2f4cf4154cd37fb19016f703a1f9a045c7e21cc994c0f23c
+5e4a49d0ff47deee398d478302cd0c90380885ab4a59b6f3d50bfaf00d9e7f3f
+0b97562161091b4eb201c7bcd4d1cd239b00df09d03836a63e8a9419cc9dae0b
+493aca2a7fdafe0ff81b6a9896c7e57faf1355fce5f3d54afe62c018a5211fae
+935f1f9795d1606cf77673b699829c9a136c6534438ff817759f8684d5345417
+70f6554539184ad886156fd610f331532d0ed22685d56ed02fa51e265ff34a1e
+fc8cb40b749148ef80016f71cb0647e1232b7450237f686828c36dffba04aa6b
+46a526e693da4fc9e2c84d7245b76ee7dbeb360b2f92b112de8e5feb3e433321
+eb67e59f3a41c7c91e5fc51e811b2ea7a70a9562bcd8fd854d761ef290ca50c8
+16f3c58d8e48d13edc3ba242c35ca241fa15e6243aab8f4b5441cc0cffc1c9e2
+7729da8ba3fe1837504d73bb13ba53ee7029aa5a1b137244be2d16ff3ae6cd71
+8151c53d223def6463c124acf3c37bd5a42e4ccfe3db5d3a22f249d0a5cf59ed
+ab67a3a6cee4f7838314e9e167d3ac722539c6a60e5961cca947c8b9d818802e
+54162eed974283ead7dd09fc33bcefe33a78ae540a98fc6710011f0cd96300bf
+e5b9acc91af99c6b38d005481771655d1b89bf8fc5fa8d682ac36a54753a106d
+6cee95baa71b131786a76f6ca7cd143270f679c91635822a4a2285d6aa44699f
+38603c3af88b82d5658531bf36ff97bb959c44345f51ad2c990347d7f803c2d8
+3345ba5554ec0af247e7ff00f483ea94a3a6407a2d0ccc9fe5a530139336190a
+166575049e84f3964141e64f9f9fbe2feff15930340b49adc782b7ec9c2c24c7
+816b7cf5e7aa3c194b3f50af218aa2b1a69e9772a74cc661a96100f9607fec9f
+0e6a34b6d3b9b6df0d0cdef014c6e3ac43491883a33304b464c3219b883a1f2c
+9a1f882449ce7c0814be53e0a26aedb003f25f8ed3f9506efadba4b8133db373
+d66c0fb02ea5d1db6484418398a374bc43f624dc1dbcb70f9b6b97691d297b4f
+50b0b7aec4de5732ac5db32e488e8131d763ea2234a8b6ba67acc6ba688c3f09
+b1da0f7074b2ec71ef2778dc1dfdfa2b27d74756a43fdf2fb8be3fdc9189166f
+5747a30bfdd73b8507e6d41bff9607a770cd6d30a8b476cb972c896b90f15057
+f3b5095adf44ef4347da078dce7b6a41f01c29abb94e2c70e3b3a24f4169f80a
+7eff7a9b53909f97160964eedcc0862697c872a164be415d28661702c33f4edf
+bc8c172fdcd3e70d3db0341ef1ff453232617b89935a9249bef30cfb7115addc
+c06bac8b37d4363710731a69e3975c983b96bc5b8033cf0c17c4a6dee8e45f61
+53c1683d296a937f733c332fa08bc9f4c5455e308733567fb7b51300c3dd4665
+15a2d90c58d2155270e64f2a555f45084bc75cc2fcc2dd5ed0582943a7a5359a
+4fab1a5310c083343ccab56c224f114e46c8e6f8233e9fb01a352048144592ee
+fbffbe37b26c257f91220d792dd0f08da33f10baa54d92044f24646f9b998283
+1bb052d3873debe609ab925b8cab12a80067e52adaa1bc0f469b0406c975a17a
+c99594a7a8c7ff29754319338e16508989ecf307e9ea3680ee18cee760a02b76
+90e09970a402bf91895df85dde19f9f125c75558ec5002cabc1d4ff0ca1dd979
+ac53b2d742f45d787eebe0c983e12740ac3b74eaeb8ddb0a058030e2b5aaabe7
+3acc75052330fdce054d0df654b4dc7cce4b07b9e48abaa918a2c45b0675a360
+8f9aa1f307b1d93c13075a57903935978e764ef45628f219ea21740a2c720dc1
+4037ee1edec2082462f882bba13fe63a335eb8c18d95c420da75b152b5791200
+2dea7d6f2eae40f84a622f33376da36234c4baec46bcddc2a7874ba2f1f57b19
+b47a7f3a4f834b5780eefc08516d223b42a4e201194f03cc94aad45cc49cb238
+2f6af71dfd8abee459fe9f9e19f1d52659d6dd9d378c4f06c39552012e17e541
+f40704a1190b5887bd0cb07fdf4a3a3adf04639c237242cba12758e371dfb2f5
+5871567815806e74fe8cafda5031354aeecf88feaa172bf8f4cd9bde36362120
+2938fff3f84180ab0d5e2ca492631ff71835add3434bc1aca1589cf50ba27fee
+f0d86e6de410c00c5a40188a91c01d944d3b941ab249ce3de0dc988482ac835e
+ba5e0a045a6dee34f7c92ea4a1dba8c403d614dfdf3bbecafc61ee5762ae4f3e
+11511eaf682fbd245d75530476c2151c4ff6aa7959c5dd0c2f54c483ea556c95
+8774853ea5f841577eb57e89aeab3ed00e79902cf36fba0a18e460c849f4e5b3
+db02888493dc249f1c1f44bc7d7011ae65184280f5f4cb19793b3bb002a439a6
+5518fae1d5848e10ccc01e75a77fb1db966111754111044e6bd282ebf00b3731
+7227ee6f76a51e1921b2ba047a5f1f09f21f13e1cf44b3582af9c2d26df159b5
+04738c1d27acd2c898ef8148f9058dcdd4ce885927ef51c36b43fafdb08dc186
+180df681f94cb9b84c298bf7804280162f58f62fedfd7bc0e9b8214992f4063f
+7685d655f248ba694252e5ca13623f12062c4872b5989e13d9c162773f976144
+cc8fcc96013595b498562ae4261e7a1efede4b79efbe7ed5e4398d1d9e66b253
+a0f99f969c15fbdac089a5ccabbbad21402852801736f0dcc64b0d4d7cfcad33
+ee75b328427178082a96435c3c9f40e9cad04d223ab295eda1ffb193abb5718b
+f5efa9db4ae428bcfb422ef2ecb16225c67def9ef76ad21cf8626f46854ccedf
+885709053e56e6e758824ee80b0c8e4d67cab4e5d6d9e92fdd9b99912c709ff4
+57d0ae3c258229551c93fbd68e47b00bd643872e5ae53f292866e566f2f9787c
+1edd81b005745b10fafc9911644c5fee21e092fde448155c9f69ee2f55c023b1
+2eff9b86a86f8786b26069ff3f2a5f7112070db4f4bf93f40ce24d1a5c4bc5a2
+b577d39f069f506f197be8c590c73bb5f44ffd6994e2eb11786004c71d349521
+2efcba99561ba4cfd479ffe07ae4c45f5ec38c164db89b192d0dd7c07228bf2c
+0ca8374f377c4b8ae58a265cee76e71e952accb01a9cf1676faa4f49dc456c9d
+c020521a2e88d2e5c3904157d3e68158ca53371c73be43bfbe2cb78e3e1a9a15
+f29359be03fe111b87b252b060c453fe8bb0ce288a37907e7648352fbe66792e
+e4060dca4506ad6a7a8806951df8d3d29d0347fc5f4f408472edf2c4abdb7418
+28dd1afa26e6e8f84e09308bb68de881a61d106757c09840e49993551dae5915
+a28a25ee7949c3a00b7f33d3b2ac252f1b8d0896d3116396e2a7fe57c028308d
+d7864d81cabe4824bf15cb5778b941a6b5e9e46539a8206f8bdab18e92bc0aff
+2f7640dcdbc2b04f7cf5781345ea8513d00c7a22dd5f1e43b60ba05acfe45572
+76ff2374e36f5132b884ed8dacaf72d5926bdaf62a16715486bfcbe4758ae053
+687031d0fdba9199fb4221ffc9b12844c4d2e94e393f6f4ff2d6842abea45372
+12128d3234738783ae23850090718bef9adb55d26bf606dc4736e8be6ab5c079
+d5a6dcf0a6f1b1df6f33ea9c1cb5e916aac2b80395522382a1801e15b44e97bf
+c32017aa2eb223c188e63a7f5e093b7354bb25071eee2c3a545763dc93e44e35
+96b9dbd7c4b4818fe8237ee258a2032153fbae62f5dc4afc16bcad79d2550a1e
+2221482e072b9ff488bee9730aec44740b55a35d9454a4afd2542ca9e992d781
+6fb6ab598ec696cd7d58db737ea1e72cc4e4ee39e1d50cf8af3d94636239bb87
+2d4532e783ea90cb1c8278a9541e90aa0b6cb5a6522617b406b495cd2f9af263
+6d9e9453174251786b4c449aa9ae25f9f48d6eb00234003a421570f08b3081cd
+62e4de0418033395b45f559ade69a75075f363a13add803d20d13f54f0f6fa6e
+4061f653d4021f3eab7808ef212a5565de8ff35e46e8bb02660326fe6bf92cda
+02f4990e9c9653d2ec5687662b965468505f26d772499e649c3a0d69e118247a
+72077084bbf6828daca78ac4524c599df56a05ffcf11d073465fff2c52b8200f
+2ab7de3212d573455e4be882a31e3f3b92b79a9fd621c5dce0c70f09df800df4
+8a594435652f454905ac19fc7aa3d43a5a5f360778e85e0b6b82d126136f4858
+3796833f454bee0f16c40dd79fe440b263d9a354ce8e7dda6947ac89828f0a0b
+4ea40d62a651421c15fbc2d271cc47116df9ffb388093efe6a3bd4a6d21150b4
+286cb50aee34371b13083fc5cb8ce57ade9ddef9bc4a77f5ba71e9c71d159199
+b29203c64c53041e38b413b901e90bbc9c67378734c049e497cc67bff03fcac3
+684c5f18857fb8a0d941660e9b04b55209241226cab4684ed71dc9a212d2a7d8
+75d0090ebc27e3ea81cfe4daa0c3277f91544d134cec4b5da927a8b9c0727ffa
+9db44a2d6d9e34a8673903879b041462708af18ec1c658f83d4f65d3df623fc5
+db60e9ca57db545ba05c10a9a68e94f85b4c2a42a686de90a50f482cf150ca6b
+f1758078bf655b04bb9cb71336c96ed9b1a217d9a0619a747cbf82c4421f5520
+3ccb5cac0067f83d17b6e57264856491bc1220aa9b628ce4252a9932aaf5fd6a
+cba6cbc11a5cf2d34bd21c77f3963a49ca746d4f1661cbdf2803ecab28f50aae
+66d2c2dfb81f0aea5fc3ff710a5e35edadc08792b69c4af174fed9cdfb2e0c43
+b5cc87adf3fc71121b1cdfed660ae99d8d036c4896362463da11a7d7ca08c7c3
+49ca8733021acad25f152e797a946d1b4010deb8c6dbb9432979556e0cb80a89
+493d58cc933e173cbd48118605bf7de3e8a909519b1f7d58c4b9811e7d3b50c7
+297270d1c1edbcbe0f4c3e93143ca44dc128eb5b17d1cdb1cd8b8233387b6270
+d2a8215407cc1df046713aed6d1495150bbea29bebfd0c08fc1124a4624b7cd1
+fec7f156f95321825fdee4c0ea668e2fe1b0d92d6aced305ff511f13ca92afd2
+e99535de55a09a3b3e0355645df6b4547a50c2c29a2536cba3909bca6b761b91
+caf5d4ab8e9eb5cadf71eee71f0433b9b9d9ce0394b5c87d38193498f3961673
+7a748fdea2248f936dadf0d8952c33a4ce3eda258cb902f87e4f623c77151eb9
+18e6e4fdc9537fbf03b743693187dae6c9a749b5c7c07c325b9b27b025c38b48
+c5ece2d82a1b982b9d058dc49b33aa425e7eb79827b5a62a4bd7c5bd4488099f
+f7c8d9aa169ad961e17a946b4e65599e268a2a96d53a9c0147da713517f0bffd
+cd992a1ab06f6057e17eede9c7f9077fa60bf4f11e885810564c0f40b29ffd2f
+b6e9a8051c661dfa341ef0ca3c630e4df019eb057bce4c1407f9185a8ab4f882
+81d175eedd45086905342282d26ae4df524b713ef2088451f1073a59acc3a1f2
+4004418469561682e72d20f59cb9ad4ee5232aeefc996ccb77fb3beb9a635900
+06fe6d2bb606293d40410919e31db52063c87c17584854d108f711af8112eefb
+890429a772e77bd03e50db3a2a1ef371268ea9cac1bf7bb15a7746790d67cc0d
+a592fec74f0000f2d1073985fd1053537a455548d43a12bd556063a6fafb0127
+e8c1190f11b992a0fd26a3ca823cce5fcfd5742afcdc02a790a25fa57dc9359d
+ff8fb3550da075e3eb3582ef454c92493e1578788f966db39d0038d2cd7811ff
+da49bfeba9cfe0654b68fc512616439226b08264533f295ae94108168eaf284f
+290d61a62e8c8247b66a64289303ac847dee0f651c1b4575115d79a9abb9e71f
+8cf7dc4b3db7c835f2abb42c37456fe3202ed6ce061841723bb2cb759bfc78c9
+07c51737b52469f1f2dafa7ece85cc798050dc1bb63436c94ed1af3a0e6628f9
+dbb10bc24d2a6d94000b2d142f654a924911de0779ee7ca12d82e04bca01b3f0
+bf6d1679d8a4210bd58933be71e5bdd9e22ae192637c90e7b8905586d8312945
+4d68274af56c5d9636dd4976c0c8574e7804bd8fac44fef418eaa7f5a0410be5
+a4c592bf762ef8e66132f71378c37ce7c1fb485d5be0a27628125cd768ca1427
+790fb8c160a7f94b80188dbb55184ea6d657178f30c055596a370c9f40dd64c4
+f7246bba74679a347e03d7037a4994f348c8c4874df31358f02157132f98ddd2
+322231a9271018a49672897625fa0041550c99ca2cac35cee4281ed9db5cbb0a
+229fecf98aa5facfb951a1b116ed5c58cf4015a9bf03a0b1505132a46ca6b3cc
+02002c40f040e0b97303ed8c42d9f62a95610de3506c7a628546f9e4d6e9973f
+2a77cff929c7a41603a99363dd7f87f3c076990546e7814ba3150d70ce635943
+88152a0bb6d23bb4b0ad0c7edda0e5fd83842a6c2db044330119c3557ece70ef
+8297d1d86334958ed8037d6fb57ef20bcdfaefa66d2f02cf9a074dac8097469a
+b35a644035a7a0f91a813aedb3d9ec6fe2ded7c9d5a414cf394dc646df0510ba
+f27f501fb2627834376f9776d4f00869bc8b0189ce88008503bb16bbc588eaa1
+edbedee5e41b81774054df5b31c4323c4f2d2289f018a3d07dee4397b69470b8
+b1d64a694f1333130b5310763054870b88d2d017fbd62f3a2d042ddbc4cf68f8
+e1aa1f790bf2c7f598ddf275f11899e46d18d9ce4645671b14617cbee762a485
+95bf927abd6aa985c1bf117d82727432b8cc573197cd87a096daf130de5c101b
+ded264c14969b04f8f55299ff35cb77b033a1752ff04c96a4dd43e9e66fba118
+9dad547bf90c89bdbd77200042172c6243d1c465751dc96f5101eee86c76e4d5
+435d1f1fa667e7b575760681d8ef3b97f01f58e2406d3aa084552ebcf985ebed
+06baf00f2098f01976db0d4a36f39221b08c00b680ee425a9fa22b5baeb571d9
+31c9009a03a88501ca1cc8da7a3afaaceb87a92835161c3f4c84970ee219dab7
+ee9f5542d108b9326d5fc58cadc39d10527d481d303cc11cc424398756932aaa
+b06d5600e4d24a0d7dc36da7e8b32871fe36e59e1049cb9ae8c361c3c3ebb365
+5ee4a2facda18377394b68f046655d679ff8122da647ca64df6a7b99a3bae5b9
+da56fbfdce787f9f8263eae5e16f348c22aecb06398d714a69dce1a932f2b50d
+4964641ded3143548e33c879b8c03206f6feb025268f61f9cae4642e6009c804
+3adf0e4a1b5ce27a812559bbbecb7402ac2a580c6d8471dcdb02e6960fb51396
+4058ead51e6b121158c9e3d0dda75439e6eb47293c642c60a70bd9307ef337f5
+ef4601dc0eddaf6d675d17e37350ac6bf3cffb442a602054b52a5ec600f883d4
+cce1ff0d70304522df3d2cacb5bcb2cb37d453b76f7d4d915663e19c6e1335ef
+6fad8e7b76fab514abe26d837565908135cf8518819916bc9f0db07fe3b6439d
+c42de0dec1f5110b8fa5ef40289834953438a7bf83a6261dc80cfef8405eac76
+32815aef696c5b86d2cdec5d3588bb8fc5e9dc61d97d2a5869115c8260f31f06
+986110dfe2c477f80bbf6b3d9f59a6dd573eff782887c9b84bed766b63fdaf13
+dc4ac55b7a127be7c2555bfdfb63ddae8346cd6ccb6c77ac057bdb1c51372603
+8cbb594119970a50f42acae2b9d69784c5f621f53292fac082bf8a78c197af6a
+8b56466e9b14f825e45a9d8f554db27cafcae033831219f6bfb6e3568c7532c0
+ddb22ed77cfd1f46bb98480d1cea4800e3d2b8f623051da01bde6c8bfd1aac33
+240f6f89d49c824e4b5d8bfed9b536fd1773a3ea254d2e56725861a1289de1f7
+8960dd096d6ba285716881f26795345504bf0685127d91c893b57122f858e1f1
+93ce8270796fb9b0fd7246445d484005689948c40376a8e612fb8827965ba50b
+565748c8afb55247f32feb36a4245f136d6cabd1f491b753eca2104199bb3f1f
+461d1be91c8ebbe6379b2ff0eef8a6fdb5fa98029d7cf458e17e15631c2b63d1
+72264f66e45a8c108460e3e353997d9ee098f83537a22b5cef20fde0ee260b85
+c98befd03e86abce66eb6551625f8dfe379a07f53c32888e9e4c04817d6d5abc
+f02ca074a8d11754f4c6fafd3c0751886de2fac502bd1d3d9ec6b56b4d5d5c01
+90aef863969d280220e45f9bc52a18784cafa7e9bd0aba47832cd22e6c0cedb6
+28cf6a1eea4e519495c60be2f0657a43f311cf331e83e9e8109b6ca5235097e6
+d116373c76e9dce208bbd9573c7b815e33e3e39f8eda3d1d07be9ade9f74181d
+c5b9214372c93275865d0d4f3902c5dafd4fb246808dc253d15626549fd97102
+1be3384bdbeb3e70f5b846a85088dc8c4a9a411ef04cb3ee0399e2f92e889ec2
+800faedbebe32208a0f81f4fb33b97bbc9daa92a3b3a53d1f7a1a957cfcda2ba
+76ec22331c2ded3de9ccb0f51e7ca924ff8d4f0f541bba1a62da701fd2a05c02
+204ea9debffce3adeb99995087932d5963d2a6c39f715225d7662873624158b0
+a3fb470af5036028a3f04600f9e86ad203fe14dcb5360aa9cfe4e225b5abee97
+e21237ae6d76aecc97470950ee7a2d6b2c5d6ec7a88578ccd349aab544d151c9
+67d693f7de7005e4d92c5dc7b14b6979b1636092fd75b52ab6e83d7de079b2e3
+f5187467a0a6b3fb4e001ac42c3ca4ba743536d9e9a8f0a37b23a5509fd88ef8
+bbeda576aa8440eb7c76ed2c1b2309120478e5a35e071d75c16ef0086eb07ce0
+4e7242a68c801dc3c68663444db00efd46649bd00616b5a346955ba95b881e7d
+82733c8f7b563ef8a124d7312fcd0ce0dd57a26e6e104a080ae51b9b80b48960
+f1ac9f15f7ad23fdae73b7e93edb14823fadf09afe275d1503a0bbbbd57efc66
+e7b4f6468369718601a36e3b163be29fbefda393416a86f976d631b80ec34755
+9a0b2e328ffdb6a1ed1ede49ea6749d7d55b04342e40804f030b04daca04b43f
+f04d2e71c3686d9a3509086cc247524725cd7ac89fba7bd37212754fa0afa635
+317f846959f2a4ddd0dcfd1b1e5cb66bad528e47f850941d7ce69a967118f490
+95a8f8978254def9e7c5e91d736a70cc3ac70d1aad33d1195256020a9ccada88
+76c0ea39dabeebf3182b0fd89fe5be65661cdc62afd9a3ced45628adc41a1a3b
+d681dbb4c941020ce8b8478cacb0e4fceb5b6c16ed9ba14f1c3704c89114c5b0
+80d1a7115d69e6f932096eb9059a6252cd63531e9a72a36bab438a17686c41b1
+5a7616d857eebeb60c66d3b2a991fc2eb3f0b93df445b370cbfbc1754051d1bb
+b2997b93b66ecf29d6e7d3c552e0538fa41e0896d05c10684e280d683e48379e
+164b356e916afb48f42fb90acb10223b54e74a2a821787322e628f6eb02be9d0
+55f46574342907e69038ed2258dd96cfe873c6fd5ecc7d460252762e7fc4af9a
+608b65b1abd49db62324c1efb0742c2ac7453012840729e4798fb62bf15a2db5
+13fa7ddf79c204efe0d4b7cb7978aa3eb36a2c53c8d2c913ebf3debb9df9c52d
+c5ec37eed5d8924adee4ab8313467f3f0afb3bf9b424749f73715cabd5b86ec8
+7264cdcbb8c111a4bab2fbc568b65a081a8e7e18fa0d04462720baac968cb6a9
+445cdf029ab4adb8786f35a8d2e02459401b5be2ba6522a7a90b46d8333c35ba
+38437539b4b994e1c1ac8f6ec84a301b764b284ceff5ffa842f298d9c2f3c197
+54809e99550cec6ec73cc66b94d6e2980c54f17506e7c67902631704f51252cc
+1596405bf72ec7243687fc7a0de9b8c2f4b311dc8ec7fee6bc31645e4322efb2
+a59483c1734df0ab32ee58b6a156ca6b9507797706894aa412b9903203fb506f
+345be538c0e253dedcbef172cb9365f99e55b740b45a091848f8ce635f54f499
+43afe4697285361f718b6de2b79f98e0e3adae0509bf571370c425d1aa795cb4
+a1b10ced0d43b59bea690990313afe7ba7eba130f7e5639bf12c6b7dbbc1a17b
+4e0fdd4b02e7fe481ea5ca3cf45337d47488bfc09a0312b0258596771ec1e26b
+c7803227bd4a5bdfa0495c227a454a579dcab23fd4ae8034458fb61b7d89de7d
+4524d7bbdfb4e00a6b4275691be8f77a490e768309d6174379f52ddce411b55d
+80843074dc394e4e88b7311918985a58e5012c6eb8007f22903f3ee0cfa1aa61
+3128fd7b6dda6e93a6cafbc6a7f3f277868dc34939542072fa746c228745319e
+c0aeb14792d9d266b2d58c804d7f4094d5cae77bfe150debdc47f2f23b0b37c3
+b19e19cf0ebaaf50d64df8f624048c3cae65e0c5770792d6a6cb97202174b003
+a4deb8a3758a83283dc162cc88526a3d037d8ab1f823eff5115da7d2373d1d11
+56b883d419931f7956024a22312130659a8d2a946dc9cb721599fe7d5d28dd66
+12977032a94bf26215af645d8659ce53528b367e30368fe0db4b194487a4890c
+26230fd2bce623a12bc09b437df3a3faeee49571df114d6492b0c1e3ef4adb7f
+8ec807c39c3ac56aa82b2a8e7b4bce3777ce3104d3a62e8bba72608649f12b04
+e5908b5f9d167d865c9cbdc94e3e8c99e06693fd43283c58cde48e5295b327dc
+176b0ca861033c1f183a41a7002e9ebecced00584c5c1fccfeb630c20666d91f
+0eda19c9a59ef508260f6bea436b351af6518cb4e009865fea06d4fd3b2ba970
+c3632c4556861e54a6441f7398d420a99624fe52cbfb68b8c1ce2a27038809c0
+424bcb7e54c5781be2be39e52bcc1df2b74ce7828056cf8e3f5a56df1e54e7db
+d281570e270dc818aae4581789c7494baf7b1ac9cf3dcb9cbae77da67c39c6ba
+db606df4098d341e81cd5a24d1abe2dea05ea064cda4017526b956cdb79540bf
+7f1324f9e256d555ba4945767f2c72db5597abcabdc81e3015660232c67e0949
+0ba4b7a04074154e8083a8c883c8b884a1681cc2525f6cf45330d8818cf92906
+049da3a2455d2041a0cf9aeaa2ca7fd0f4e5d15580e5bae137d39baab9c400be
+0ff27d17d87970db423398e931f58afbc415758b1b1a8312facde8c4451dc91a
+09cdfa481770f0e445d35b98460b6a7b3e93f2b3d5f0b49b967a3819c13ff6b0
+4b3e665d95d8760fc380ba7f7d400154684e081ce7e2000c6e3a02322d3f3835
+4e49daf3c8bbb58422d64e73e0a491d3edfdb599614ef55b603b00d8e1aa200a
+225f39bc76f58506ee900c2db1d8fd16872998017927c4a561b83b6167a34ca9
+4407b21a2607d96846a9bb7b543382980e39fc1a9dd9f47c562de35e18a341b0
+63878dc89f993080a1d995ef07991d52ea00c6372863b56b744344541feddbf2
+75b432e36368e85161e1ad0ceeb772939ace8a7db790c66d5b821a90d1cccf7d
+ec5b2970a04efcc3060b0c163594d7b4f0215cc30b01e71b97f382e11f27b871
+922ce013ba8fc3bb678bd64630936f29f853e1379760bde9562f8708f8905a59
+6df302ff8f289896a7a38809246e2ece8e99e96b6b471916c883bb7c948e8fac
+6588e2b30bfed55f074fdb0989b85fdfd023f15f1ff89c2f29f4f5381c5ef9ff
+8e702aa40c1c6dc7aa48430c564ed5712ebb99af607df5860af30e1b4ec1a492
+6b9ddf2aac1352f4b15613cf6e86708c73bf73728f3bba7eb6b0d30c427454b3
+c8e882eeeaa998e9f02d10a787f6017a5c0a55027d2ea1da1485ab60a397f449
+4f2fab1106c23e3ed97dd268a6f5a9abb2f0cb470e1ee6411b27708b1f2cc7f6
+9efde016cf7c122903ac89dc47aa11d0c45414b8264bebce5850b39884195860
+39a7cb52c877e5ea6a0bbcbfbf3a104c0a66af928f6aaaf062b8da0fdb290f51
+9b26b6b2cf2fa49ff36e7435b86f720c06036f2a322c3fd0fb37ea7b850ea80c
+6cd6b4224e8eddd5adcf97f69d9c80102ae4f6f6e5663d0e47ccf992e11233db
+5f37e9efeb51f8cb754b919a80c0f0999311d1784221347d746be1590d077f8f
+c8a98ec3ba007a04866bf3e1e24bbdbe7839bb7adedb55c3584ce2e2b85a631a
+5bee6015589a877604e67121ede937afca89af6e186d10d3fa057c9703b17f26
+30025f6332087e97b1ce60c7e26e48d546ae484dbc604a1b33528b5dc41bc2b1
+1b9d717dcef2d469f7b5f0b3047876c578f3dcb15425678136229a7387966c00
+593c6e96e603f222d4bf10880978e1d0f2e246176e6daed0ecf75e30430ed2a1
+8907963577026bf6689b4a4c420c5a622602f2390ef9fbd5f6c716c8550acf50
+3180e80c95e8d4db4a8fa3eaccd85672b2fb2c0f7b0f8deb4955bd625f131904
+289166003bb3451c1d82b19c461b21f9523681468ea247f28d6e8eae788cf06f
+3148ea2d53810077c14b98b5aa1bb2294b202885808854bc6107f4667ebaec53
+6dca8dac30a933b183f5465bdb24ac02a6fe61d7c440e068d5c1b2511599971d
+09ca25be37e1baa6580850a7656ef4875ac5dca306ae04090496932140464a48
+2b60cf202b4e308a97d4f75123e655e4d210b6f2f285ff921a9906028e331aba
+6542a742fd93bbb77167d04267e3b7fa862437c37c2a8cbb1e4085ce24887ad9
+b00155d04414e5e8d35903c4b2f842b43a333442d17a0545681a20188e6f58ea
+bee8d954f42963533dead29cfeb41dc6cb8e70c122948be88afec65d315d3a1e
+8440ec98d8191c891affca48a8fcb5feba24cdb0716466840bdcec527520aa6e
+2fc538993766cd018effd726855a2759a0b90c12995b475d6e4703a8fb39a59f
+1499ca0079d2a25ab06e0c8772889354840c25e10cc6706e9108b62ae8e545dd
+1be37905f8adb05fa0265b736c55ffd4ccfd8b0e4ae6b558c1debdf02a836d0a
+bee910df3e245d668cd878f528b6d9d16f4a0fa5540623182028ca199a0a8386
+ae47cda55b8c71081a78efdda2d709d973d69ef1b8f27db52ebb298bdf2fb718
+afff5377c78edbc3a58fa3d56a3b811bf5873a8f787897c62ce1a281141df5af
+be323c10e74c8ce430dd192dccde81adc639fef9e5fbfecee5be9d0da1547211
+6d6961617b07b6d8a8e184969da1cecc18745012ced3834fa40dce5a0e93a673
+8f51f43afb5ad5ae0939f4d3cae2b1e386fbefd8c840e2685b156c4d2f2cb563
+511bfd502ea44f80eab5a58ea38a682e863e13b07a442864c1d8215db4e824c5
+bd5abb55ed959042b65bb7d1272c0e4d429024cb101d8d340e3c9b918661a55a
+d29cbd42b25000959044f9d5f9256410d5dbdc9619a54229a1ca1a96f939cd04
+7fb87288db04a3a105e61b967467123d186ace108abef8ea741a4f3aa45738cd
+37b9af8a997c9f6a4cf1b335e3248000dcad05d7565aa99dc144076bb70721b9
+eafa34f716ca82830e8981c4368559f859ebe5a89ef467193a08a3ce62822321
+4637619f8ec9595107d156be7153b6433bedf24d330968dd9153bb90a09f0a91
+43c851d4a5de88c60b0a5d8e3fd2ce90617d14c2ad91a3b9e54b5447bc0fc905
+d27e961b1888ae18d5455fd5878cd43849dcbc2b01a1cfd2543ef89aa4ac0a07
+e4b3bc437520361daada75f76668e37daf5c2f21133653ea1855851a3fac8a3c
+a8bcfc449febdcb4566b0ade704fb68e8715eb37461750897b802d099af47ba5
+3efd1f74f9d13ee62aa163174cf38d8e75f793f763f9ab03fafd8d52bef23f5f
+86f4da2b1cfbea8233805b9d9e47e7c4232631a56360dea075491e410ce10b33
+8e9bb61387fda4dee805d038c67ef2c9cf36ef4f2d7705ec90002afa3ee52059
+d4412a1ace2939ec8753653aab214344cc65b2c2e2a0fca95fe6144953c4b2f0
+8dd8b6d0bb097ee5d333e4f9c0c7a54687c3f2745a5ca79dc3238ab554ced0f6
+29707805440078bbbaedc13a2ebf81fe9d5686b9ea7aff8d6d98cefb0f453fbc
+441e9d82023541d3dcdb8d255e16a7b48832bfbabf7802161c7b164bd7fa7035
+8cb6f75c1dd4838277a5baabb00276394b7d3af5dd1ec4db34c33e73c6be810e
+d1e1c0f07b397c466ca035edadc839ccc6c3b7b07ec1f669ed0798d1296b191e
+31551b05356c2fed9fe7680e0b5e1ebc1dd117730554c57fa82854729f3e4c6b
+2428a8de3d3555ffe2220259ba377772616f367966c983a3ac18b04471506e6d
+07fce9847e39bd3a845bb894f7b0e04daea1f10f8a23d030a0054659db2b8949
+a79ff3d3df5e76e7bc5a0163e1d108e30d47c01a0d17780736a36b664372f6fd
+71f65fda05da8fa900c3f9e6f78c2e3a83e97cbf56aba6e4d6be0faa5ec5d7e0
+bb97e659c6c1a778354907ed74bd12d79d7a0b4fa78b2a157c3fafb6814b4759
+3db04c2a8b16f3635efb2d03525be6d6372560bc4c96e489e971559e49960bfa
+a5be48071bc009df1482b6c9b452dd558cf537ed3263a10b278ec050492728d0
+4f335c50b2ff81c70a78f8d8d3f2e3f9efe9efcc2c5f4e52e00b97ead7d11295
+912b35bd0dab31c9c3e661db42eb98b86e090e55db806b16035848de4b60bc07
+f6b7d9ba8841a73471692dcaf020cda34343fb71a18910aa400927d4359c535d
+773d3e4b7b95fc2b10292a38660f0aeee380148ab062142399c767c4add99367
+f28c35d5b8aea728aded4ae367b41d43b6adb2032410d70dd875828fdcc5c976
+e221f05c764c5042d2a7ab671a234a4baaef58054064a3265d6e5c92dc532663
+87c81c142f1b94ed0f5a16eb83fa9811c475aa0dc93f9ccf237970904d983cae
+a7914a8e481400e58f431701bf0cb884c1e5c1f52d592ad3caa7d8a37d1c9568
+acc90fde3c96fca19bede3d8d92114d4702e1d38e9526fb918c2b8b8a80fe52e
+5545fdc287108a435029613684952b6d9f5b14ecd5b02d607a311b90b84d68df
+ef5dc37f2c55ac5aba975bbe16f1f18b2f17b4a9fb96754fa1228a848fbc6fb1
+c874307680c9c58f46e69bd27ca7e55e7cdd7cff4271341dcb34ee5b300e8dd4
+bdec4fee74bc74cdbbd0ecacfc1a4ac7f4714b4bc308ce6d88438920fca53ece
+8367968559eaf9df11a0d3bb80e650e4d836ca854dc1556b5ef36b9e64bf60fe
+650125a292b63ef6982b7fc80de9903cfc18ad9a1a14dc03d32e9ee15adcacc5
+a9b11139757d948bf23e547769f4d9e897ba15ef4a495735af47de7bdaa63765
+40d035a6ae198f03ef371a49fa624478fee97359bdbab730e4750a79e4ecaa3d
+182c6bcf9af7f132392b5e0940c10d8cbb27a58f84001a951c356706ad2463b7
+24baaf3a13bb4aabd80f4c1d32d48a0a5a5be34b03fb140c638de3cfb14c4c1b
+ca45d82c270692d193f0aa4286cfa414beaa7be6908e4d1208b5c2ed29407091
+c6fd5e78400d148a9ae24dc6589dca89ada0ddf0126203229b33de78f409c247
+90ec215d8889e450b6041406aeb5732dea90891059e6f03edba61e003ef6eac6
+925e213b1b4cb51fec8578d36527de080e9b6a2f2a8cc08dd888b705e580f4a0
+5dfac8b6c05b297368fe786caf8730fb703b28ff3fb63c5183f4f8f97719e4ee
+d65d13043f1b28b01a2b555ce07acaaf98e47d034e6a3e271122935f6224a167
+5b4cd9fa85ee51deb3b132b36fccd473d1ec278c87a6e4c2976ea93c5348cd6b
+9eb4dddf8f3c9fd6d5b7a746756762f641adc4e1bce84423d589cd4d66d521b4
+a99ea4c0766e9de460ba8f32d580383884a9c5e4907d7469752ee7d68d04372e
+db9579b6b20fdc55c9bc29b227248252f595272fd12639d0f2179f27754adc25
+334f39f6205ba259f5544c39dada5d7ff050d80afeac0c552fd467b10023e530
+f791ad4b12d04665a9f9e04f7fa19f74c73890f6bffd42a7da2947ae70dd13d5
+8ab82e2c44a7bf38c6bce43d7026441418f12fa7a2a7cb81b7e4d4d0652d13b2
+2d6b130dbf0b6433db47ebd76cf0ee4f2aca9a4d8c9b3339c03087f871793105
+d25f5f4591a8c6af03c42ad8bc129437b5698a6c67fb36ef68ca0e9291a3eedd
+1e5fbe6e97a8a56482c0c2f99919648a24e4067c6180a35f37e066af4134abd4
+e366adf29467371a14c80002b5afc5003db922fa92af13306de6ad27edc0ff47
+ad4da4af9771c76ef2c77b54fe2a67719478718989abf9beefbd962b449ad634
+9ae7fce4e6f425e752460dbad42163fe5e9f8eeb93375fb3f46990445ef2e59b
+de0045c70a03300752482f1ddc1755806babc181224471af546b31584d50ab56
+9ff247586ef4e52363df927b8810a5975152d92915572e13c92a65e5444a8ca0
+46fcd260cc88761a3ec8834e169af050408655605c793f9298edb5f657fd9e0e
+0142cbcc53e74a5b00c3d0c77222b4b5df610ece643275968122e36135d86ada
+7fbea4d22e1b51017274365069d75851b50e8401c1c34a228c23c10cbe330dc6
+4ed9b9aca8f3069f636e286bba6cad0159321d5c27718f410b39f99f4724aaec
+1afc7f6ff415a8fc73d5e26be4cd6e93b5464b764b7cac70827d48a3fa61c7a0
+f1943551599bb6216bb70b19a1a0b763e2f6b1c4babd95094bb87c222fdaf5cf
+9eb0467a5c93a6c42f4ad9be37d0137a057aeee887f9ced56ee8ccd85bb8c5c7
+7de9bcbcb7200c063735150ae6f2fa20cf3cd920d5198483ecb81c0eda832843
+27e0c1828a244ff1106323d4ddef2c71430b4c4de536abbb28f87e5ff93c3a23
+7697a73cd0653bca405e167aa727b132e98092629971184405e342bd802d5b72
+f1a61d42152936472892786973ef23a6f4254b3c3a42531fe34af9569c466373
+8bb88d96f27a8478a732fe405840e685aa416052a8a86beb62fad458e6b90c17
+a45b1ded2eb9a31e2c2f7ae2832c34326dba870eb3fe7439fc69d6f26d5572c6
+ce9b77a07d977392f52594c4610d5c1974378d2626a79d7f084ece540d32ed18
+5ef252ad0b4be2fc499cc708cccb88a80ae59a31e3038fef230dd0c101781d95
+b754338c0286917d565feda78b54e9a46f929fe351c74b649d05e014f4b9f941
+14d84223a0f3b11f6985fac073e2e6a3124b664883a92a0efb9b1d017f823525
+fac9bc150808a58caad6b7c0f4892e0285d86e79d587686d0a52284572ccde01
+7a9eb12d4559e1793e09f92a84e99a0ca40e00024a9d80ecfafe6a4a0afd607b
+ef3c195148ca0699a363057b98be0ff2981bb9413fae9891d8ce9fa723c278f8
+828be8479cc8f1a792d5cc074bb7ec4c3e65c1e2ef34c14241fd0b6c097ad945
+479e7a932aa4654f34705cabc11d8fae6f4aebed345c325f907bfb04b41b35f2
+4d261885880bfb5b1f98b138ef31e3a7b96e439232e4d0d9bb84139f41f48561
+d828648a60ed3ae4f092b86bcd6789015b085506fe1f266cc02ff5c5f3ad7280
+4456b4ef8ad3c1eb2f4fb956f726dcfe2d49a73d7e9f7abca13cde848550cde8
+7e083b85084000f39d390d707a017c63b44a9563547648c48f3d3e5c577a9e15
+a7e663c19b7d82aaa700229bb541d7dc95d83c3a953670c6aa4ba8a95e2119f8
+ed8b056d8dc936c4fea8e8b298819a56cf089c0be99820bf2eb51455e3482f57
+62acb88c1baa53ac2dbae3030043e8992ebe1a7863e47d9e106a60fbb83355f3
+b98fb2744652df95ce801cc169936cb12c211aff9d77ae43dc7fbef2112e2d44
+a3ecd9a8c2cd6fc94b4ecc6d1f5b2fdacdfd153e11d8c936d822306ce7cd6d7a
+2172d8f72ecff7f8a4d1eab8247c1261f8df09ad23e5893f900e1e414d13c25a
+1f235af5fff7743baac23af56c8d8baa12e095ace339a4541e87eed758ae490d
+04da6c27bdfae6c59e263c3143caf2febad0f61a618a372052f3f550e80f6a25
+3109a9d2cd667ba408e019fb67f8360d5590850e1ac72160f57ff00cb8e766c1
+dfd23f82fb24b407dbe5cd232641880da9054ce3934475db0ecbfca3cf56eb3b
+5d5f9d5b69a847eb7bae3f80f5d96e49cc11251cd4e3ec235b211a75551d3df6
+8c9bf13c5cb2191f42a92d9ef1419a0b31996a53a1b13235b6dedf6719ae00af
+af731d1bdeb99eafdb8ac4007e4a689aaffe5afa456ef93cb02b4a6842d60ce1
+cd0b0d9ec8c90fc47779fc2494dbc683b6db5360887f367ad6f5cc2986d21cff
+622f47f4dd116d5e0300b0d1d5ac15ff4162435c7a10ed614359d63d230132c7
+bab8dbc07d28aa915a91c5bee6f3073f6de313d561c0ef9c8746ce87cee5d42b
+74a9bb276cb8797f1715136b8a181a7ce12c85f5b9e288ca852617add6b72ab6
+07eace83bf400342f5273f2670212358c22192bd0ad7a5905074e42081df0fe4
+de698f14471fb3162a847f5dfb83a84f93f8ee6b6519ea588e0d4baff3953bf8
+81b78049dcbd4b84ce45bdc45499e3a5776b8e4e9063137d4e2556aa2e62b2a9
+eb5ce11735ed65ed5cd49135bba4b6d5f184eed7933f698157525da9793c8b10
+41d1bdf92d244fe701d30ace06cc28dc4afbc99ac05f2b8ece0bd19771c7337d
+d7470206c9e4f9ce5241ba4f038c02a8db27f75218875cf8334ed4bdb739a13c
+9d1469d58033f821c98b3684b51731451d3b05dcb1a7a691d3f0e2fe44a82688
+cc91e3711114d2997e159c81408665b2050c89e10a045ebbec019bb9e7567d95
+9176ee0cd69104974a26c6c7da1f690b6503a75cfaeed44e9e37be1a9454b7bb
+921f7922c213b175abd329b680f5def8130c911c25f4ba34fb2bf49c65ce74bb
+223904c11d7860ee74ce7aa113fc40a64fa702db89517b0fd444d0166cff31f7
+da476a7415e5c308ed98b09cb41adad1807694c1a39a20d3ffe0f33211e36d54
+395061b0df33dda283e1d7f5a9399bc144ccb6863611e7aac1817fd2b95182dc
+92755c6bac1b4074dcf43e556641616541284d5eb13fb49a4b375c31df13db9f
+ed989c06f374582862caad3b745fec8145649dcc6b23adf6b4852b6021394dd6
+a19ce0cbfe5b041afdad277df48db2955de8cda9a54379ed246370442196dd80
+0875fb8a65e3300e3e4b97ee6cb4134aaa59ee3517e285982a4e65ce966836f3
+0465e0a7d4bd636870dab95869fbe7b7b9cf28db23839a964612322be21c4f0c
+3cab665c4dd6f40150542c3ac64d147c09e5cc969642154a35edcae9c119bdac
+2b82e582e2ff515e1c88f91f474e03381368417b4084f6788a8e19519c9f771d
+92133700a7ffbf3e82ae50fb19f59a7a385d169d1393c150b88e5156c41e28fd
+055eeeac7fee07c6a3be5a8587be73b77826274bd3d35574e2ad12bd1551be0d
+d4ff959077cdff691b265cb353ade31f77ecf60057c0eba5c51a7bdd69739633
+b45c312f9b77b0d87ec77fdac0049645c979663dc0314c8f101681e0e47aeeff
+fc8f7d39a57009ae49735d6c386262bd09cad0f52d862b60832ead3b3cbd3efc
+bb992f901443f78f45f9112a8b79d83888b412803e5621e70dc7b2eb475a7a3e
+0d07c36ab6dd2eb352738536c879ca83b31906ebc89c7304dd1cf95559cf7bae
+eecc29a5d0f839b64c0a24b6b9135dd0fef4bbfa01b7d6a86d407abeda8775a2
+6e20bdc6d2189cd7055b39d30315b9ba68f717840c10f27b88e22075a7288c8c
+3db83c27b55d6032a2b71f326469af86bc7342f43c4c95c8b13bf295ad130cb7
+421ce98e16cb34afd5342289819a8927447c0df7c98e9a965ee83269056a7c91
+961efa627927d007bf3836ff5eab5b6241ec8debc29dba3055d38101d1032307
+86644c8ae82f2c516028f780853be93fb7a220d71e60dafc26e582065444bda3
+5afe0470dfc2f2faa9d2af52eb11f048b16ebc88f23f33944085e1d9b1e85870
+18029b166b6a6eaf36f2507415fc1a58ad4b403c89994910896bd32acb360703
+17534352e8b001ea7a79e81c07e740c3dfae6f247579f957fac2c71cc792f7f0
+1c5d51b81f5029866f454fefc4c15fbc369d0b5f45633fe46a0fb141ad869770
+91af47762fb7c2291a413e51f9d697864c8a7018818a03203bda7d356382fe40
+b271118745973af3455e9021cffffc9c429273404e0813699841332ae86aa519
+608e948ba76806963b3656c44263a1c5df864e8634cecc6f8dc053ffedfcf030
+fb3c759daf5376152d39ad6e724f2a09a06e77bc1eb78fac830149954261f8cb
+765d2aaef1847073e6a5ed1d965ee9d3ef47650fc131a229bd68a22aa3bab374
+b0ca5b3ae476fd880957cc7c08f8729185aed5de06dc4ac1727ca7942ca50e7d
+8bd0c697d43c70cc1992205c9b914312b4a91344d47be4a9ba955da671977af9
+a92f0102e56705575bbb13106995f75de702f0eef35474ac55608c72bd513cb6
+e6ad69f1dd84869c03cf13d22550471c04cc72a5664d3154d56886fca7cd18fb
+e7e172dd975923c6e6a34dd59653260a28d90bd16e5ef7ed6a76a55345b08015
+b5e1e209e9db3471b9d0008174922d1e120f00620519d526e85a80fbdd658357
+56428f1549147e1adcf069508723625f929ceacdcfbcb0eac8337b3de3276894
+1298a952cc3fb5f55589aca8bb44111dd1023305f8a1d2b70d1da6fd510efddf
+a55a18dfb4cc9a1b4378f9dba73e29d16daed328d3b915821159b0bfb6872240
+acd1a3fbda983af9d64188be5412b15a03b52982bb60a7748f06897c63d97775
+3e60bda00e6cb10c4fd1c14a49a9ba5383f025393f79efffe774ad9cb66bdb11
+e3feae202ac9ab6dc25eb347b967b9a9af308368cd0702dbdd5f8ca2744eaad8
+0f0a9093c127b1b01bc718e7979c40d2c3c130dc3e9eaeed82e44f12872ff753
+ab30138a8b8ffa23123abf35e67a20a44fce65e6f7d20abaf919f8e1dae23d04
+5e3c5ff827a3ae677e0ec586f828a0f2a60e61eb82019ac0e1798af174de34f4
+d65c27344cd9f575de52695e1564d76b3bcd81d02fcb1ad4b12499fdd6ebfba7
+c1c55c98c7418936fcab4606dc7203dbca8471df5828ba2a8d6ca99d4aeaeb7c
+bef1d59a6daa5d0e7ec0a82fd392790b8e17d25f51cdf802784931d9f10b824d
+ab7026fe65d2832d166cfb9b108a75b02ac5b74c7726cb5f178784f68d478ca7
+e29097b39014e81f10828dd37e22889551489ba2f6a2f53c72e831c75d2c36fc
+e19cbb452652673c10a79b0eb2cd2bc4360a464a8bed2e369cf49660c9ece723
+7d8cbec3c3d7fe2b223b469a0feaccea1451d1cb931ecc6c307132d89a281fa5
+85626cd2ab00279137c326061a1f3165b68d04ce49bf0d308a21168d54f4a3f0
+7d8b8984b41248a4b5ba90c1b88403e69c920179c41c83a5e2dca301dc67bc54
+7171f622980e28f452568ea30e9dfb145d06afff9e28f43b04535ac63250cd37
+22ffda3ddc5049cc71ff4592766a786190c7fe9c7e87331b3bf1c8c25e289e00
+e9aa9e8ac24a978998366cb808f1dd3bf204af1adf8b6c3fcc93482510c720af
+03e18d7f6820935881c4732bc2bd68b1663260581ac200be9b0dacb1e6a910ee
+d5186437c5bdb966b939edaacabc104c3db19553117de2371839e6034f0f9f9b
+878626fb6de546c64224475d416418a1fffd40341edf45f4d91df8571000b6c4
+e7e890ee4ca95e65f18bd0ac230b98d2487a59af084feb16e4aa48cdcd150549
+f93a03156ff8b76913decf03fb5ca3213b14686b664daab69d9d6f056cc6d372
+31122a599b78080a0ef40e75b9d61b17612c0982690bab7a8dff6265189ea5cb
+4d8607b8a3447e2de6463f97e35c40efd806d021d47f01568ad586ea33fcdd4b
+37680417b08ed5fd7bb1cf72b3751fd861e0f5170d6cb02a803c908420482226
+e009240ba80b0275b1402f6536da71325bd6c88e2d0642d60f964ed85acfc28d
+d0fbcc7ebc3d462cb97fc41b8c4bf8352a09d71ccf318db3f2ef5e16d6711c37
+93bd1ab7f7c73db89c93b7843c2c2f2deb720a2e840fda79d3220ac62c280628
+0062d213b27010e5332a1bfff9ae17063c4aac27a190a5559933cffe1fb01db3
+b048fbb50add93b115ff5370ca33678389a703bb381841ef19d8cd1b930c2c3e
+0f1211744ea18b15e6b29d8fb3fee9f5416718e1da724b7b3a1e82a28ee059fe
+463640a5fa80025208ecdd98c0bab3ba2cbbcd80b730686ca27b14240fb1e9a5
+c751f88f42687a2901c489c8e7e428616ae0553d503d093729b64edf3937434a
+390e6c6e1094848d10511feedeaf239a1afeecb5981ff1f6d042914f02f17847
+4ceb12269438aa42a0f9bde00db8783c01383be5c3a45941f8afd0177055870f
+484eca20fe4288a96ba1d7a341b3c48cd24ba4dcb14e703db1389a5659e86ee6
+e4884fa3363238f8532d950d5b1566dc7f68eb0a31a0aa314deefdec9a87a7a6
+e7fbdf52e14cf413fd6029a77aeb5f0806ba51f9e63063185c926ad04df113e9
+265be0f9fc4e71d2b18680bbd314dae429c9eb2d84789bb85064468c26c04215
+321132cd4eac9046092f1979e2833d2d5d1a724d10629a0d4e9c7e03a9d3b2b6
+3c07d9f4cfed06a476ccd1941a071679df08f38154c2936fa47177986edac8a7
+925814c83814c705994bb0c9546a1fbce22111adaad613960c0a95d98d9452b8
+7441c3666b539950a27ce6d892c2d65b412fc58547ab21a6d02f1209cd304235
+ec32f4349fda2d1a6d9299c9ad560781dcd011e1d2d7cf91ecb6f5193bf9a33e
+9f73a66b5184f9266afc4183131e7854f949d4114c48cc079cb892e8cae078c9
+c88223e65723bbdd78e23843515712bff13f4544d43a144fd180639c00e0f6e3
+9f871f8c056d8e774fd3d05175e8b411038483f333e18f169048ff21000fe84b
+2541309205f6635a05de58dd71efacb7182f3972ea4aa2c5a6483ac9e38ddb1d
+008e5a2f4940fcf820987e2bb7b73fa38f8441806ac0de93736109af54048a1d
+e95cab99b1f3c5e104af17c67f0e0a99a0dd9c7cac7d917a8a0032abae8c577b
+f6a647f505fca2e854998057387da13f8189a3f9bde597d66a1d87388d840f09
+8a9e712c6e3485eb1b8005e3e3224d959b925a237e167e10965f6aed03941a4f
+90354b6bc6dcab1a2079d0fec8afb174b57d339252bc36a554682cf59773fca0
+36230a8bfe5538f7770e126ebd9dd87d800663c14f5f8a4d4bb935fe7d05d2e2
+11b720bfb754d323a8a252919a048db606eac7c590ad68115d052a78d8f0f7b4
+89e048372b293ae6a44169ea795cf46d25637c7252bc7a0062896f546624e151
+891ca2e70dac19e06a3fd3c67de2ff19c3d3838413f82a38369c5f2166ccd869
+5d4dbd9856755ab9f7c94810488bbc713b1aba7f66c07155138ce859a79f023e
+8954decac00adaa02e38cdfb927460cf42cd34caaaa62acb7ad369f917bdabef
+9e2103e0117f6f99b1e9dd73edae4b8c353f6961c8f078327d7256b5ca6a5996
+bcaf0cf21718f574f6f2b501dc8a3b115d4af7af8062e17a9f43ef05c1103ed1
+b406b350ad72f31ed50de231536a0ca1db4373b3641b39471774426a87d5731b
+7aebab1f6a9cc832f7a8e542abe1b8815d20addf94c143f4824ad09d702acca5
+f80bc581e0c848248613f5d86a006ed509a5a55591852200aa9a3534e90ae0fe
+95098ccb45bc63a4d86e2e1883db0bd1e293360158a5403467b96c1b9e11f082
+ac3d3a5ca47a04b7605ba54c8f9b34abe2fbeade06c3f3d32d667b6a0074eeb8
+84a40571a4e5f2c31483accf78230043c5a17f8da13b1bc9fa92d1db17724bac
+73e48427410b582352164389f95357f53902454e167ab86c5a5c404b648c4c37
+aaced6c49af4479405fb9d32f6cd78e2114b820cc03877c29cf4707642c942c7
+3874aa52f3f2955ca7832a12545f745cfb8768b1d24354f9bf721d55bd9d0722
+acd1fbbea4b1f26128902892bfafc8c9b1c5860f33321edc5afe7009f6ee10e8
+d5cd98fa521b3f45869b41f8ca4032e8f814ed3eb0dac7828893cc1ef2649390
+e70cef649f6c469c830a2820ac2b19bce71b76e2c7fb653093b9fcf65477ea03
+0e09e3de1306a2131a1792a616755b670e91a002166433e03a221310b633f96f
+7c4671b45b9e5933f67019ce6992ce8575efb90d4691680a02a4acb8978ce207
+817505cd7f01e027a965f79e6d698d6de13f56ce427a7ff627ed78237f99a702
+efb328304bfb288641d8685cbf43e96e85e2d6aedfe97e8bec0bd5f9622b98aa
+316450a090f406879c74985decdc96962a50b5de3a5848f92492037011d0f4e7
+dd0fdc25d578a0f86f80a8ae25b9975fbac71fbef5c04810ea0a19b89facc0cc
+aa73cd34b809854296cbdf532f9593332114e100ee289cd181bd3b3aa05c4fe7
+b3f323638087a2b97157b23505f9a653cee29017a39666e3680df121aaf641e5
+f971007acf53e8065a0fe536bb72e2fddb8bc51701f5d069faabd6c101e786c8
+ef8d299eee8fc6f7f97c1d9c09bc82f8204c9231083394ce840312344d592d98
+f652724515c99539d2c463fabd42c8360e0f0f5aa9739e3d2e484d3d39f2ce73
+f522a79dbc46266aa36518f0278432bb380904ebb6f30ea6edc835e33b81be18
+21b6e0d803f31e8be97a261b3b1426281b00b8b6baa6ddb76ced67d802415006
+1dbb011d04048481d815ea0db0dbccb20044fec53da88ec8cadc39bd57a2dcd0
+fd36197e8075729a1bd4517751dbae89bd44e2ca7cbed060e3e6e6b124b88ae3
+02f90a2388bce72b3ce9ef9e1bc8392246271d4cd5fa6067ce5eb426b2692836
+6f00ffbd94493239104a7d71adb54f3f1d74447676511fdcb82674cc89ed6b67
+7e6a24a5dd5d29f76eff3bcfbed6b5e81ff42a04b42b9c30763e501b1bd830e4
+4c9890fbe3b039eed303f064e63b7bf7291c58f2b45aa511b6e6fd2b169d6ce5
+3c9d4331ba7029bb94b7b5265da7c0cbab1afeeb6368ad410552a8b23c4838f5
+eb07bdafc3d60458649ac2c8fc787bbafb1b17128e82359c89a20075bfdb63e6
+d9be78d619689ca0f8c2e9c38a5e8b5eb42b4d07a9a3542cf835a423cb65bedb
+ba210e7b2073919ef7d76471d17b165d6aedef70651f46d22bb1b6d3a8b3898d
+e9370aaa67830689c4773e20e0264978655ee01d30e42f8a6ea3ce955c657970
+23129accb969cf84005341b24f92e23b4b3962834ed91ed40c6866f6aae07219
+f49f0d260d329c287d4bf91c504dbb44d13da65a96b522673b7912dde6fe5e76
+2ee0d89ed7fd96886e761c4f262c88db3e212c603e8981298e4520428c058480
+2f5fe0fb223f697f946330d97f6275c100e5d15f36867a7e9de9338d654cf267
+5bb52e5ee6c925faa522780519c15b655ae54ff19203a3465582e7b40e904716
+45152728ad912b05d25eff9cdf03b762507d6980908e22ba5399b2fc2cdddb10
+47d8d3b6d502ae49527a62ca8968a2c8b6c814ee0cf1a7618a8ea8d7e7c5f61a
+50515dba6ed6187394d8051121b685be8d029d97449c85c95b4d68da2a31278c
+dc7f39c44039a3a363d243910bd26c882ba1ef5be080d0f8b8eef954d9b7c186
+ea5e664aa4141f9ffea443d59124e25899d0d18dced06702499ab54e72894285
+fa1ce9a831ab914be5711a225ced342a9de51a7178a4e5c4491a3aff1a606aea
+4b91188a20a63747374aeb9014cb11cd6f52bbf906af5aa6b5ac2cfe27e75efb
+c554648f01f12eccccd3d94ad3b3271ec690e9a874284fd22587a58a4d78680f
+20bdaa27a59c0fe1eda28b86834234d291682efce38bcc93bf85ebb7b493066a
+40ecb74eb37dd1d018d3af16977ca7b539e5d9e68312d255b930cf1a859625f9
+bf81aec791a6fa63f578a4ecf972e1f61ef9258eff34f91ad002ea7a5c3b29ee
+2ab6d77ac6c14c71ad07895206b0a68ed4c21e197851ad6944d4f61f3d089c06
+d0589e1dc7494f3e4ebab1f56666b2290679a7656cf84ffa3f1a58c1fed9a62f
+3c1b0de1496a711f5b8e074712c3b44f49bffe341ed6e5926368388d0ff7882f
+ff24a63273155908d94e78ed8252603dd8eebe7d19e588b029e5136840a26664
+9918867370b5f8d3942127044ec81cc46d1e88fe88e47b8af625b6826f2c9ee3
+5f8a6afd319d66812d80a171a13316c2a4c73900686d4616126aa83d635c4f9d
+27fae14feef992bddddc1bd7c890e67cc544c31ac9d05541d7af116724081cb3
+8517dca6cc490a02295fbeed7e147b1e45033c70afbfd711a2d41248cba6b56c
+94e1b2c1a12409ca6e529f82e0f23bd91da21fbba01f22968a8cac2a231362e3
+61523dd5ebee42c524cfdf2eaf818650db8da4e382cc519214a7b4fb3f90e82b
+315db75c77b7ca98f2f21258a68417326cdb55cf6c2b31f197872a8d9d65e17c
+dd64e130ae2d6d2a16a717de89257bca08f5bca777b3021ce17d5b1a07aef098
+88fc1e420c9e08ae3ebd4ecd38d4417191658035a623a3dcc20257c0e1c4d1d7
+d7e0e54defa4d27fb4f815ddb2604103030e95f27871c7b0e9a4c4610948fa16
+012c4b30cc2e6c03a7ccad7f0643b9a1cc1eec9c7a2681ed802ddbe7d8eb18f0
+2770b022d4f2aba40045ffd23a888e04b800735f917f0abea17b934e4a7e4d06
+aaad3add44cca6826070ec12bc89e716dc416bcefe0725c086e7804ab8b7fd91
+6b1e1e3b289afbcb202d7074de5c61f2819f13735639aaeb50e7dc9aef89209a
+eca7316bb46f7f03ee99f56817d385102faaee63ca8eda61bc7b905ee48a9625
+69dc8122ebd8bfabcb8de515b7dae09542db46e0e6036611762442e8d3943d90
+a89024e8cf46bab577380287f30284717c433f304119762b276fe84fb0d0f075
+3d6591879886da5fc0e179a7fef454699e159e8a2d35dd1f678c8a5825febc07
+6b2d56fc48370317c9fd3cc71a4cf3772bc6a9f202a736e863ddb4cebe059958
+1ee4c17ea43b964cbc2add4d2e2ac013b43d11d1c1471565b90d1bb0da9b914c
+44810b030e137b37ef8942dbf6b041d9f7f90305afa411ad92527177f894c2ce
+835b8942eed694b6e7503851d5dc49bd40e05056841fdd21d6c29012161cff37
+8e213657bb2db9f3655cc1d18b8c6a4d31b2d37036b1be5f51d1277cee26c758
+685b9b2bb9b9f9e88f9146ba4ab794f535358fb49fc0d40ffa41ad402644120d
+0ea676f3d5588683683b79520de05a35c0f394f0f5ec6d13ac6b1df71e030dc6
+9d51c242324df025c96c5429f19c7190c77666731a46f8143c37252db87120e7
+8bf1c9f22b7a80dc4b999baa805d9f1eb5ac4e28a70d0fbd6c7dfdd7c1fbb560
+26073cc5cae5a1cdf5f8471585165ee0191d353ccdcd05f2117cdae9cce71d72
+9d0f61304f1d29eb2c5b6ec18290a9b27baceae80a405afb5664ba513ab26596
+bbaf87266ef51dbd35112eac5576b04b650031528738edfb937dec3f9b0caedf
+2447aeeffec2129814289b09842d3dd90b86300f1a035849b87fc2dc2e4292eb
+568ecf43a518512f9dd5287988e688c5f040a442150d447196bc5216df070c0a
+551928a0629cf3c7e430baab504b2315156cc367f55f090dc1f6ad0214353780
+f1cc7f4a9b262debe551a5de29cf1cdf08117de2df62f164fa91d166eaa72758
+9b862a3dfdbff31b404c11e411f0c642ca06962c18304403db2eb93cfadd392a
+c7a030971f2f897017317054572a4b5fe8fda9dfbad4c079b0c9c3e3336aae94
+b11baa3d0257a88a1919be409db0e1ea7f2f55fbafd3b4a8b5f55581aab8fb31
+a233d2081374327616cfc745b3c7039dd295a0b5068560fb68eb67c567c589f6
+08a45a312ca25accb1775badfd7b2eb0f4bc948b62362b3bcf40766b987622b7
+da7d9b68a7909104056fd5e0709a5928a0c314d407745c9b5f4c9a65483a7bd2
+3f550b7030671bba32d959442573fe4e6cbeb995b2fbc273b6894f70701fd2fc
+3f6ed6fe95cd521e83862350df167894510c93b44032187f171773b42f483e61
+2048daacd1f4be69c9ef5e6c1f63c72b759651047cd9ce71199dcbab1c36c1e7
+da6dd872b1a4c03d7145e4dca72064e2ffca5e43fd66a7c532feda1000904032
+843fcb1e24d99146dd27fc507cfba1a63b0843af0ebc0a336aafee4eb27d8796
+c0c1298a23874928f35c7cdd27145835742eab898c2d04e7ebd5c0fabf4806c4
+d44ba374cb1ff5052be5066d3a52e1f2ba4841fe35ae6c097cdd1c8b04883764
+efb0f51f646cccd0321ecab07647066dcff2bae0916983248b080730e45b8aaa
+df9fc1805e6c5fb1b536c598ad35c0f69f561dd949ed142ba7fde6778620e8d6
+ac443fdfdde254bf1475ef95bbd54d1ec0d9b531d1cf690cfea6ea9dd4d732e3
+e5bcdec23e92eeefd411298360effa920d3ceefe61858b9a356023766a181bf2
+aedd269b7d4c610fcc03cd8fda705186b3c842bc6d7299abf6f704b419bd2b2a
+a3f75c8b0ebdeab45eff8f1b9bbb9b187b03a0735b28813860d2916f7279fc8b
+e6df8e6f5155c3dba782aecff52b1be8710747c51e9a76d2574fcca699b9fdc5
+48043c2fb363a596f6b3b371657bdc2be900c9707a78fbfcbd1c0bf881468e2d
+f7adefd8bf30dee1b2347aecde496c012f0cbfe3816068ff31f3b3adf56b612a
+632703d469de017d29769ed5d55aa1f37651c11f0739ebb4c3af0a3c62c4523f
+05081ee09ae3076d361ca40f8b13d4a631b9f24a5366f4e678481abe3993ce9b
+ab8813c00b4b95be15086c69a55e65ae908fc071f0b3c59daa9719321edff148
+62fa6efdd42bd192d0138f6bbf5890f78a9c38d147ae00157f4307dc3095432f
+5bbe889da9a5bee8669c6651e758ed5ad38856433fb9702d70c12aa29cea4424
+8e50e17ce514b8ef3131a972b8db0b0be78c93831806e98916572e7959b8317c
+54149e5372718963eb74983316ba7a0418a921433d759e73cdacb1bb6ac69df6
+7961040ac80e5459f3120ca3c8eaea02497b64ef871030f1501ad79dc405019a
+00e355ae3b7c48ed22873196a98637ba2b25bf8b946ce509da8bd25456373c29
+dbf08751baf2bbb23ed57b32d3abe306c29eba95e3a404c3a15b172a8313932a
+c5ae1588a0227de80156713ea8239110c31e0452a2e2cf376cfcf90a41f68bdf
+c08018786b166a5e9326b1beff6a4ed7fa44f523911d1819cb6aa1ea58fc52cb
+51a74b886466964dba973726352554470d5c35353b60b68830bfcc8f14cbdf00
+f8974ab1914694025375901d71324ba933b2ba61204c4ba5f6429077f429f17e
+8f1f95c2308b2900bca909945f96f33d73a7a2186e28d4132db91aa6d8751e47
+fc1665361bbeaebdcf6bae29423c6fadb892bab7cc872ee4cf3490d2956907ff
+baeb199c2ccde45a2649aa2e4d8dc65142df9b332691d9b154dc072ea33d1710
+e64eea0969e54a54fcc812d6c8ce00c790d71ddd67c6b8d4ca7baa6575cfcee5
+8af7c205f455c24ede135b653875780f54c98e3159ed284139f4fb00f64ec8c1
+1191c0b484b9fd15fa6f0fb5b6999de6358741a0a48abeee116a44cbc015c0b4
+5584b76b7e73d6d3148bcd17cdfecbefd83aa402162367bc40e7835fb64a4b04
+dc4827158e4b8f193bc470c9bc0c46047b4cc4d797ea9ed7f35868331517fc43
+15b9a080d5171104dce9c6b8d9105b0031f4b1f9ce7d8e2b7d08c5d633290fa8
+8356b073770339203622f09407cd263412ecec6c7f58f8d8f5be935ccdebc64d
+5f0a0287fdcabae9ffd00c24e7cfaeddf9fae5f60d4864e8268885f71a8a941e
+facc6eca987d76f1ca3ff9795045b4b9057a7ecee50a04d09b859207ee869750
+282bc24be3dd0d84e33963c4c1a484de057541399cfe79a5bded1bb90833d8ef
+4bb6f36b7b9f236ff00bcf441ea1e1ea04bec9f3402e7993be2d024a5d06c653
+6c2c090d51187ad3eafd64bb0389da839cde99222868ce4ad6e03d6fad5411b0
+9826c410db9179d22db159ff72b0c9246c690b527bbaae566d145284f3e7bf24
+20c2a7bc2ee6607ced1e89c558f3a4bf132f23ddf53b240284de7c4839ef3b53
+6532a60c9df1cbd3470d44c7c0d26136cf4955951fdc3f0b85ff4d4057f47a6b
+8648c3640059f4543316ba9007c0e784f79c79156b04f60b030cb98b4fd47577
+dccc5c223e0f1211744ea18b7c0e676bfc5ca9592a7d9e7c13b93bbe2bc62251
+71d0a4c44950754ddc2ccd486d2b702f4188987595bf8988130b0c059b279418
+ee3d0e21a1a8e2b1852ba7e01e9cf19746131e126bf5eedb08e6c635d700b0b8
+caaf2de2c01f82ed911278abf80a8a47aebe6330e0d1bc308f00a9fb8944052b
+4cf11ec1f73f3dbac7a9124c6fd2009944732d672c3d4cd2d387b4235fec275b
+907d25342890dc44dd670020814e4155463f6bc4804f68efab0501388a3ef816
+e2ed8b83840782e22be4a0b562a6b0361b8129872c5ca1c0055940378bae85f8
+2a39e182e758f970c22aaead53ee5f934dfe39abfd55bdd3275b65b0b71eca9e
+467dd786fba4f8c7084db6069703dd97b05f80013cd99dea6698ad3ab5f09f11
+5a00e47fbfcd13caaa7e82191d1881b3992b69c6cfa15307d6ae8370252145b0
+97709d56b69ecb253757ae7b1a11cd9a8c2210af5c91fe93bc2147681b19c75e
+a360636446935d85470b0c28d927b07afcf63809a53c0a8baee76632982e9fa3
+1cd9b8f117a7b41689c83a23043550a3f165bfe81f0e0e224a6568e60e28c466
+28b633d66fc7370f65fd2ac5694e7c9a616bb88298cc7a7bdf9a39f1ffc1aefb
+16de11a575a58fb8f65c47aaab60b465d3e8eef5240a9773874bb658d7356723
+f449270f0eaa74cda1a6d5f732bffebf4415e77d3a566d48e47d7629fa6b8a21
+6e8032e7ceedbd8ead4e922685d6be6448a14fc839ae4a74f6a5a59c01375a4f
+d57555f569d036d0337351c7305c49217103adfe4b6ddc8376f134eeb11ca705
+92d1e2e80c423ae05a0822375e0d3f059cbe839b07a290a6b421c7d513f1961b
+27c632269a8c35eed49010c561b580412b9363aa0e9c76e210b6a86a55016da9
+e04e8f7a5c525ad3ef4edd7550b6e2f6669508051973e36e435579c178d2d39f
+3e27bde7de754bbc68da87698aada323575cca997a1cc75f706634834d1dac10
+7103b40c71a1e59358ede2d1ee87082235e808481f456968bd649a268e566680
+3a8471be291dea8358c52ad22508da5762fb1e1f6128b34e32bb68039237a400
+c1afe9e07c69d8b486281a9ec09067d09b0c8bc16c0960771066aca2f4c18907
+4e2ab98be4da38b2ef92f3a922a1fa3afa66ccb7dc5c59cd06f7b6dfe00eb86b
+8abc28551decbfcab8ae483839cbedd2cfd6e7c8f6348444ee0e67e943e88444
+f9f871e9291a5eae7d63522caf0396c4cb477d2df5377c39c46b14125e100ad2
+4f15e1aa3dc33b356f6a6d9e1404efa7f05b6208948b01acbfb8a1079181872b
+f06cdcd561a855b1884008e605d67e653ef3357b9c6d5af90228c6b4676644fc
+cabbab905c7e9164a901ba7fd1c4742d454784887907b7e1a9b2d1c844ca2f03
+3991fd9fdd19fb4af35738bb380bb940b03e9fddaac1f64afc6e3c99ee1d7dc5
+5ce48afb651c9595e41c894a8d75af382c709720a2e94f03fa45ec2cfbf7d3e7
+6fc0dba65ae93bad352e1b14aa0f3c39cb8766c106a1fc656401e4ff5c4584ab
+974e93b2bc7f16209d80bb566ed5b67acbc84635bfea38f1e87c39027a0d1ccb
+1a93049e936116e1847fb3123585096edccbfd0aa63e623b8503a0d4760aae40
+0a88a7e87400875eac6f14fb7a7a8682560de6cbb43fcdf303758e182c4d12d3
+87ed4533d7c9f2b8e13489130d6ae5fe066f4ad856b167725e4ca2e7b842dd7f
+599de83285aa617d5ead39c7ac6ab77e42f5e1153250803103f7769cf35430f4
+fc5b91642a1b798ef39d359f7196d3f7b0f864e016da2e43ea1a2b8e8334e8ba
+c0af735eeec83a2e344ac6a62461cc1dbfacfafe1cc1db2ab1655ba0a7d64eb2
+4f165b64608fb31dddf15f708f052adcb6ce974d807e6ad8f7b94979ac09fd65
+163a3699cb80a8a32d49efcafad68856da3be30b616873ee511e361963983106
+f700c080555e719697b0de489286e397f5f81af94632216e357f1020dacd270e
+c7d9bb4235aa738a8dca7f23680c377a643b0b59e6a7fb83507c8cfbd1df3758
+3a565760689d25526f064e961d0c3d2071054eed80850bb10ee9e73f27654d20
+32bc2a750714ece1a4ede7b7c4be79f4e0c3d7a6b433f44774d6cc6a93f6dd1a
+29b855b805788ff4436794d2aa348da5ab69ce698e7d5b1956d1a7888607d7bb
+a6b59226b144dc8589e4787c8ab0342c8a085e12fe9ddad5fe1c185099e207ac
+f4308371fa4afbdf90eb5d08a0076d70615a4d17264baa2e712063032c188877
+fcec4971f2c7f4a9012fbf3fe15b76adf4f35e5c0f337862584b12d04bfbf85e
+8b3e8eeb64bc0fea31faa7a697b34be4a8c3fcae69ebd8c25fba58f3d101c9cc
+7d6cb4a6835a5262a28f9304dae690bfaf6eaeef09d44a0418b78e2a4d381047
+35a4523e8341a74509eef699744865f10168cbe739d4eed29e6542cb0dbad470
+d9dd0ddfb8164959ec59a632863920e18ab5c001845ece330868dff7b7ed51a8
+c50a11861df524d4dd80c7b370886921b9f6b28015a83412cdc4fab786791bd6
+37081dff54753214e35477c847ea13286c28fdefe690fef446f4cad67ac3ba20
+958743af664767e8d8ef8a389ec1c138128aaff1b7557b52eb59dee75db67cbb
+60d9b08b3a443b10bc84417aac4aa89973c8d8932af5b91b82b7569cac09d69c
+0cae12b7411269a1c550c94dce68d60ef418f21ab29ab552eca48a4ee94a4b27
+5d174ae06ce469bd5110d23678269b244cd596650ab74f024460c84b564cfc31
+f10b7d0a15166acbc8e9a70668aecd4fda96ec72af4d438f3e9c42e068bb2504
+66e725c097f17e533d6ddb0223a8c018e6445a468e5afed7d7d93ca5e8807f6b
+cabc59e9d7eb20de335ee9328a6726bab86e2bbcb847e18e2be079bda3c1a380
+2d338cc33a35b0bb17b37188cbcd54c760cfb17d5da0c66197da9123942a8789
+6bf0e622dc9e93457e1b73b7f5b96c894eb76af290e1f9dc439e36e972b3f865
+187781130941b3596aa9dc699a6a586c1acfba7e1d7b340312856ba9caf63662
+02f6f303f92f15726dd083b1555263d2e3bd50baaf7c0bcca4ca774f5e35cd0c
+85caca2366e06fb942b482bbfb1926fe47b1d95a83a5143ba801efbc7d62c003
+2f915672ee8ae1b235096c8f62635bc5c42a03d6be518e9c4a68ee0d8e48d1e2
+2de2828cfc76efb4a12ba5c3a3a08175545256dfdf53edf0c91bdff981f5b9b3
+fb8b9ffabb007646755e76a278cb1752b8081ebe91dc77177aeca8ca90ce2077
+f90d35e6f7aa90b1505e472ae5f09bb05f2a37b5e2cea2e1a3e5fe0e367a07c0
+f31f855d7f1c7b4272ea37398bec83f7b79fbf86129799e2ca9e07b2a01213aa
+2d73499beb7ff978dafb22518227532efae2a60cae34347ad352a00c0adfea43
+e3f95208c6f5c21e1fb3c0b07a1d143d5a8b55cac717fda2b72ff0c3fdbabf56
+34cf4c2051dbc59ec4c00b6e1cfe454cdbd9fda033cda90a2b66fad3e8271e17
+fafa42139467358062f2a1c244c48d52a9fbf5c1f360bfa45d0b03ec7ec4805f
+3e7f67e258936a690090abd309b2b93424efeb8d5d7deabee5eaf0db5d56c33b
+3a728c08624ea5d0c85f77beac600c5fb8c184ac63b3fcc5344f22dd878abe96
+4320ad7ad8803c28b4753b071213851247f35a2f90a59072312059373cad6276
+2a17bb449602f8424ac583843810a1c6db922a87bbddf800bea24f6ebb071d2f
+3a82053bc5ddf3eb11c3c32f551a81deb3000e57282552fc0b8ee17ad909bb2d
+b851793f65123af449d07279de0999aa5208cc293593eb9ef607c7e20e873e64
+6097ff18507152a8d111e24e419c148a5a1fbd339428f184bb3c5ce3a36af6a6
+9b211cee9150c40506c1e990d19ba8b4f0b5c01e7c19a05904cb63d127118908
+5ab937a91b2dc3b59ac6bd1971c084e2c156c10661b2c7a3d170bde0fa3e682b
+abbbf187ff39304d6e5ae80eabce1c54a0bfe09f22d97133e94afa9981e837cc
+0a80b34b29899474b65ebc6df73e5abc2f2300226c4e96e7c32b3f80a26fadc1
+e503fed433b7cfaf681e6cb33057a915ec53112e76cd9602ff64a75543eeb82d
+e6f45aecf9620a5a3bae1f4459b9ae6162e472e93eb2e0daceec8f0608721072
+e456972d5fa6bc4a45afe5a8a5694e538e659107ff4cd6a8ee3d38544e397ffc
+26a98e8771a5e07c99402b1722b09cc959f9d25126a82c7820de21a9855eda2a
+1b7e4d0145ec7017caa0349bd13177070509276241a0aa7dfae14f1e2d1805ee
+58714e9f2a647fcede216c7b3297910df45f91d8e0c9ec279f16a744930f3b77
+0b80d299b44b478377ef237341ecc5d9a59b333cfbbc229be944d368559988c4
+940209b2a4fe6d55713acce94181339a1d70589e96a54a952163b072e116f5ae
+4e672556501ac126d0dc91531894b69d5c8c6bc531db69a961cd517d4bc07375
+5683fa7cd9a77fc56c6ae62cdf6bb0857e8906f835035e78d3f7ad14375b6f21
+fd0436e6c914c3b8d6e543df1ee4b992c024e9d9fd316dfba3ba9809a089cc99
+8f34466ac94d689350a5075998d7240efcda132f3b8f2ccc0fc78f5d77a313e4
+09d5ae9f2839b9ba67cb79e1a0e7ff8f77e766aae4e1931a51890b3f0e3b39bd
+5412ab04baeeae007ef88984afdd56b7f81f403fc6eae6993367ac9071b396b7
+f690f613193c638b9c6b10350d0170696db08055fc583cb04c3c66cb71d354a6
+6c02a9c33e312dd36d23c37836795088375e42241d176d2168dcea614a14850f
+d283c962ddd274d58fcbba7668fc6987cb0d204cc4db70ad44c53ab5da5e15fb
+55c5c53b85d0468b1798bffc105d55973d081db5cf801d98525597cfab8be326
+f6723c6c5060c6001ff9da8d01855933f68d88ea8c403e6dc8aa446e5eabd57a
+4d8c080ab4c164e3a97c1c6e87b3e279b97e99127fbbf1126099928ea8c0af7f
+3e592175164f46dd15ce60c6087d1893dd388b7a8eb65d0c38c56fa3fbbf88cf
+314e3ca2b710ff61f610ba945f7405c373c2b4147713206be618b238ba3e367b
+9d158dbbbe4dfc0aaede3f9457893910f3a83bf6baa3688fb1176c93132e24a9
+bdbecae4b53fd2cd971091dc99359d11af0aa3e94264538203459faf0cc36410
+def19e919a2714e2ce1895ceeb1f26ce613e5f1bee352ef3a63f94fc343fe7c3
+4f831ab892db8e438144821d5a701cb71d36526ac44a9354f3440f74e9e1fda7
+723f0120f75059513bd52cb2781a08b52142e53d0a4a23d7d003cae26b838552
+a1376832da4a1b8cc20cfa9f72fe3a9380a282bbb73a9ac07972677045589c0f
+e08cd002c80e0064a732a3dbb6dc83393d470f826fecdba4f1d2e4be76a5a433
+9a5e25b1f2b734d4f1469b84d977e3ccbe5951c768c9274d0423fe5d740ac8a4
+e3affe4fa68dd8cab058ebb21962d72dcfeaaa47e541a2ff3e6ecdacf0484ec3
+7151b6b1fa6109a0113e3bef3145b53d05c62e0391e266393ba1a4b39a806a12
+f2fe6562dff2fc97eff531ded8dc6e0ed359b2bf16d00031e9e0753bb32c156e
+ea81b7db813752506558d39f967450fa05390368950c456f106211dfc0621e52
+66c14e1ebc6a9f3de3165500218fd683fa0dd2b4fcb937f14f7d3fb85faf556e
+3f53ad74a4bcd96872b46716e0bd737dbf5d3115ce382ffb8243c499503283c4
+274a0e51c9d41b302fd57c648a4e1769dcab4668ee624f3b9c84f296ba9ca925
+056358d1e7f5a56ce13333e53b5676d5e9ccfa4cf68f18f28aee890b1bfad737
+03b64dcd7c5f34387f4522f421045adb7cb8b552d5145b83e24667d4f80cf636
+45f80b863f0293b1d494ed65532c3963b69fe1a858850799155ed2450d836d30
+d28dbfc50c7a58fd713346961d7f0e5513394b29c06c52b786b48a2f13b95b9d
+56d6aca8250f3b716f055275af003304d36713f14228b5ec40de7c3ba9ae32cd
+eb91f8110bafcf7c92ea383bd9ca4d4fd0f6ccb0902b07841a4cbee2cbe21c4a
+8124a11ef70c32d89e4b86d0e84294412f305ec186afd600ef7d72634e931f60
+a75a22b03abbb87b4a163044b96d43cb1a8eac9484df8955edd18bb298612f6d
+a9ab0fe3153be8def9616e883e5ca081c79f607d7439ce5a28ceb6b17f51f0f0
+9affc2a8a766dcbc19e1dafdb34bab555934bc8b3bf2255e4aab837de34079d7
+3b310d8d82d2abe249a28e1fc6f65e529c301933849883e86fd7b7b44271da63
+15913982ad40ffb9c796f3b519f84d15ba2fb42fdd632e042b7d9f94b28f5b81
+5139956c9d4e3521d59777d3c34a07e4ab2d92ce1e64c02c612023bd84153d02
+ac1b86b34937b82522277d5c11a7a3daa95e63e4d122e626d9167a5a17b4d041
+8d5aaf88b8a59de837362e48c9fd7123c3c30d906cc081ff9a0a1ce3b168b9e1
+ce50fb45fcaa3061d7dd0fce1c7051ea3c032fa52fa63442629461bbf742df11
+e6a548e7f73cd0441032ffb059fb81b468380c5eafb679d5ae2f384fca2236da
+5d5d878a6e29e282d045a419488daa5d3500bab9ddbb6123112949548b37cd85
+46e466baf1c18d05475029cf8b06206338df4d287541137ee8d509ae9e3716df
+32627c0e28eb8edcc62bcc37536bf8a36b8a545d539f2754fc39eb8da84fef99
+02a3733603379181d07b780b1f0dab6b69ab3268adeba5cac438510e80b96eb0
+79ccdf7e76b2f88a37beb1b83e4b3529ab0b6b1a35d5b7add114578ce3783582
+fc23f6c5df43330e9397c71e984817b908dddb6a02e6da93c87f623b3dcacbb7
+5c7b032d24a5e516bfe910b1569ef48975b8b7448f2469a41b4ab69f419f9f39
+2d3ca7b9bc768151274aaf55b06c3e399cb19b6e0f15ec8868c6e668e524aed3
+ec176ffb6a8accdc068a22eff5d79498a00a99562e490c7cff7e59e7d3b3b1f5
+8e48e1ef5e08391391c7e9ccebcce6376480909156717c07de203306e8c7c7db
+0b8737da43a6a8e9218133af777bcc505e5f5375a3fe8411cb5a317619e9d259
+07cbec0088f2610250754acf52e22dfd59a160aace94a7392f2f3a578980b1bd
+6d1dedb930b6948583f7d978fbde76276822ffa4014cfae2ac22913621b0f778
+d50edfe8517092759af5bdd038316d1b08f2b31550d1fe1fab8b62e79419010d
+6a59355a2e73cb08443700ceab7ee437401683f20cc5e9a21f056866f2b67dfb
+4e1e8df8ddcf2c5823e7fef6f91ebd1fa571ce77ba080c0f604a263379ec4e2f
+f5694975fa6adb5a646ad327db787c204f9b5ff9f9228a3337c111d32cbd6f8b
+1da885425af61709e40d4352a1be6d8e2775372a3f7717e9aa9b951f79d2bfb8
+399d1f915e26836d454d286655657b72378551cf133bfcaaaa2a45e98f840bca
+522311100c7c92f46661aa1a75fb44cb28a1a7f0cbeebc2008c5fe882108e2ee
+1e0c683320c084d1e5ff7c0657de7942b0daad28494b24649b271d82c10b7373
+f02be60741104e31bc70a2fc2ceb7db132f272caaf093993de5cc3d139d559b8
+ddfd287fb1705ca461eb03a4e039428ad0ea3baf3b29f824fc8c8edfffed4803
+e5d0af7bef8aac625a184911bb28809661eb2631a078fb9a244f043adb379476
+d0dcdabb21be4f1cee1b21d775a53dc6a1bfc4a8
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+cleartomark
diff --git a/e2e-tests/cypress/fonts/Type1/c0419bt_.afm b/e2e-tests/cypress/fonts/Type1/c0419bt_.afm
new file mode 100644
index 00000000..daca3412
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0419bt_.afm
@@ -0,0 +1,264 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Fixed Pitch 810
+Comment bitsFontID 0419
+Comment bitsManufacturingDate Mon Nov 5 16:16:55 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530419
+FontName Courier10PitchBT-Roman
+FullName Courier 10 Pitch
+FamilyName Courier 10 Pitch
+Weight Normal
+ItalicAngle 0.00
+IsFixedPitch true
+FontBBox -44 -299 664 858
+UnderlinePosition -97
+UnderlineThickness 82
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 579
+XHeight 452
+Ascender 639
+Descender -195
+StartCharMetrics 228
+C 32 ; WX 602 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 602 ; N exclam ; B 210 -11 392 613 ;
+C 34 ; WX 602 ; N quotedbl ; B 154 337 448 579 ;
+C 35 ; WX 602 ; N numbersign ; B 105 -58 497 674 ;
+C 36 ; WX 602 ; N dollar ; B 98 -122 498 666 ;
+C 37 ; WX 602 ; N percent ; B 101 -2 501 618 ;
+C 38 ; WX 602 ; N ampersand ; B 88 -11 501 547 ;
+C 39 ; WX 602 ; N quoteright ; B 215 317 396 579 ;
+C 40 ; WX 602 ; N parenleft ; B 201 -196 401 597 ;
+C 41 ; WX 602 ; N parenright ; B 201 -196 400 597 ;
+C 42 ; WX 602 ; N asterisk ; B 103 201 499 579 ;
+C 43 ; WX 602 ; N plus ; B 48 49 553 566 ;
+C 44 ; WX 602 ; N comma ; B 178 -157 399 163 ;
+C 45 ; WX 602 ; N hyphen ; B 76 178 526 273 ;
+C 46 ; WX 602 ; N period ; B 204 -7 397 167 ;
+C 47 ; WX 602 ; N slash ; B 114 -55 503 699 ;
+C 48 ; WX 602 ; N zero ; B 101 -17 501 632 ;
+C 49 ; WX 602 ; N one ; B 126 0 499 630 ;
+C 50 ; WX 602 ; N two ; B 80 0 487 633 ;
+C 51 ; WX 602 ; N three ; B 90 -16 497 633 ;
+C 52 ; WX 602 ; N four ; B 88 0 493 631 ;
+C 53 ; WX 602 ; N five ; B 84 -16 503 616 ;
+C 54 ; WX 602 ; N six ; B 100 -17 499 633 ;
+C 55 ; WX 602 ; N seven ; B 101 -10 491 616 ;
+C 56 ; WX 602 ; N eight ; B 102 -17 500 633 ;
+C 57 ; WX 602 ; N nine ; B 98 -17 497 633 ;
+C 58 ; WX 602 ; N colon ; B 204 -7 397 438 ;
+C 59 ; WX 602 ; N semicolon ; B 178 -157 395 438 ;
+C 60 ; WX 602 ; N less ; B 34 68 543 547 ;
+C 61 ; WX 602 ; N equal ; B 51 189 551 426 ;
+C 62 ; WX 602 ; N greater ; B 59 68 569 547 ;
+C 63 ; WX 602 ; N question ; B 122 -8 488 613 ;
+C 64 ; WX 602 ; N at ; B 109 -53 476 668 ;
+C 65 ; WX 602 ; N A ; B 0 0 607 579 ;
+C 66 ; WX 602 ; N B ; B 42 0 558 579 ;
+C 67 ; WX 602 ; N C ; B 39 -14 541 595 ;
+C 68 ; WX 602 ; N D ; B 49 -2 558 579 ;
+C 69 ; WX 602 ; N E ; B 33 0 530 579 ;
+C 70 ; WX 602 ; N F ; B 45 0 547 579 ;
+C 71 ; WX 602 ; N G ; B 34 -16 574 596 ;
+C 72 ; WX 602 ; N H ; B 49 0 553 579 ;
+C 73 ; WX 602 ; N I ; B 94 0 508 579 ;
+C 74 ; WX 602 ; N J ; B 54 -14 574 579 ;
+C 75 ; WX 602 ; N K ; B 35 0 573 579 ;
+C 76 ; WX 602 ; N L ; B 28 0 555 579 ;
+C 77 ; WX 602 ; N M ; B 7 0 595 579 ;
+C 78 ; WX 602 ; N N ; B 16 -8 569 579 ;
+C 79 ; WX 602 ; N O ; B 36 -16 566 595 ;
+C 80 ; WX 602 ; N P ; B 43 0 547 579 ;
+C 81 ; WX 602 ; N Q ; B 36 -128 566 595 ;
+C 82 ; WX 602 ; N R ; B 29 0 588 579 ;
+C 83 ; WX 602 ; N S ; B 70 -23 527 596 ;
+C 84 ; WX 602 ; N T ; B 45 0 557 579 ;
+C 85 ; WX 602 ; N U ; B 31 -16 570 579 ;
+C 86 ; WX 602 ; N V ; B 8 -7 594 579 ;
+C 87 ; WX 602 ; N W ; B 2 0 597 579 ;
+C 88 ; WX 602 ; N X ; B 42 0 560 579 ;
+C 89 ; WX 602 ; N Y ; B 38 0 562 579 ;
+C 90 ; WX 602 ; N Z ; B 89 0 492 579 ;
+C 91 ; WX 602 ; N bracketleft ; B 202 -181 400 579 ;
+C 92 ; WX 602 ; N backslash ; B 114 -55 503 698 ;
+C 93 ; WX 602 ; N bracketright ; B 202 -181 400 579 ;
+C 94 ; WX 602 ; N asciicircum ; B 126 448 476 676 ;
+C 95 ; WX 602 ; N underscore ; B -22 -299 626 -217 ;
+C 96 ; WX 602 ; N quoteleft ; B 210 334 391 596 ;
+C 97 ; WX 602 ; N a ; B 66 -12 568 464 ;
+C 98 ; WX 602 ; N b ; B 33 -10 566 639 ;
+C 99 ; WX 602 ; N c ; B 48 -12 523 463 ;
+C 100 ; WX 602 ; N d ; B 44 -12 577 639 ;
+C 101 ; WX 602 ; N e ; B 51 -12 546 465 ;
+C 102 ; WX 602 ; N f ; B 93 0 530 640 ;
+C 103 ; WX 602 ; N g ; B 66 -196 567 452 ;
+C 104 ; WX 602 ; N h ; B 49 0 561 639 ;
+C 105 ; WX 602 ; N i ; B 82 0 540 672 ;
+C 106 ; WX 602 ; N j ; B 115 -193 452 672 ;
+C 107 ; WX 602 ; N k ; B 51 0 575 639 ;
+C 108 ; WX 602 ; N l ; B 91 0 531 639 ;
+C 109 ; WX 602 ; N m ; B 0 0 614 464 ;
+C 110 ; WX 602 ; N n ; B 51 0 563 464 ;
+C 111 ; WX 602 ; N o ; B 45 -12 557 463 ;
+C 112 ; WX 602 ; N p ; B 33 -195 566 452 ;
+C 113 ; WX 602 ; N q ; B 39 -195 572 452 ;
+C 114 ; WX 602 ; N r ; B 53 0 556 464 ;
+C 115 ; WX 602 ; N s ; B 87 -12 512 464 ;
+C 116 ; WX 602 ; N t ; B 43 -10 550 591 ;
+C 117 ; WX 602 ; N u ; B 35 -10 558 452 ;
+C 118 ; WX 602 ; N v ; B 21 -18 580 452 ;
+C 119 ; WX 602 ; N w ; B -10 -18 603 452 ;
+C 120 ; WX 602 ; N x ; B 40 0 568 452 ;
+C 121 ; WX 602 ; N y ; B 28 -195 570 452 ;
+C 122 ; WX 602 ; N z ; B 98 0 508 452 ;
+C 123 ; WX 602 ; N braceleft ; B 143 -183 468 581 ;
+C 124 ; WX 602 ; N bar ; B 259 -261 342 789 ;
+C 125 ; WX 602 ; N braceright ; B 142 -183 467 581 ;
+C 126 ; WX 602 ; N asciitilde ; B 64 249 538 366 ;
+C 161 ; WX 602 ; N exclamdown ; B 210 -29 392 595 ;
+C 162 ; WX 602 ; N cent ; B 93 -33 493 646 ;
+C 163 ; WX 602 ; N sterling ; B 127 -4 501 625 ;
+C 164 ; WX 602 ; N fraction ; B 114 -55 503 699 ;
+C 165 ; WX 602 ; N yen ; B 38 0 562 579 ;
+C 166 ; WX 602 ; N florin ; B 17 -139 541 632 ;
+C 167 ; WX 602 ; N section ; B 75 -105 526 579 ;
+C 168 ; WX 602 ; N currency ; B 60 188 548 675 ;
+C 169 ; WX 602 ; N quotesingle ; B 243 330 358 579 ;
+C 170 ; WX 602 ; N quotedblleft ; B 107 341 475 596 ;
+C 171 ; WX 602 ; N guillemotleft ; B 149 44 453 407 ;
+C 172 ; WX 602 ; N guilsinglleft ; B 232 44 370 407 ;
+C 173 ; WX 602 ; N guilsinglright ; B 231 44 370 407 ;
+C 174 ; WX 602 ; N fi ; B -1 0 602 672 ;
+C 175 ; WX 602 ; N fl ; B -1 0 602 642 ;
+C 177 ; WX 602 ; N endash ; B -22 184 626 266 ;
+C 178 ; WX 602 ; N dagger ; B 121 -15 481 595 ;
+C 179 ; WX 602 ; N daggerdbl ; B 121 -15 481 595 ;
+C 180 ; WX 602 ; N periodcentered ; B 207 214 395 402 ;
+C 182 ; WX 602 ; N paragraph ; B 57 -77 551 616 ;
+C 183 ; WX 602 ; N bullet ; B 222 256 379 413 ;
+C 184 ; WX 602 ; N quotesinglbase ; B 199 -147 380 116 ;
+C 185 ; WX 602 ; N quotedblbase ; B 114 -139 481 116 ;
+C 186 ; WX 602 ; N quotedblright ; B 122 325 489 579 ;
+C 187 ; WX 602 ; N guillemotright ; B 148 44 453 407 ;
+C 188 ; WX 602 ; N ellipsis ; B 31 -15 570 122 ;
+C 189 ; WX 602 ; N perthousand ; B -44 -2 664 618 ;
+C 191 ; WX 602 ; N questiondown ; B 118 -25 485 595 ;
+C 193 ; WX 602 ; N grave ; B 124 504 407 670 ;
+C 194 ; WX 602 ; N acute ; B 195 504 478 670 ;
+C 195 ; WX 602 ; N circumflex ; B 132 509 470 657 ;
+C 196 ; WX 602 ; N tilde ; B 123 537 478 636 ;
+C 197 ; WX 602 ; N macron ; B 130 551 472 611 ;
+C 198 ; WX 602 ; N breve ; B 113 528 488 661 ;
+C 199 ; WX 602 ; N dotaccent ; B 239 527 363 652 ;
+C 200 ; WX 602 ; N dieresis ; B 117 522 484 641 ;
+C 202 ; WX 602 ; N ring ; B 177 499 425 747 ;
+C 203 ; WX 602 ; N cedilla ; B 178 -233 431 -9 ;
+C 205 ; WX 602 ; N hungarumlaut ; B 168 504 535 661 ;
+C 206 ; WX 602 ; N ogonek ; B 248 -224 435 15 ;
+C 207 ; WX 602 ; N caron ; B 132 511 469 659 ;
+C 208 ; WX 602 ; N emdash ; B -22 184 626 266 ;
+C 225 ; WX 602 ; N AE ; B 0 0 578 579 ;
+C 227 ; WX 602 ; N ordfeminine ; B 117 110 518 596 ;
+C 232 ; WX 602 ; N Lslash ; B 28 0 555 579 ;
+C 233 ; WX 602 ; N Oslash ; B 36 -55 566 631 ;
+C 234 ; WX 602 ; N OE ; B 34 0 578 579 ;
+C 235 ; WX 602 ; N ordmasculine ; B 95 110 508 596 ;
+C 241 ; WX 602 ; N ae ; B 18 -13 579 465 ;
+C 245 ; WX 602 ; N dotlessi ; B 82 0 540 452 ;
+C 248 ; WX 602 ; N lslash ; B 91 0 531 639 ;
+C 249 ; WX 602 ; N oslash ; B 47 -22 557 475 ;
+C 250 ; WX 602 ; N oe ; B 35 -13 579 466 ;
+C 251 ; WX 602 ; N germandbls ; B 21 -11 550 640 ;
+C -1 ; WX 602 ; N Aacute ; B 0 0 607 802 ;
+C -1 ; WX 602 ; N Acircumflex ; B 0 0 607 789 ;
+C -1 ; WX 602 ; N Adieresis ; B 0 0 607 773 ;
+C -1 ; WX 602 ; N Agrave ; B 0 0 607 802 ;
+C -1 ; WX 602 ; N Aring ; B 0 0 607 858 ;
+C -1 ; WX 602 ; N Atilde ; B 0 0 607 768 ;
+C -1 ; WX 602 ; N Ccedilla ; B 39 -233 541 595 ;
+C -1 ; WX 602 ; N Eacute ; B 33 0 530 802 ;
+C -1 ; WX 602 ; N Ecircumflex ; B 33 0 530 789 ;
+C -1 ; WX 602 ; N Edieresis ; B 33 0 530 773 ;
+C -1 ; WX 602 ; N Egrave ; B 33 0 530 802 ;
+C -1 ; WX 602 ; N Iacute ; B 94 0 508 802 ;
+C -1 ; WX 602 ; N Icircumflex ; B 94 0 508 789 ;
+C -1 ; WX 602 ; N Idieresis ; B 94 0 508 773 ;
+C -1 ; WX 602 ; N Igrave ; B 94 0 508 802 ;
+C -1 ; WX 602 ; N Ntilde ; B 16 -8 569 768 ;
+C -1 ; WX 602 ; N Oacute ; B 36 -16 566 802 ;
+C -1 ; WX 602 ; N Ocircumflex ; B 36 -16 566 789 ;
+C -1 ; WX 602 ; N Odieresis ; B 36 -16 566 773 ;
+C -1 ; WX 602 ; N Ograve ; B 36 -16 566 802 ;
+C -1 ; WX 602 ; N Otilde ; B 36 -16 566 768 ;
+C -1 ; WX 602 ; N Scaron ; B 70 -23 527 791 ;
+C -1 ; WX 602 ; N Uacute ; B 31 -16 570 802 ;
+C -1 ; WX 602 ; N Ucircumflex ; B 31 -16 570 789 ;
+C -1 ; WX 602 ; N Udieresis ; B 31 -16 570 773 ;
+C -1 ; WX 602 ; N Ugrave ; B 31 -16 570 802 ;
+C -1 ; WX 602 ; N Ydieresis ; B 38 0 562 773 ;
+C -1 ; WX 602 ; N Zcaron ; B 89 0 492 791 ;
+C -1 ; WX 602 ; N aacute ; B 66 -12 568 670 ;
+C -1 ; WX 602 ; N acircumflex ; B 66 -12 568 657 ;
+C -1 ; WX 602 ; N adieresis ; B 66 -12 568 641 ;
+C -1 ; WX 602 ; N agrave ; B 66 -12 568 670 ;
+C -1 ; WX 602 ; N aring ; B 66 -12 568 743 ;
+C -1 ; WX 602 ; N atilde ; B 66 -12 568 636 ;
+C -1 ; WX 602 ; N ccedilla ; B 48 -233 523 463 ;
+C -1 ; WX 602 ; N eacute ; B 51 -12 546 670 ;
+C -1 ; WX 602 ; N ecircumflex ; B 51 -12 546 657 ;
+C -1 ; WX 602 ; N edieresis ; B 51 -12 546 641 ;
+C -1 ; WX 602 ; N egrave ; B 51 -12 546 670 ;
+C -1 ; WX 602 ; N iacute ; B 82 0 540 670 ;
+C -1 ; WX 602 ; N icircumflex ; B 82 0 540 657 ;
+C -1 ; WX 602 ; N idieresis ; B 82 0 540 641 ;
+C -1 ; WX 602 ; N igrave ; B 82 0 540 670 ;
+C -1 ; WX 602 ; N ntilde ; B 51 0 563 636 ;
+C -1 ; WX 602 ; N oacute ; B 45 -12 557 670 ;
+C -1 ; WX 602 ; N ocircumflex ; B 45 -12 557 657 ;
+C -1 ; WX 602 ; N odieresis ; B 45 -12 557 641 ;
+C -1 ; WX 602 ; N ograve ; B 45 -12 557 670 ;
+C -1 ; WX 602 ; N otilde ; B 45 -12 557 636 ;
+C -1 ; WX 602 ; N scaron ; B 87 -12 512 659 ;
+C -1 ; WX 602 ; N uacute ; B 35 -10 558 670 ;
+C -1 ; WX 602 ; N ucircumflex ; B 35 -10 558 657 ;
+C -1 ; WX 602 ; N udieresis ; B 35 -10 558 641 ;
+C -1 ; WX 602 ; N ugrave ; B 35 -10 558 670 ;
+C -1 ; WX 602 ; N ydieresis ; B 28 -195 570 641 ;
+C -1 ; WX 602 ; N zcaron ; B 98 0 508 659 ;
+C -1 ; WX 602 ; N trademark ; B 56 337 547 616 ;
+C -1 ; WX 602 ; N copyright ; B 11 45 591 625 ;
+C -1 ; WX 602 ; N logicalnot ; B 48 170 553 445 ;
+C -1 ; WX 602 ; N registered ; B 11 45 591 625 ;
+C -1 ; WX 602 ; N minus ; B 76 262 526 354 ;
+C -1 ; WX 602 ; N Eth ; B 20 -2 558 579 ;
+C -1 ; WX 602 ; N Thorn ; B 55 0 518 579 ;
+C -1 ; WX 602 ; N Yacute ; B 38 0 562 802 ;
+C -1 ; WX 602 ; N brokenbar ; B 271 -172 331 699 ;
+C -1 ; WX 602 ; N degree ; B 143 349 459 665 ;
+C -1 ; WX 602 ; N divide ; B 51 99 551 516 ;
+C -1 ; WX 602 ; N eth ; B 45 -12 557 640 ;
+C -1 ; WX 602 ; N mu ; B 32 -201 534 452 ;
+C -1 ; WX 602 ; N multiply ; B 105 125 493 511 ;
+C -1 ; WX 602 ; N onehalf ; B 59 -89 539 680 ;
+C -1 ; WX 602 ; N onequarter ; B 59 -94 539 680 ;
+C -1 ; WX 602 ; N onesuperior ; B 164 240 462 633 ;
+C -1 ; WX 602 ; N plusminus ; B 105 44 497 619 ;
+C -1 ; WX 602 ; N thorn ; B 33 -195 566 639 ;
+C -1 ; WX 602 ; N threequarters ; B 59 -94 539 680 ;
+C -1 ; WX 602 ; N threesuperior ; B 140 222 461 633 ;
+C -1 ; WX 602 ; N twosuperior ; B 145 240 451 632 ;
+C -1 ; WX 602 ; N yacute ; B 28 -195 570 670 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 0
+EndKernPairs
+StartTrackKern 0
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0419bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0419bt_.pfb
new file mode 100644
index 00000000..4a49dd59
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0419bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0582bt_.afm b/e2e-tests/cypress/fonts/Type1/c0582bt_.afm
new file mode 100644
index 00000000..f9e7d56b
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0582bt_.afm
@@ -0,0 +1,264 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Fixed Pitch 810
+Comment bitsFontID 0582
+Comment bitsManufacturingDate Mon Nov 5 23:53:48 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530582
+FontName Courier10PitchBT-Italic
+FullName Courier 10 Pitch Italic
+FamilyName Courier 10 Pitch
+Weight Normal
+ItalicAngle 12.0000
+IsFixedPitch true
+FontBBox -92 -299 664 858
+UnderlinePosition -97
+UnderlineThickness 82
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 579
+XHeight 452
+Ascender 639
+Descender -195
+StartCharMetrics 228
+C 32 ; WX 602 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 602 ; N exclam ; B 183 -16 418 600 ;
+C 34 ; WX 602 ; N quotedbl ; B 154 337 448 579 ;
+C 35 ; WX 602 ; N numbersign ; B 105 -58 497 674 ;
+C 36 ; WX 602 ; N dollar ; B 57 -123 525 665 ;
+C 37 ; WX 602 ; N percent ; B 101 -2 501 618 ;
+C 38 ; WX 602 ; N ampersand ; B 66 -10 492 552 ;
+C 39 ; WX 602 ; N quoteright ; B 227 321 445 579 ;
+C 40 ; WX 602 ; N parenleft ; B 207 -194 498 597 ;
+C 41 ; WX 602 ; N parenright ; B 107 -194 398 597 ;
+C 42 ; WX 602 ; N asterisk ; B 103 201 499 579 ;
+C 43 ; WX 602 ; N plus ; B 48 49 553 566 ;
+C 44 ; WX 602 ; N comma ; B 141 -160 400 147 ;
+C 45 ; WX 602 ; N hyphen ; B 93 178 541 273 ;
+C 46 ; WX 602 ; N period ; B 204 -16 397 159 ;
+C 47 ; WX 602 ; N slash ; B 114 -55 503 699 ;
+C 48 ; WX 602 ; N zero ; B 61 -16 538 634 ;
+C 49 ; WX 602 ; N one ; B 76 0 440 630 ;
+C 50 ; WX 602 ; N two ; B 25 0 504 631 ;
+C 51 ; WX 602 ; N three ; B 37 -16 498 633 ;
+C 52 ; WX 602 ; N four ; B 68 0 479 634 ;
+C 53 ; WX 602 ; N five ; B 31 -16 515 616 ;
+C 54 ; WX 602 ; N six ; B 90 -15 554 632 ;
+C 55 ; WX 602 ; N seven ; B 136 -8 533 616 ;
+C 56 ; WX 602 ; N eight ; B 80 -17 526 634 ;
+C 57 ; WX 602 ; N nine ; B 40 -14 504 632 ;
+C 58 ; WX 602 ; N colon ; B 173 -18 428 438 ;
+C 59 ; WX 602 ; N semicolon ; B 96 -160 434 438 ;
+C 60 ; WX 602 ; N less ; B 34 68 543 547 ;
+C 61 ; WX 602 ; N equal ; B 51 189 551 426 ;
+C 62 ; WX 602 ; N greater ; B 59 68 569 547 ;
+C 63 ; WX 602 ; N question ; B 134 -16 494 597 ;
+C 64 ; WX 602 ; N at ; B 131 -51 534 668 ;
+C 65 ; WX 602 ; N A ; B -52 0 555 579 ;
+C 66 ; WX 602 ; N B ; B -11 0 544 579 ;
+C 67 ; WX 602 ; N C ; B 37 -16 582 596 ;
+C 68 ; WX 602 ; N D ; B -11 0 577 579 ;
+C 69 ; WX 602 ; N E ; B -11 0 586 579 ;
+C 70 ; WX 602 ; N F ; B 12 0 610 579 ;
+C 71 ; WX 602 ; N G ; B 37 -16 582 596 ;
+C 72 ; WX 602 ; N H ; B -11 0 612 579 ;
+C 73 ; WX 602 ; N I ; B 45 0 556 579 ;
+C 74 ; WX 602 ; N J ; B 26 -16 637 579 ;
+C 75 ; WX 602 ; N K ; B -3 0 617 579 ;
+C 76 ; WX 602 ; N L ; B -8 0 551 579 ;
+C 77 ; WX 602 ; N M ; B -36 0 630 579 ;
+C 78 ; WX 602 ; N N ; B -1 -8 633 579 ;
+C 79 ; WX 602 ; N O ; B 21 -15 580 596 ;
+C 80 ; WX 602 ; N P ; B 5 0 578 579 ;
+C 81 ; WX 602 ; N Q ; B 21 -128 580 596 ;
+C 82 ; WX 602 ; N R ; B -9 0 545 579 ;
+C 83 ; WX 602 ; N S ; B 46 -21 566 598 ;
+C 84 ; WX 602 ; N T ; B 71 0 604 579 ;
+C 85 ; WX 602 ; N U ; B 82 -16 628 579 ;
+C 86 ; WX 607 ; N V ; B 66 -9 647 579 ;
+C 87 ; WX 602 ; N W ; B 60 -7 651 579 ;
+C 88 ; WX 602 ; N X ; B 2 0 609 579 ;
+C 89 ; WX 602 ; N Y ; B 86 0 621 579 ;
+C 90 ; WX 602 ; N Z ; B 57 0 558 579 ;
+C 91 ; WX 602 ; N bracketleft ; B 165 -183 512 579 ;
+C 92 ; WX 602 ; N backslash ; B 114 -55 503 698 ;
+C 93 ; WX 602 ; N bracketright ; B 82 -183 429 579 ;
+C 94 ; WX 602 ; N asciicircum ; B 126 448 476 676 ;
+C 95 ; WX 602 ; N underscore ; B -22 -299 626 -217 ;
+C 96 ; WX 602 ; N quoteleft ; B 218 339 436 597 ;
+C 97 ; WX 602 ; N a ; B 54 -10 524 463 ;
+C 98 ; WX 602 ; N b ; B -10 -13 562 639 ;
+C 99 ; WX 602 ; N c ; B 63 -12 573 463 ;
+C 100 ; WX 602 ; N d ; B 39 -11 591 639 ;
+C 101 ; WX 602 ; N e ; B 63 -12 552 463 ;
+C 102 ; WX 602 ; N f ; B 72 0 619 640 ;
+C 103 ; WX 602 ; N g ; B 62 -196 599 464 ;
+C 104 ; WX 602 ; N h ; B 17 0 522 639 ;
+C 105 ; WX 602 ; N i ; B 56 0 504 671 ;
+C 106 ; WX 602 ; N j ; B 3 -196 463 671 ;
+C 107 ; WX 602 ; N k ; B 28 0 575 639 ;
+C 108 ; WX 602 ; N l ; B 57 0 489 639 ;
+C 109 ; WX 602 ; N m ; B -34 0 574 464 ;
+C 110 ; WX 602 ; N n ; B 17 0 522 463 ;
+C 111 ; WX 602 ; N o ; B 48 -13 555 464 ;
+C 112 ; WX 602 ; N p ; B -55 -195 565 463 ;
+C 113 ; WX 602 ; N q ; B 34 -195 611 465 ;
+C 114 ; WX 602 ; N r ; B 41 0 599 463 ;
+C 115 ; WX 602 ; N s ; B 60 -12 536 464 ;
+C 116 ; WX 602 ; N t ; B 77 -10 505 591 ;
+C 117 ; WX 602 ; N u ; B 83 -12 534 452 ;
+C 118 ; WX 602 ; N v ; B 54 -12 601 452 ;
+C 119 ; WX 602 ; N w ; B 22 -14 636 452 ;
+C 120 ; WX 602 ; N x ; B 9 0 601 452 ;
+C 121 ; WX 602 ; N y ; B -40 -195 604 452 ;
+C 122 ; WX 602 ; N z ; B 63 0 544 452 ;
+C 123 ; WX 602 ; N braceleft ; B 143 -183 468 581 ;
+C 124 ; WX 602 ; N bar ; B 259 -261 342 789 ;
+C 125 ; WX 602 ; N braceright ; B 142 -183 467 581 ;
+C 126 ; WX 602 ; N asciitilde ; B 64 249 538 366 ;
+C 161 ; WX 602 ; N exclamdown ; B 192 -16 428 600 ;
+C 162 ; WX 602 ; N cent ; B 84 -17 512 630 ;
+C 163 ; WX 602 ; N sterling ; B 42 -17 519 636 ;
+C 164 ; WX 602 ; N fraction ; B 114 -55 503 699 ;
+C 165 ; WX 602 ; N yen ; B 43 0 609 616 ;
+C 166 ; WX 602 ; N florin ; B 17 -139 541 632 ;
+C 167 ; WX 602 ; N section ; B 50 -105 540 580 ;
+C 168 ; WX 602 ; N currency ; B 60 188 548 675 ;
+C 169 ; WX 602 ; N quotesingle ; B 243 330 358 579 ;
+C 170 ; WX 602 ; N quotedblleft ; B 114 339 553 597 ;
+C 171 ; WX 602 ; N guillemotleft ; B 141 47 460 409 ;
+C 172 ; WX 602 ; N guilsinglleft ; B 227 47 385 409 ;
+C 173 ; WX 602 ; N guilsinglright ; B 225 49 383 410 ;
+C 174 ; WX 602 ; N fi ; B -22 0 596 671 ;
+C 175 ; WX 602 ; N fl ; B -22 0 588 639 ;
+C 177 ; WX 602 ; N endash ; B -24 184 627 266 ;
+C 178 ; WX 602 ; N dagger ; B 151 -16 505 595 ;
+C 179 ; WX 602 ; N daggerdbl ; B 121 -16 516 596 ;
+C 180 ; WX 602 ; N periodcentered ; B 204 229 397 403 ;
+C 182 ; WX 602 ; N paragraph ; B 98 -77 615 616 ;
+C 183 ; WX 602 ; N bullet ; B 222 256 379 413 ;
+C 184 ; WX 602 ; N quotesinglbase ; B 164 -141 382 118 ;
+C 185 ; WX 602 ; N quotedblbase ; B 54 -140 494 119 ;
+C 186 ; WX 602 ; N quotedblright ; B 128 321 567 579 ;
+C 187 ; WX 602 ; N guillemotright ; B 130 49 448 410 ;
+C 188 ; WX 602 ; N ellipsis ; B 27 -12 574 133 ;
+C 189 ; WX 602 ; N perthousand ; B -44 -2 664 618 ;
+C 191 ; WX 602 ; N questiondown ; B 112 -17 473 596 ;
+C 193 ; WX 602 ; N grave ; B 193 515 466 660 ;
+C 194 ; WX 602 ; N acute ; B 241 515 514 660 ;
+C 195 ; WX 602 ; N circumflex ; B 186 507 486 658 ;
+C 196 ; WX 602 ; N tilde ; B 182 537 536 636 ;
+C 197 ; WX 602 ; N macron ; B 188 551 530 611 ;
+C 198 ; WX 602 ; N breve ; B 226 528 568 660 ;
+C 199 ; WX 602 ; N dotaccent ; B 297 527 422 652 ;
+C 200 ; WX 602 ; N dieresis ; B 175 522 543 641 ;
+C 202 ; WX 602 ; N ring ; B 242 499 490 747 ;
+C 203 ; WX 602 ; N cedilla ; B 146 -224 401 0 ;
+C 205 ; WX 602 ; N hungarumlaut ; B 211 515 580 659 ;
+C 206 ; WX 602 ; N ogonek ; B 176 -224 364 15 ;
+C 207 ; WX 602 ; N caron ; B 226 510 527 661 ;
+C 208 ; WX 602 ; N emdash ; B -25 184 627 266 ;
+C 225 ; WX 602 ; N AE ; B -92 0 609 579 ;
+C 227 ; WX 602 ; N ordfeminine ; B 108 219 484 597 ;
+C 232 ; WX 602 ; N Lslash ; B -8 0 551 579 ;
+C 233 ; WX 602 ; N Oslash ; B -25 -28 613 611 ;
+C 234 ; WX 602 ; N OE ; B 3 0 623 579 ;
+C 235 ; WX 602 ; N ordmasculine ; B 99 215 504 596 ;
+C 241 ; WX 602 ; N ae ; B -12 -12 598 464 ;
+C 245 ; WX 602 ; N dotlessi ; B 56 0 504 452 ;
+C 248 ; WX 602 ; N lslash ; B 57 0 520 639 ;
+C 249 ; WX 602 ; N oslash ; B 10 -24 579 476 ;
+C 250 ; WX 602 ; N oe ; B 7 -12 596 464 ;
+C 251 ; WX 602 ; N germandbls ; B -20 -13 544 639 ;
+C -1 ; WX 602 ; N Aacute ; B -52 0 555 792 ;
+C -1 ; WX 602 ; N Acircumflex ; B -52 0 555 790 ;
+C -1 ; WX 602 ; N Adieresis ; B -52 0 555 773 ;
+C -1 ; WX 602 ; N Agrave ; B -52 0 555 792 ;
+C -1 ; WX 602 ; N Aring ; B -52 0 555 858 ;
+C -1 ; WX 602 ; N Atilde ; B -52 0 555 768 ;
+C -1 ; WX 602 ; N Ccedilla ; B 37 -224 582 596 ;
+C -1 ; WX 602 ; N Eacute ; B -11 0 586 792 ;
+C -1 ; WX 602 ; N Ecircumflex ; B -11 0 586 790 ;
+C -1 ; WX 602 ; N Edieresis ; B -11 0 586 773 ;
+C -1 ; WX 602 ; N Egrave ; B -11 0 586 792 ;
+C -1 ; WX 602 ; N Iacute ; B 45 0 556 792 ;
+C -1 ; WX 602 ; N Icircumflex ; B 45 0 556 790 ;
+C -1 ; WX 602 ; N Idieresis ; B 45 0 565 773 ;
+C -1 ; WX 602 ; N Igrave ; B 45 0 556 792 ;
+C -1 ; WX 602 ; N Ntilde ; B -1 -8 633 768 ;
+C -1 ; WX 602 ; N Oacute ; B 21 -15 580 792 ;
+C -1 ; WX 602 ; N Ocircumflex ; B 21 -15 580 790 ;
+C -1 ; WX 602 ; N Odieresis ; B 21 -15 580 773 ;
+C -1 ; WX 602 ; N Ograve ; B 21 -15 580 792 ;
+C -1 ; WX 602 ; N Otilde ; B 21 -15 580 768 ;
+C -1 ; WX 602 ; N Scaron ; B 46 -21 566 793 ;
+C -1 ; WX 602 ; N Uacute ; B 82 -16 628 792 ;
+C -1 ; WX 602 ; N Ucircumflex ; B 82 -16 628 790 ;
+C -1 ; WX 602 ; N Udieresis ; B 82 -16 628 773 ;
+C -1 ; WX 602 ; N Ugrave ; B 82 -16 628 792 ;
+C -1 ; WX 602 ; N Ydieresis ; B 86 0 621 773 ;
+C -1 ; WX 602 ; N Zcaron ; B 57 0 558 793 ;
+C -1 ; WX 602 ; N aacute ; B 54 -10 524 660 ;
+C -1 ; WX 602 ; N acircumflex ; B 54 -10 524 658 ;
+C -1 ; WX 602 ; N adieresis ; B 54 -10 553 641 ;
+C -1 ; WX 602 ; N agrave ; B 54 -10 524 660 ;
+C -1 ; WX 602 ; N aring ; B 54 -10 524 743 ;
+C -1 ; WX 602 ; N atilde ; B 54 -10 546 636 ;
+C -1 ; WX 602 ; N ccedilla ; B 63 -224 573 463 ;
+C -1 ; WX 602 ; N eacute ; B 63 -12 552 660 ;
+C -1 ; WX 602 ; N ecircumflex ; B 63 -12 552 658 ;
+C -1 ; WX 602 ; N edieresis ; B 63 -12 552 641 ;
+C -1 ; WX 602 ; N egrave ; B 63 -12 552 660 ;
+C -1 ; WX 602 ; N iacute ; B 56 0 514 660 ;
+C -1 ; WX 602 ; N icircumflex ; B 56 0 504 658 ;
+C -1 ; WX 602 ; N idieresis ; B 56 0 543 641 ;
+C -1 ; WX 602 ; N igrave ; B 56 0 504 660 ;
+C -1 ; WX 602 ; N ntilde ; B 17 0 545 636 ;
+C -1 ; WX 602 ; N oacute ; B 48 -13 555 660 ;
+C -1 ; WX 602 ; N ocircumflex ; B 48 -13 555 658 ;
+C -1 ; WX 602 ; N odieresis ; B 48 -13 555 641 ;
+C -1 ; WX 602 ; N ograve ; B 48 -13 555 660 ;
+C -1 ; WX 602 ; N otilde ; B 48 -13 555 636 ;
+C -1 ; WX 602 ; N scaron ; B 60 -12 536 661 ;
+C -1 ; WX 602 ; N uacute ; B 83 -12 534 660 ;
+C -1 ; WX 602 ; N ucircumflex ; B 83 -12 534 658 ;
+C -1 ; WX 602 ; N udieresis ; B 83 -12 534 641 ;
+C -1 ; WX 602 ; N ugrave ; B 83 -12 534 660 ;
+C -1 ; WX 602 ; N ydieresis ; B -40 -195 604 641 ;
+C -1 ; WX 602 ; N zcaron ; B 63 0 544 661 ;
+C -1 ; WX 602 ; N trademark ; B 56 337 547 616 ;
+C -1 ; WX 602 ; N copyright ; B 11 45 591 625 ;
+C -1 ; WX 602 ; N logicalnot ; B 48 170 553 445 ;
+C -1 ; WX 602 ; N registered ; B 11 45 591 625 ;
+C -1 ; WX 602 ; N minus ; B 76 262 526 354 ;
+C -1 ; WX 602 ; N Eth ; B -11 0 577 579 ;
+C -1 ; WX 602 ; N Thorn ; B 5 0 518 579 ;
+C -1 ; WX 602 ; N Yacute ; B 86 0 621 792 ;
+C -1 ; WX 602 ; N brokenbar ; B 271 -172 331 699 ;
+C -1 ; WX 602 ; N degree ; B 143 349 459 665 ;
+C -1 ; WX 602 ; N divide ; B 51 99 551 516 ;
+C -1 ; WX 602 ; N eth ; B 47 -13 554 636 ;
+C -1 ; WX 602 ; N mu ; B 32 -201 534 452 ;
+C -1 ; WX 602 ; N multiply ; B 105 125 493 511 ;
+C -1 ; WX 602 ; N onehalf ; B 59 -89 539 680 ;
+C -1 ; WX 602 ; N onequarter ; B 59 -94 539 680 ;
+C -1 ; WX 602 ; N onesuperior ; B 164 240 462 633 ;
+C -1 ; WX 602 ; N plusminus ; B 105 44 497 619 ;
+C -1 ; WX 602 ; N thorn ; B -55 -195 565 639 ;
+C -1 ; WX 602 ; N threequarters ; B 59 -94 539 680 ;
+C -1 ; WX 602 ; N threesuperior ; B 140 222 461 633 ;
+C -1 ; WX 602 ; N twosuperior ; B 145 240 451 632 ;
+C -1 ; WX 602 ; N yacute ; B -40 -195 604 660 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 0
+EndKernPairs
+StartTrackKern 0
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0582bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0582bt_.pfb
new file mode 100644
index 00000000..9baa1da2
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0582bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0583bt_.afm b/e2e-tests/cypress/fonts/Type1/c0583bt_.afm
new file mode 100644
index 00000000..032ef458
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0583bt_.afm
@@ -0,0 +1,264 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Fixed Pitch 810
+Comment bitsFontID 0583
+Comment bitsManufacturingDate Mon Nov 5 23:57:48 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530583
+FontName Courier10PitchBT-Bold
+FullName Courier 10 Pitch Bold
+FamilyName Courier 10 Pitch
+Weight Bold
+ItalicAngle 0.00
+IsFixedPitch true
+FontBBox -44 -310 665 875
+UnderlinePosition -97
+UnderlineThickness 93
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 579
+XHeight 452
+Ascender 639
+Descender -195
+StartCharMetrics 228
+C 32 ; WX 602 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 602 ; N exclam ; B 205 -11 398 613 ;
+C 34 ; WX 602 ; N quotedbl ; B 154 337 448 579 ;
+C 35 ; WX 602 ; N numbersign ; B 99 -58 502 684 ;
+C 36 ; WX 602 ; N dollar ; B 84 -122 510 672 ;
+C 37 ; WX 602 ; N percent ; B 100 -16 501 633 ;
+C 38 ; WX 602 ; N ampersand ; B 57 -16 525 562 ;
+C 39 ; WX 602 ; N quoteright ; B 206 311 403 579 ;
+C 40 ; WX 602 ; N parenleft ; B 196 -197 452 597 ;
+C 41 ; WX 602 ; N parenright ; B 151 -197 407 597 ;
+C 42 ; WX 602 ; N asterisk ; B 102 197 500 579 ;
+C 43 ; WX 602 ; N plus ; B 35 42 566 573 ;
+C 44 ; WX 602 ; N comma ; B 170 -159 410 169 ;
+C 45 ; WX 602 ; N hyphen ; B 76 170 526 282 ;
+C 46 ; WX 602 ; N period ; B 199 -7 404 177 ;
+C 47 ; WX 602 ; N slash ; B 103 -58 513 703 ;
+C 48 ; WX 602 ; N zero ; B 57 -17 545 632 ;
+C 49 ; WX 602 ; N one ; B 103 0 516 633 ;
+C 50 ; WX 602 ; N two ; B 62 0 506 633 ;
+C 51 ; WX 602 ; N three ; B 73 -17 502 633 ;
+C 52 ; WX 602 ; N four ; B 73 0 527 631 ;
+C 53 ; WX 602 ; N five ; B 69 -17 515 616 ;
+C 54 ; WX 602 ; N six ; B 78 -16 521 633 ;
+C 55 ; WX 602 ; N seven ; B 61 -9 476 616 ;
+C 56 ; WX 602 ; N eight ; B 74 -17 528 633 ;
+C 57 ; WX 602 ; N nine ; B 76 -16 519 632 ;
+C 58 ; WX 602 ; N colon ; B 198 -7 403 438 ;
+C 59 ; WX 602 ; N semicolon ; B 170 -159 410 438 ;
+C 60 ; WX 602 ; N less ; B 43 62 558 553 ;
+C 61 ; WX 602 ; N equal ; B 36 160 566 456 ;
+C 62 ; WX 602 ; N greater ; B 43 62 558 553 ;
+C 63 ; WX 602 ; N question ; B 115 -8 495 613 ;
+C 64 ; WX 602 ; N at ; B 66 -50 524 668 ;
+C 65 ; WX 602 ; N A ; B -6 0 609 579 ;
+C 66 ; WX 602 ; N B ; B 34 0 569 579 ;
+C 67 ; WX 602 ; N C ; B 30 -16 564 596 ;
+C 68 ; WX 602 ; N D ; B 33 0 569 579 ;
+C 69 ; WX 602 ; N E ; B 35 0 547 579 ;
+C 70 ; WX 602 ; N F ; B 39 0 551 579 ;
+C 71 ; WX 602 ; N G ; B 25 -16 602 596 ;
+C 72 ; WX 602 ; N H ; B 33 0 569 579 ;
+C 73 ; WX 602 ; N I ; B 94 0 508 579 ;
+C 74 ; WX 602 ; N J ; B 53 -16 580 579 ;
+C 75 ; WX 602 ; N K ; B 33 0 582 579 ;
+C 76 ; WX 602 ; N L ; B 21 0 575 579 ;
+C 77 ; WX 602 ; N M ; B -12 0 613 579 ;
+C 78 ; WX 602 ; N N ; B 25 -7 587 579 ;
+C 79 ; WX 602 ; N O ; B 25 -16 577 595 ;
+C 80 ; WX 602 ; N P ; B 55 0 560 579 ;
+C 81 ; WX 602 ; N Q ; B 25 -157 577 595 ;
+C 82 ; WX 602 ; N R ; B 40 0 603 579 ;
+C 83 ; WX 602 ; N S ; B 56 -16 545 596 ;
+C 84 ; WX 602 ; N T ; B 30 0 572 580 ;
+C 85 ; WX 602 ; N U ; B 12 -16 590 579 ;
+C 86 ; WX 602 ; N V ; B 0 0 603 579 ;
+C 87 ; WX 602 ; N W ; B -12 0 613 579 ;
+C 88 ; WX 602 ; N X ; B 35 0 573 579 ;
+C 89 ; WX 602 ; N Y ; B 38 0 556 579 ;
+C 90 ; WX 602 ; N Z ; B 69 0 512 579 ;
+C 91 ; WX 602 ; N bracketleft ; B 205 -181 428 579 ;
+C 92 ; WX 602 ; N backslash ; B 103 -58 512 703 ;
+C 93 ; WX 602 ; N bracketright ; B 147 -181 370 579 ;
+C 94 ; WX 602 ; N asciicircum ; B 104 422 498 671 ;
+C 95 ; WX 602 ; N underscore ; B -22 -310 626 -207 ;
+C 96 ; WX 602 ; N quoteleft ; B 202 327 398 596 ;
+C 97 ; WX 602 ; N a ; B 51 -12 562 464 ;
+C 98 ; WX 602 ; N b ; B 30 -12 567 639 ;
+C 99 ; WX 602 ; N c ; B 43 -12 546 464 ;
+C 100 ; WX 602 ; N d ; B 34 -12 571 639 ;
+C 101 ; WX 602 ; N e ; B 43 -12 555 463 ;
+C 102 ; WX 602 ; N f ; B 97 0 549 641 ;
+C 103 ; WX 602 ; N g ; B 34 -196 571 463 ;
+C 104 ; WX 602 ; N h ; B 39 0 568 639 ;
+C 105 ; WX 602 ; N i ; B 82 0 536 672 ;
+C 106 ; WX 602 ; N j ; B 63 -196 426 672 ;
+C 107 ; WX 602 ; N k ; B 46 0 569 639 ;
+C 108 ; WX 602 ; N l ; B 73 0 528 639 ;
+C 109 ; WX 602 ; N m ; B -7 0 616 464 ;
+C 110 ; WX 602 ; N n ; B 40 0 568 463 ;
+C 111 ; WX 602 ; N o ; B 34 -12 568 463 ;
+C 112 ; WX 602 ; N p ; B 1 -195 567 463 ;
+C 113 ; WX 602 ; N q ; B 34 -195 600 463 ;
+C 114 ; WX 602 ; N r ; B 59 0 573 464 ;
+C 115 ; WX 602 ; N s ; B 78 -14 527 467 ;
+C 116 ; WX 602 ; N t ; B 39 -10 548 600 ;
+C 117 ; WX 602 ; N u ; B 33 -12 560 452 ;
+C 118 ; WX 602 ; N v ; B 28 -12 572 452 ;
+C 119 ; WX 602 ; N w ; B -27 -12 628 452 ;
+C 120 ; WX 602 ; N x ; B 53 0 553 452 ;
+C 121 ; WX 602 ; N y ; B 28 -195 572 452 ;
+C 122 ; WX 602 ; N z ; B 82 0 501 452 ;
+C 123 ; WX 602 ; N braceleft ; B 143 -185 473 582 ;
+C 124 ; WX 602 ; N bar ; B 243 -261 359 789 ;
+C 125 ; WX 602 ; N braceright ; B 142 -185 473 582 ;
+C 126 ; WX 602 ; N asciitilde ; B 50 236 551 380 ;
+C 161 ; WX 602 ; N exclamdown ; B 205 -11 398 613 ;
+C 162 ; WX 602 ; N cent ; B 70 -33 517 646 ;
+C 163 ; WX 602 ; N sterling ; B 80 -15 512 633 ;
+C 164 ; WX 602 ; N fraction ; B 103 -58 513 703 ;
+C 165 ; WX 602 ; N yen ; B 42 0 559 616 ;
+C 166 ; WX 602 ; N florin ; B 39 -139 562 632 ;
+C 167 ; WX 602 ; N section ; B 63 -105 540 579 ;
+C 168 ; WX 602 ; N currency ; B 58 189 544 676 ;
+C 169 ; WX 602 ; N quotesingle ; B 245 337 357 579 ;
+C 170 ; WX 602 ; N quotedblleft ; B 98 327 486 596 ;
+C 171 ; WX 602 ; N guillemotleft ; B 142 36 459 411 ;
+C 172 ; WX 602 ; N guilsinglleft ; B 223 36 380 411 ;
+C 173 ; WX 602 ; N guilsinglright ; B 222 36 378 411 ;
+C 174 ; WX 602 ; N fi ; B 21 0 578 672 ;
+C 175 ; WX 602 ; N fl ; B 21 0 578 639 ;
+C 177 ; WX 602 ; N endash ; B -22 179 626 272 ;
+C 178 ; WX 602 ; N dagger ; B 121 -15 481 596 ;
+C 179 ; WX 602 ; N daggerdbl ; B 121 -15 484 596 ;
+C 180 ; WX 602 ; N periodcentered ; B 201 208 400 407 ;
+C 182 ; WX 602 ; N paragraph ; B 45 -77 566 616 ;
+C 183 ; WX 602 ; N bullet ; B 222 256 379 413 ;
+C 184 ; WX 602 ; N quotesinglbase ; B 196 -159 393 110 ;
+C 185 ; WX 602 ; N quotedblbase ; B 103 -159 491 110 ;
+C 186 ; WX 602 ; N quotedblright ; B 110 311 499 579 ;
+C 187 ; WX 602 ; N guillemotright ; B 141 36 457 411 ;
+C 188 ; WX 602 ; N ellipsis ; B 27 -12 574 133 ;
+C 189 ; WX 602 ; N perthousand ; B -44 -16 665 633 ;
+C 191 ; WX 602 ; N questiondown ; B 113 -8 494 613 ;
+C 193 ; WX 602 ; N grave ; B 121 493 407 670 ;
+C 194 ; WX 602 ; N acute ; B 195 493 481 670 ;
+C 195 ; WX 602 ; N circumflex ; B 130 508 472 662 ;
+C 196 ; WX 602 ; N tilde ; B 122 533 479 640 ;
+C 197 ; WX 602 ; N macron ; B 130 547 472 615 ;
+C 198 ; WX 602 ; N breve ; B 114 528 488 661 ;
+C 199 ; WX 602 ; N dotaccent ; B 234 523 367 656 ;
+C 200 ; WX 602 ; N dieresis ; B 117 519 485 644 ;
+C 202 ; WX 602 ; N ring ; B 168 499 433 764 ;
+C 203 ; WX 602 ; N cedilla ; B 179 -233 437 0 ;
+C 205 ; WX 602 ; N hungarumlaut ; B 165 502 535 666 ;
+C 206 ; WX 602 ; N ogonek ; B 240 -229 440 15 ;
+C 207 ; WX 602 ; N caron ; B 130 510 472 663 ;
+C 208 ; WX 602 ; N emdash ; B -22 179 626 272 ;
+C 225 ; WX 602 ; N AE ; B -36 0 611 579 ;
+C 227 ; WX 602 ; N ordfeminine ; B 102 143 511 633 ;
+C 232 ; WX 602 ; N Lslash ; B 21 0 575 579 ;
+C 233 ; WX 602 ; N Oslash ; B 25 -67 577 636 ;
+C 234 ; WX 602 ; N OE ; B 6 0 606 579 ;
+C 235 ; WX 602 ; N ordmasculine ; B 87 143 514 632 ;
+C 241 ; WX 602 ; N ae ; B 3 -12 601 464 ;
+C 245 ; WX 602 ; N dotlessi ; B 82 0 536 452 ;
+C 248 ; WX 602 ; N lslash ; B 73 0 528 639 ;
+C 249 ; WX 602 ; N oslash ; B 34 -75 568 516 ;
+C 250 ; WX 602 ; N oe ; B 0 -12 601 463 ;
+C 251 ; WX 602 ; N germandbls ; B 12 -12 554 640 ;
+C -1 ; WX 602 ; N Aacute ; B -6 0 609 802 ;
+C -1 ; WX 602 ; N Acircumflex ; B -6 0 609 794 ;
+C -1 ; WX 602 ; N Adieresis ; B -6 0 609 776 ;
+C -1 ; WX 602 ; N Agrave ; B -6 0 609 802 ;
+C -1 ; WX 602 ; N Aring ; B -6 0 609 875 ;
+C -1 ; WX 602 ; N Atilde ; B -6 0 609 772 ;
+C -1 ; WX 602 ; N Ccedilla ; B 30 -233 564 596 ;
+C -1 ; WX 602 ; N Eacute ; B 35 0 547 802 ;
+C -1 ; WX 602 ; N Ecircumflex ; B 35 0 547 794 ;
+C -1 ; WX 602 ; N Edieresis ; B 35 0 547 776 ;
+C -1 ; WX 602 ; N Egrave ; B 35 0 547 802 ;
+C -1 ; WX 602 ; N Iacute ; B 94 0 508 802 ;
+C -1 ; WX 602 ; N Icircumflex ; B 94 0 508 794 ;
+C -1 ; WX 602 ; N Idieresis ; B 94 0 508 776 ;
+C -1 ; WX 602 ; N Igrave ; B 94 0 508 802 ;
+C -1 ; WX 602 ; N Ntilde ; B 25 -7 587 772 ;
+C -1 ; WX 602 ; N Oacute ; B 25 -16 577 802 ;
+C -1 ; WX 602 ; N Ocircumflex ; B 25 -16 577 794 ;
+C -1 ; WX 602 ; N Odieresis ; B 25 -16 577 776 ;
+C -1 ; WX 602 ; N Ograve ; B 25 -16 577 802 ;
+C -1 ; WX 602 ; N Otilde ; B 25 -16 577 772 ;
+C -1 ; WX 602 ; N Scaron ; B 56 -16 545 795 ;
+C -1 ; WX 602 ; N Uacute ; B 12 -16 590 802 ;
+C -1 ; WX 602 ; N Ucircumflex ; B 12 -16 590 794 ;
+C -1 ; WX 602 ; N Udieresis ; B 12 -16 590 776 ;
+C -1 ; WX 602 ; N Ugrave ; B 12 -16 590 802 ;
+C -1 ; WX 602 ; N Ydieresis ; B 38 0 556 776 ;
+C -1 ; WX 602 ; N Zcaron ; B 69 0 512 795 ;
+C -1 ; WX 602 ; N aacute ; B 51 -12 562 670 ;
+C -1 ; WX 602 ; N acircumflex ; B 51 -12 562 662 ;
+C -1 ; WX 602 ; N adieresis ; B 51 -12 562 644 ;
+C -1 ; WX 602 ; N agrave ; B 51 -12 562 670 ;
+C -1 ; WX 602 ; N aring ; B 51 -12 562 764 ;
+C -1 ; WX 602 ; N atilde ; B 51 -12 562 640 ;
+C -1 ; WX 602 ; N ccedilla ; B 43 -233 546 464 ;
+C -1 ; WX 602 ; N eacute ; B 43 -12 555 670 ;
+C -1 ; WX 602 ; N ecircumflex ; B 43 -12 555 662 ;
+C -1 ; WX 602 ; N edieresis ; B 43 -12 555 644 ;
+C -1 ; WX 602 ; N egrave ; B 43 -12 555 670 ;
+C -1 ; WX 602 ; N iacute ; B 82 0 536 670 ;
+C -1 ; WX 602 ; N icircumflex ; B 82 0 536 662 ;
+C -1 ; WX 602 ; N idieresis ; B 82 0 536 644 ;
+C -1 ; WX 602 ; N igrave ; B 82 0 536 670 ;
+C -1 ; WX 602 ; N ntilde ; B 40 0 568 640 ;
+C -1 ; WX 602 ; N oacute ; B 34 -12 568 670 ;
+C -1 ; WX 602 ; N ocircumflex ; B 34 -12 568 662 ;
+C -1 ; WX 602 ; N odieresis ; B 34 -12 568 644 ;
+C -1 ; WX 602 ; N ograve ; B 34 -12 568 670 ;
+C -1 ; WX 602 ; N otilde ; B 34 -12 568 640 ;
+C -1 ; WX 602 ; N scaron ; B 78 -14 527 663 ;
+C -1 ; WX 602 ; N uacute ; B 33 -12 560 670 ;
+C -1 ; WX 602 ; N ucircumflex ; B 33 -12 560 662 ;
+C -1 ; WX 602 ; N udieresis ; B 33 -12 560 644 ;
+C -1 ; WX 602 ; N ugrave ; B 33 -12 560 670 ;
+C -1 ; WX 602 ; N ydieresis ; B 28 -195 572 644 ;
+C -1 ; WX 602 ; N zcaron ; B 82 0 501 663 ;
+C -1 ; WX 602 ; N trademark ; B 32 329 566 590 ;
+C -1 ; WX 602 ; N copyright ; B 1 32 601 632 ;
+C -1 ; WX 602 ; N logicalnot ; B 34 156 567 459 ;
+C -1 ; WX 602 ; N registered ; B 1 32 601 632 ;
+C -1 ; WX 602 ; N minus ; B 36 257 566 359 ;
+C -1 ; WX 602 ; N Eth ; B 33 0 569 579 ;
+C -1 ; WX 602 ; N Thorn ; B 68 0 526 579 ;
+C -1 ; WX 602 ; N Yacute ; B 38 0 556 802 ;
+C -1 ; WX 602 ; N brokenbar ; B 253 -172 349 699 ;
+C -1 ; WX 602 ; N degree ; B 143 349 459 665 ;
+C -1 ; WX 602 ; N divide ; B 36 86 565 531 ;
+C -1 ; WX 602 ; N eth ; B 34 -12 563 639 ;
+C -1 ; WX 602 ; N mu ; B 25 -195 542 453 ;
+C -1 ; WX 602 ; N multiply ; B 93 110 509 526 ;
+C -1 ; WX 602 ; N onehalf ; B 22 -86 572 680 ;
+C -1 ; WX 602 ; N onequarter ; B 22 -93 570 680 ;
+C -1 ; WX 602 ; N onesuperior ; B 175 241 428 624 ;
+C -1 ; WX 602 ; N plusminus ; B 60 29 541 619 ;
+C -1 ; WX 602 ; N thorn ; B 1 -195 567 639 ;
+C -1 ; WX 602 ; N threequarters ; B 38 -93 570 689 ;
+C -1 ; WX 602 ; N threesuperior ; B 127 222 452 633 ;
+C -1 ; WX 602 ; N twosuperior ; B 145 240 465 632 ;
+C -1 ; WX 602 ; N yacute ; B 28 -195 572 670 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 0
+EndKernPairs
+StartTrackKern 0
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0583bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0583bt_.pfb
new file mode 100644
index 00000000..08f6871c
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0583bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0611bt_.afm b/e2e-tests/cypress/fonts/Type1/c0611bt_.afm
new file mode 100644
index 00000000..4578e779
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0611bt_.afm
@@ -0,0 +1,264 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Fixed Pitch 810
+Comment bitsFontID 0611
+Comment bitsManufacturingDate Tue Nov 6 01:15:55 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530611
+FontName Courier10PitchBT-BoldItalic
+FullName Courier 10 Pitch Bold Italic
+FamilyName Courier 10 Pitch
+Weight Bold
+ItalicAngle 12.0000
+IsFixedPitch true
+FontBBox -79 -310 665 875
+UnderlinePosition -97
+UnderlineThickness 93
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 579
+XHeight 452
+Ascender 639
+Descender -195
+StartCharMetrics 228
+C 32 ; WX 602 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 602 ; N exclam ; B 170 -14 408 596 ;
+C 34 ; WX 602 ; N quotedbl ; B 169 323 481 579 ;
+C 35 ; WX 602 ; N numbersign ; B 99 -58 502 684 ;
+C 36 ; WX 602 ; N dollar ; B 62 -123 559 672 ;
+C 37 ; WX 602 ; N percent ; B 100 -16 501 633 ;
+C 38 ; WX 602 ; N ampersand ; B 38 -14 538 562 ;
+C 39 ; WX 602 ; N quoteright ; B 185 304 418 579 ;
+C 40 ; WX 602 ; N parenleft ; B 182 -194 524 596 ;
+C 41 ; WX 602 ; N parenright ; B 75 -195 417 595 ;
+C 42 ; WX 602 ; N asterisk ; B 102 234 500 616 ;
+C 43 ; WX 602 ; N plus ; B 35 42 566 573 ;
+C 44 ; WX 602 ; N comma ; B 115 -164 396 167 ;
+C 45 ; WX 602 ; N hyphen ; B 62 170 526 282 ;
+C 46 ; WX 602 ; N period ; B 182 -12 377 163 ;
+C 47 ; WX 602 ; N slash ; B 9 -124 580 704 ;
+C 48 ; WX 602 ; N zero ; B 43 -15 559 634 ;
+C 49 ; WX 602 ; N one ; B 48 0 455 633 ;
+C 50 ; WX 602 ; N two ; B 14 0 527 631 ;
+C 51 ; WX 602 ; N three ; B 19 -17 507 634 ;
+C 52 ; WX 602 ; N four ; B 54 0 532 635 ;
+C 53 ; WX 602 ; N five ; B 37 -17 560 616 ;
+C 54 ; WX 602 ; N six ; B 71 -17 585 633 ;
+C 55 ; WX 602 ; N seven ; B 114 -8 545 616 ;
+C 56 ; WX 602 ; N eight ; B 52 -16 549 633 ;
+C 57 ; WX 602 ; N nine ; B 26 -16 540 634 ;
+C 58 ; WX 602 ; N colon ; B 155 -12 405 438 ;
+C 59 ; WX 602 ; N semicolon ; B 84 -164 427 438 ;
+C 60 ; WX 602 ; N less ; B 41 62 556 553 ;
+C 61 ; WX 602 ; N equal ; B 36 160 566 456 ;
+C 62 ; WX 602 ; N greater ; B 32 62 547 553 ;
+C 63 ; WX 602 ; N question ; B 123 -14 510 596 ;
+C 64 ; WX 602 ; N at ; B 76 -53 550 667 ;
+C 65 ; WX 602 ; N A ; B -44 0 562 579 ;
+C 66 ; WX 602 ; N B ; B -11 0 551 579 ;
+C 67 ; WX 602 ; N C ; B 31 -16 600 595 ;
+C 68 ; WX 602 ; N D ; B -10 0 572 579 ;
+C 69 ; WX 602 ; N E ; B -8 0 593 579 ;
+C 70 ; WX 602 ; N F ; B 6 0 607 579 ;
+C 71 ; WX 602 ; N G ; B 24 -16 586 595 ;
+C 72 ; WX 602 ; N H ; B -8 0 606 579 ;
+C 73 ; WX 602 ; N I ; B 56 0 544 579 ;
+C 74 ; WX 602 ; N J ; B 19 -16 624 579 ;
+C 75 ; WX 602 ; N K ; B 1 0 621 579 ;
+C 76 ; WX 602 ; N L ; B -4 0 543 579 ;
+C 77 ; WX 602 ; N M ; B -54 0 638 579 ;
+C 78 ; WX 602 ; N N ; B -25 -9 625 579 ;
+C 79 ; WX 602 ; N O ; B 16 -15 585 596 ;
+C 80 ; WX 602 ; N P ; B 3 0 579 579 ;
+C 81 ; WX 602 ; N Q ; B 16 -158 585 596 ;
+C 82 ; WX 602 ; N R ; B -6 0 561 579 ;
+C 83 ; WX 602 ; N S ; B 26 -20 572 604 ;
+C 84 ; WX 602 ; N T ; B 51 0 612 579 ;
+C 85 ; WX 602 ; N U ; B 56 -17 628 579 ;
+C 86 ; WX 602 ; N V ; B 44 0 640 579 ;
+C 87 ; WX 602 ; N W ; B 39 0 645 579 ;
+C 88 ; WX 602 ; N X ; B 0 0 581 579 ;
+C 89 ; WX 602 ; N Y ; B 63 0 587 579 ;
+C 90 ; WX 602 ; N Z ; B 44 0 537 579 ;
+C 91 ; WX 602 ; N bracketleft ; B 144 -181 519 579 ;
+C 92 ; WX 602 ; N backslash ; B 80 -126 522 703 ;
+C 93 ; WX 602 ; N bracketright ; B 66 -181 441 579 ;
+C 94 ; WX 602 ; N asciicircum ; B 104 422 498 671 ;
+C 95 ; WX 602 ; N underscore ; B -22 -310 626 -207 ;
+C 96 ; WX 602 ; N quoteleft ; B 212 323 445 597 ;
+C 97 ; WX 602 ; N a ; B 35 -11 514 465 ;
+C 98 ; WX 602 ; N b ; B -21 -9 541 639 ;
+C 99 ; WX 602 ; N c ; B 27 -12 557 465 ;
+C 100 ; WX 602 ; N d ; B 34 -12 582 639 ;
+C 101 ; WX 602 ; N e ; B 34 -12 537 464 ;
+C 102 ; WX 602 ; N f ; B 66 0 616 639 ;
+C 103 ; WX 602 ; N g ; B 34 -195 600 464 ;
+C 104 ; WX 602 ; N h ; B -1 0 520 639 ;
+C 105 ; WX 602 ; N i ; B 47 0 483 672 ;
+C 106 ; WX 602 ; N j ; B -19 -195 471 672 ;
+C 107 ; WX 602 ; N k ; B 6 0 558 639 ;
+C 108 ; WX 602 ; N l ; B 37 0 473 639 ;
+C 109 ; WX 602 ; N m ; B -54 0 573 464 ;
+C 110 ; WX 602 ; N n ; B -1 0 520 463 ;
+C 111 ; WX 602 ; N o ; B 39 -13 545 464 ;
+C 112 ; WX 602 ; N p ; B -79 -195 549 463 ;
+C 113 ; WX 602 ; N q ; B 34 -195 605 464 ;
+C 114 ; WX 602 ; N r ; B 35 0 571 464 ;
+C 115 ; WX 602 ; N s ; B 45 -18 527 473 ;
+C 116 ; WX 602 ; N t ; B 66 -8 508 601 ;
+C 117 ; WX 602 ; N u ; B 57 -12 523 452 ;
+C 118 ; WX 602 ; N v ; B 55 -12 575 452 ;
+C 119 ; WX 602 ; N w ; B 5 -13 635 452 ;
+C 120 ; WX 602 ; N x ; B 26 0 551 452 ;
+C 121 ; WX 602 ; N y ; B -7 -195 575 452 ;
+C 122 ; WX 602 ; N z ; B 48 0 522 452 ;
+C 123 ; WX 602 ; N braceleft ; B 143 -185 473 582 ;
+C 124 ; WX 602 ; N bar ; B 243 -261 359 789 ;
+C 125 ; WX 602 ; N braceright ; B 124 -185 454 582 ;
+C 126 ; WX 602 ; N asciitilde ; B 50 236 551 380 ;
+C 161 ; WX 602 ; N exclamdown ; B 184 -14 422 595 ;
+C 162 ; WX 602 ; N cent ; B 82 -33 543 646 ;
+C 163 ; WX 602 ; N sterling ; B 78 -15 559 632 ;
+C 164 ; WX 602 ; N fraction ; B 9 -124 580 704 ;
+C 165 ; WX 602 ; N yen ; B 42 0 592 616 ;
+C 166 ; WX 602 ; N florin ; B -34 -140 614 631 ;
+C 167 ; WX 602 ; N section ; B 35 -103 566 579 ;
+C 168 ; WX 602 ; N currency ; B 58 189 544 676 ;
+C 169 ; WX 602 ; N quotesingle ; B 242 323 360 579 ;
+C 170 ; WX 602 ; N quotedblleft ; B 106 323 550 597 ;
+C 171 ; WX 602 ; N guillemotleft ; B 126 35 469 408 ;
+C 172 ; WX 602 ; N guilsinglleft ; B 220 35 391 408 ;
+C 173 ; WX 602 ; N guilsinglright ; B 220 38 391 411 ;
+C 174 ; WX 602 ; N fi ; B -21 0 595 672 ;
+C 175 ; WX 602 ; N fl ; B -21 0 599 639 ;
+C 177 ; WX 602 ; N endash ; B -31 179 633 272 ;
+C 178 ; WX 602 ; N dagger ; B 143 -16 504 595 ;
+C 179 ; WX 602 ; N daggerdbl ; B 106 -16 515 595 ;
+C 180 ; WX 602 ; N periodcentered ; B 201 210 400 409 ;
+C 182 ; WX 602 ; N paragraph ; B 45 -77 566 616 ;
+C 183 ; WX 602 ; N bullet ; B 222 256 379 413 ;
+C 184 ; WX 602 ; N quotesinglbase ; B 160 -109 393 166 ;
+C 185 ; WX 602 ; N quotedblbase ; B 41 -109 486 166 ;
+C 186 ; WX 602 ; N quotedblright ; B 114 304 559 579 ;
+C 187 ; WX 602 ; N guillemotright ; B 125 38 468 411 ;
+C 188 ; WX 602 ; N ellipsis ; B -11 -12 536 133 ;
+C 189 ; WX 602 ; N perthousand ; B -44 -16 665 633 ;
+C 191 ; WX 602 ; N questiondown ; B 97 -14 484 596 ;
+C 193 ; WX 602 ; N grave ; B 126 499 386 673 ;
+C 194 ; WX 602 ; N acute ; B 276 499 536 673 ;
+C 195 ; WX 602 ; N circumflex ; B 164 509 499 662 ;
+C 196 ; WX 602 ; N tilde ; B 160 532 518 639 ;
+C 197 ; WX 602 ; N macron ; B 166 546 507 614 ;
+C 198 ; WX 602 ; N breve ; B 159 528 527 660 ;
+C 199 ; WX 602 ; N dotaccent ; B 265 523 398 656 ;
+C 200 ; WX 602 ; N dieresis ; B 148 519 516 644 ;
+C 202 ; WX 602 ; N ring ; B 231 499 495 764 ;
+C 203 ; WX 602 ; N cedilla ; B 95 -232 354 0 ;
+C 205 ; WX 602 ; N hungarumlaut ; B 198 503 576 665 ;
+C 206 ; WX 602 ; N ogonek ; B 188 -226 388 14 ;
+C 207 ; WX 602 ; N caron ; B 164 509 499 662 ;
+C 208 ; WX 602 ; N emdash ; B -31 179 633 272 ;
+C 225 ; WX 602 ; N AE ; B -79 0 652 579 ;
+C 227 ; WX 602 ; N ordfeminine ; B 93 260 476 641 ;
+C 232 ; WX 602 ; N Lslash ; B -4 0 543 579 ;
+C 233 ; WX 602 ; N Oslash ; B -33 -35 619 615 ;
+C 234 ; WX 602 ; N OE ; B 4 0 652 579 ;
+C 235 ; WX 602 ; N ordmasculine ; B 90 258 495 640 ;
+C 241 ; WX 602 ; N ae ; B -27 -13 587 464 ;
+C 245 ; WX 602 ; N dotlessi ; B 47 0 483 452 ;
+C 248 ; WX 602 ; N lslash ; B 37 0 518 639 ;
+C 249 ; WX 602 ; N oslash ; B -29 -60 592 490 ;
+C 250 ; WX 602 ; N oe ; B -7 -13 587 464 ;
+C 251 ; WX 602 ; N germandbls ; B -16 -14 527 640 ;
+C -1 ; WX 602 ; N Aacute ; B -44 0 562 805 ;
+C -1 ; WX 602 ; N Acircumflex ; B -44 0 562 794 ;
+C -1 ; WX 602 ; N Adieresis ; B -44 0 562 776 ;
+C -1 ; WX 602 ; N Agrave ; B -44 0 562 805 ;
+C -1 ; WX 602 ; N Aring ; B -44 0 562 875 ;
+C -1 ; WX 602 ; N Atilde ; B -44 0 562 771 ;
+C -1 ; WX 602 ; N Ccedilla ; B 31 -232 600 595 ;
+C -1 ; WX 602 ; N Eacute ; B -8 0 593 805 ;
+C -1 ; WX 602 ; N Ecircumflex ; B -8 0 593 794 ;
+C -1 ; WX 602 ; N Edieresis ; B -8 0 593 776 ;
+C -1 ; WX 602 ; N Egrave ; B -8 0 593 805 ;
+C -1 ; WX 602 ; N Iacute ; B 56 0 561 805 ;
+C -1 ; WX 602 ; N Icircumflex ; B 56 0 544 794 ;
+C -1 ; WX 602 ; N Idieresis ; B 56 0 544 776 ;
+C -1 ; WX 602 ; N Igrave ; B 56 0 544 805 ;
+C -1 ; WX 602 ; N Ntilde ; B -25 -9 625 771 ;
+C -1 ; WX 602 ; N Oacute ; B 16 -15 585 805 ;
+C -1 ; WX 602 ; N Ocircumflex ; B 16 -15 585 794 ;
+C -1 ; WX 602 ; N Odieresis ; B 16 -15 585 776 ;
+C -1 ; WX 602 ; N Ograve ; B 16 -15 585 805 ;
+C -1 ; WX 602 ; N Otilde ; B 16 -15 585 771 ;
+C -1 ; WX 602 ; N Scaron ; B 26 -20 572 794 ;
+C -1 ; WX 602 ; N Uacute ; B 56 -17 628 805 ;
+C -1 ; WX 602 ; N Ucircumflex ; B 56 -17 628 794 ;
+C -1 ; WX 602 ; N Udieresis ; B 56 -17 628 776 ;
+C -1 ; WX 602 ; N Ugrave ; B 56 -17 628 805 ;
+C -1 ; WX 602 ; N Ydieresis ; B 63 0 587 776 ;
+C -1 ; WX 602 ; N Zcaron ; B 44 0 537 794 ;
+C -1 ; WX 602 ; N aacute ; B 35 -11 547 673 ;
+C -1 ; WX 602 ; N acircumflex ; B 35 -11 514 662 ;
+C -1 ; WX 602 ; N adieresis ; B 35 -11 527 644 ;
+C -1 ; WX 602 ; N agrave ; B 35 -11 514 673 ;
+C -1 ; WX 602 ; N aring ; B 35 -11 514 759 ;
+C -1 ; WX 602 ; N atilde ; B 35 -11 529 639 ;
+C -1 ; WX 602 ; N ccedilla ; B 27 -232 557 465 ;
+C -1 ; WX 602 ; N eacute ; B 34 -12 537 673 ;
+C -1 ; WX 602 ; N ecircumflex ; B 34 -12 537 662 ;
+C -1 ; WX 602 ; N edieresis ; B 34 -12 537 644 ;
+C -1 ; WX 602 ; N egrave ; B 34 -12 537 673 ;
+C -1 ; WX 602 ; N iacute ; B 47 0 536 673 ;
+C -1 ; WX 602 ; N icircumflex ; B 47 0 499 662 ;
+C -1 ; WX 602 ; N idieresis ; B 47 0 516 644 ;
+C -1 ; WX 602 ; N igrave ; B 47 0 483 673 ;
+C -1 ; WX 602 ; N ntilde ; B -1 0 520 639 ;
+C -1 ; WX 602 ; N oacute ; B 39 -13 545 673 ;
+C -1 ; WX 602 ; N ocircumflex ; B 39 -13 545 662 ;
+C -1 ; WX 602 ; N odieresis ; B 39 -13 545 644 ;
+C -1 ; WX 602 ; N ograve ; B 39 -13 545 673 ;
+C -1 ; WX 602 ; N otilde ; B 39 -13 545 639 ;
+C -1 ; WX 602 ; N scaron ; B 45 -18 527 662 ;
+C -1 ; WX 602 ; N uacute ; B 57 -12 525 673 ;
+C -1 ; WX 602 ; N ucircumflex ; B 57 -12 523 662 ;
+C -1 ; WX 602 ; N udieresis ; B 57 -12 523 644 ;
+C -1 ; WX 602 ; N ugrave ; B 57 -12 523 673 ;
+C -1 ; WX 602 ; N ydieresis ; B -7 -195 575 644 ;
+C -1 ; WX 602 ; N zcaron ; B 48 0 522 662 ;
+C -1 ; WX 602 ; N trademark ; B 32 329 566 590 ;
+C -1 ; WX 602 ; N copyright ; B 1 32 601 632 ;
+C -1 ; WX 602 ; N logicalnot ; B 34 156 567 459 ;
+C -1 ; WX 602 ; N registered ; B 1 32 601 632 ;
+C -1 ; WX 602 ; N minus ; B 36 257 566 359 ;
+C -1 ; WX 602 ; N Eth ; B -6 0 577 579 ;
+C -1 ; WX 602 ; N Thorn ; B 5 0 543 579 ;
+C -1 ; WX 602 ; N Yacute ; B 63 0 587 805 ;
+C -1 ; WX 602 ; N brokenbar ; B 253 -172 349 699 ;
+C -1 ; WX 602 ; N degree ; B 171 349 486 665 ;
+C -1 ; WX 602 ; N divide ; B 36 86 565 531 ;
+C -1 ; WX 602 ; N eth ; B 38 -13 545 638 ;
+C -1 ; WX 602 ; N mu ; B 25 -195 542 453 ;
+C -1 ; WX 602 ; N multiply ; B 93 110 509 526 ;
+C -1 ; WX 602 ; N onehalf ; B 22 -86 572 680 ;
+C -1 ; WX 602 ; N onequarter ; B 22 -93 570 680 ;
+C -1 ; WX 602 ; N onesuperior ; B 175 241 428 624 ;
+C -1 ; WX 602 ; N plusminus ; B 60 29 541 619 ;
+C -1 ; WX 602 ; N thorn ; B -79 -195 549 639 ;
+C -1 ; WX 602 ; N threequarters ; B 38 -93 570 689 ;
+C -1 ; WX 602 ; N threesuperior ; B 127 222 452 633 ;
+C -1 ; WX 602 ; N twosuperior ; B 145 240 465 632 ;
+C -1 ; WX 602 ; N yacute ; B -7 -195 575 673 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 0
+EndKernPairs
+StartTrackKern 0
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0611bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0611bt_.pfb
new file mode 100644
index 00000000..ec6ed060
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0611bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0632bt_.afm b/e2e-tests/cypress/fonts/Type1/c0632bt_.afm
new file mode 100644
index 00000000..31721010
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0632bt_.afm
@@ -0,0 +1,628 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Transitional 801
+Comment bitsFontID 0632
+Comment bitsManufacturingDate Tue Nov 6 02:14:13 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530632
+FontName CharterBT-Bold
+FullName Bitstream Charter Bold
+FamilyName Bitstream Charter
+Weight Bold
+ItalicAngle 0.00
+IsFixedPitch false
+FontBBox -166 -237 1263 963
+UnderlinePosition -109
+UnderlineThickness 91
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 672
+XHeight 488
+Ascender 740
+Descender -219
+StartCharMetrics 228
+C 32 ; WX 291 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 340 ; N exclam ; B 94 -8 247 685 ;
+C 34 ; WX 339 ; N quotedbl ; B 40 418 299 715 ;
+C 35 ; WX 736 ; N numbersign ; B 56 -24 675 710 ;
+C 36 ; WX 581 ; N dollar ; B 61 -102 533 742 ;
+C 37 ; WX 888 ; N percent ; B 36 -12 863 683 ;
+C 38 ; WX 741 ; N ampersand ; B 52 -12 725 684 ;
+C 39 ; WX 255 ; N quoteright ; B 47 395 220 698 ;
+C 40 ; WX 428 ; N parenleft ; B 90 -142 387 718 ;
+C 41 ; WX 428 ; N parenright ; B 37 -142 330 718 ;
+C 42 ; WX 500 ; N asterisk ; B 53 338 447 718 ;
+C 43 ; WX 833 ; N plus ; B 124 0 710 597 ;
+C 44 ; WX 289 ; N comma ; B 30 -176 221 129 ;
+C 45 ; WX 326 ; N hyphen ; B 36 191 291 291 ;
+C 46 ; WX 289 ; N period ; B 65 -8 224 151 ;
+C 47 ; WX 491 ; N slash ; B -28 -93 472 672 ;
+C 48 ; WX 581 ; N zero ; B 39 -12 549 683 ;
+C 49 ; WX 581 ; N one ; B 108 0 495 681 ;
+C 50 ; WX 581 ; N two ; B 48 0 533 684 ;
+C 51 ; WX 581 ; N three ; B 42 -11 523 682 ;
+C 52 ; WX 581 ; N four ; B 25 -32 566 677 ;
+C 53 ; WX 581 ; N five ; B 54 -10 525 672 ;
+C 54 ; WX 581 ; N six ; B 46 -13 554 714 ;
+C 55 ; WX 581 ; N seven ; B 75 -34 556 672 ;
+C 56 ; WX 581 ; N eight ; B 41 -16 540 685 ;
+C 57 ; WX 581 ; N nine ; B 42 -54 546 683 ;
+C 58 ; WX 340 ; N colon ; B 94 -8 252 489 ;
+C 59 ; WX 340 ; N semicolon ; B 67 -176 255 489 ;
+C 60 ; WX 833 ; N less ; B 128 22 704 574 ;
+C 61 ; WX 833 ; N equal ; B 124 156 710 440 ;
+C 62 ; WX 833 ; N greater ; B 129 22 704 574 ;
+C 63 ; WX 487 ; N question ; B 35 -8 437 684 ;
+C 64 ; WX 917 ; N at ; B 74 -154 854 693 ;
+C 65 ; WX 651 ; N A ; B -12 0 670 678 ;
+C 66 ; WX 628 ; N B ; B 28 0 590 672 ;
+C 67 ; WX 638 ; N C ; B 40 -13 602 683 ;
+C 68 ; WX 716 ; N D ; B 28 0 682 672 ;
+C 69 ; WX 596 ; N E ; B 28 0 566 672 ;
+C 70 ; WX 552 ; N F ; B 25 0 529 672 ;
+C 71 ; WX 710 ; N G ; B 40 -12 691 683 ;
+C 72 ; WX 760 ; N H ; B 30 0 734 672 ;
+C 73 ; WX 354 ; N I ; B 29 0 329 672 ;
+C 74 ; WX 465 ; N J ; B 11 -13 465 672 ;
+C 75 ; WX 650 ; N K ; B 29 0 672 672 ;
+C 76 ; WX 543 ; N L ; B 27 0 533 672 ;
+C 77 ; WX 883 ; N M ; B 24 0 863 672 ;
+C 78 ; WX 727 ; N N ; B 24 0 711 672 ;
+C 79 ; WX 752 ; N O ; B 40 -17 718 687 ;
+C 80 ; WX 587 ; N P ; B 24 0 569 672 ;
+C 81 ; WX 752 ; N Q ; B 39 -179 720 687 ;
+C 82 ; WX 671 ; N R ; B 30 -7 692 672 ;
+C 83 ; WX 568 ; N S ; B 58 -12 517 683 ;
+C 84 ; WX 603 ; N T ; B 15 0 594 672 ;
+C 85 ; WX 705 ; N U ; B 20 -13 695 672 ;
+C 86 ; WX 635 ; N V ; B -21 -3 661 672 ;
+C 87 ; WX 946 ; N W ; B 1 0 945 672 ;
+C 88 ; WX 637 ; N X ; B -1 0 644 672 ;
+C 89 ; WX 610 ; N Y ; B -11 0 627 672 ;
+C 90 ; WX 592 ; N Z ; B 44 0 550 672 ;
+C 91 ; WX 443 ; N bracketleft ; B 135 -133 406 709 ;
+C 92 ; WX 491 ; N backslash ; B -8 -93 486 672 ;
+C 93 ; WX 443 ; N bracketright ; B 42 -133 312 709 ;
+C 94 ; WX 1000 ; N asciicircum ; B 201 437 798 714 ;
+C 95 ; WX 500 ; N underscore ; B 0 -237 500 -152 ;
+C 96 ; WX 255 ; N quoteleft ; B 49 395 222 699 ;
+C 97 ; WX 544 ; N a ; B 40 -10 535 500 ;
+C 98 ; WX 577 ; N b ; B 9 -2 547 740 ;
+C 99 ; WX 476 ; N c ; B 34 -8 464 498 ;
+C 100 ; WX 596 ; N d ; B 36 -10 577 740 ;
+C 101 ; WX 524 ; N e ; B 37 -9 493 501 ;
+C 102 ; WX 341 ; N f ; B 30 0 412 744 ;
+C 103 ; WX 551 ; N g ; B 33 -218 555 498 ;
+C 104 ; WX 597 ; N h ; B 16 0 586 740 ;
+C 105 ; WX 305 ; N i ; B 29 0 293 724 ;
+C 106 ; WX 297 ; N j ; B -80 -215 242 724 ;
+C 107 ; WX 553 ; N k ; B 17 0 572 740 ;
+C 108 ; WX 304 ; N l ; B 22 0 292 740 ;
+C 109 ; WX 892 ; N m ; B 30 0 883 500 ;
+C 110 ; WX 605 ; N n ; B 27 0 594 499 ;
+C 111 ; WX 577 ; N o ; B 36 -9 547 499 ;
+C 112 ; WX 591 ; N p ; B 21 -219 560 500 ;
+C 113 ; WX 575 ; N q ; B 37 -218 572 499 ;
+C 114 ; WX 421 ; N r ; B 24 0 421 498 ;
+C 115 ; WX 447 ; N s ; B 40 -11 411 500 ;
+C 116 ; WX 358 ; N t ; B 18 -5 357 599 ;
+C 117 ; WX 600 ; N u ; B 22 -10 583 499 ;
+C 118 ; WX 513 ; N v ; B -7 0 535 488 ;
+C 119 ; WX 799 ; N w ; B -1 0 811 488 ;
+C 120 ; WX 531 ; N x ; B 11 0 532 488 ;
+C 121 ; WX 515 ; N y ; B -5 -219 537 486 ;
+C 122 ; WX 495 ; N z ; B 45 0 466 486 ;
+C 123 ; WX 493 ; N braceleft ; B 46 -134 421 705 ;
+C 124 ; WX 500 ; N bar ; B 207 -237 294 764 ;
+C 125 ; WX 493 ; N braceright ; B 62 -134 438 705 ;
+C 126 ; WX 833 ; N asciitilde ; B 86 212 747 384 ;
+C 161 ; WX 340 ; N exclamdown ; B 93 -8 246 685 ;
+C 162 ; WX 581 ; N cent ; B 58 -103 504 612 ;
+C 163 ; WX 581 ; N sterling ; B 42 0 540 680 ;
+C 164 ; WX 167 ; N fraction ; B -166 -1 333 672 ;
+C 165 ; WX 595 ; N yen ; B -7 0 604 672 ;
+C 166 ; WX 581 ; N florin ; B 12 -149 535 683 ;
+C 167 ; WX 500 ; N section ; B 45 -142 455 720 ;
+C 168 ; WX 606 ; N currency ; B 36 166 571 699 ;
+C 169 ; WX 175 ; N quotesingle ; B 40 418 135 715 ;
+C 170 ; WX 475 ; N quotedblleft ; B 49 395 443 699 ;
+C 171 ; WX 449 ; N guillemotleft ; B 34 53 404 427 ;
+C 172 ; WX 255 ; N guilsinglleft ; B 34 53 207 427 ;
+C 173 ; WX 255 ; N guilsinglright ; B 37 53 211 427 ;
+C 174 ; WX 622 ; N fi ; B 30 0 605 745 ;
+C 175 ; WX 627 ; N fl ; B 30 0 616 746 ;
+C 177 ; WX 500 ; N endash ; B 0 195 500 286 ;
+C 178 ; WX 500 ; N dagger ; B 17 -130 484 718 ;
+C 179 ; WX 500 ; N daggerdbl ; B 17 -132 484 718 ;
+C 180 ; WX 289 ; N periodcentered ; B 65 256 224 415 ;
+C 182 ; WX 491 ; N paragraph ; B 18 -79 458 672 ;
+C 183 ; WX 590 ; N bullet ; B 150 227 439 516 ;
+C 184 ; WX 255 ; N quotesinglbase ; B 32 -174 205 130 ;
+C 185 ; WX 475 ; N quotedblbase ; B 35 -174 429 130 ;
+C 186 ; WX 475 ; N quotedblright ; B 47 395 441 698 ;
+C 187 ; WX 449 ; N guillemotright ; B 42 53 412 427 ;
+C 188 ; WX 1000 ; N ellipsis ; B 87 -8 914 151 ;
+C 189 ; WX 1287 ; N perthousand ; B 36 -12 1263 683 ;
+C 191 ; WX 487 ; N questiondown ; B 37 -8 440 684 ;
+C 193 ; WX 500 ; N grave ; B 85 551 307 742 ;
+C 194 ; WX 500 ; N acute ; B 205 551 428 742 ;
+C 195 ; WX 500 ; N circumflex ; B 96 551 404 742 ;
+C 196 ; WX 500 ; N tilde ; B 87 570 411 723 ;
+C 197 ; WX 500 ; N macron ; B 85 603 417 676 ;
+C 198 ; WX 500 ; N breve ; B 98 567 403 719 ;
+C 199 ; WX 500 ; N dotaccent ; B 185 578 316 713 ;
+C 200 ; WX 500 ; N dieresis ; B 83 578 417 710 ;
+C 202 ; WX 500 ; N ring ; B 131 546 369 784 ;
+C 203 ; WX 500 ; N cedilla ; B 171 -230 374 0 ;
+C 205 ; WX 500 ; N hungarumlaut ; B 107 551 490 742 ;
+C 206 ; WX 500 ; N ogonek ; B 176 -225 336 0 ;
+C 207 ; WX 500 ; N caron ; B 96 551 404 742 ;
+C 208 ; WX 1000 ; N emdash ; B 0 195 1000 286 ;
+C 225 ; WX 890 ; N AE ; B -59 0 863 672 ;
+C 227 ; WX 408 ; N ordfeminine ; B 30 323 402 681 ;
+C 232 ; WX 543 ; N Lslash ; B 9 0 533 672 ;
+C 233 ; WX 752 ; N Oslash ; B 40 -83 718 754 ;
+C 234 ; WX 1010 ; N OE ; B 39 -11 980 682 ;
+C 235 ; WX 433 ; N ordmasculine ; B 27 324 411 680 ;
+C 241 ; WX 768 ; N ae ; B 40 -10 735 500 ;
+C 245 ; WX 305 ; N dotlessi ; B 29 0 293 497 ;
+C 248 ; WX 304 ; N lslash ; B -3 0 328 740 ;
+C 249 ; WX 577 ; N oslash ; B 37 -83 548 571 ;
+C 250 ; WX 861 ; N oe ; B 37 -9 827 500 ;
+C 251 ; WX 642 ; N germandbls ; B 18 -9 622 743 ;
+C -1 ; WX 651 ; N Aacute ; B -12 0 670 930 ;
+C -1 ; WX 651 ; N Acircumflex ; B -12 0 670 930 ;
+C -1 ; WX 651 ; N Adieresis ; B -12 0 670 898 ;
+C -1 ; WX 651 ; N Agrave ; B -12 0 670 930 ;
+C -1 ; WX 651 ; N Aring ; B -12 0 670 963 ;
+C -1 ; WX 651 ; N Atilde ; B -12 0 670 911 ;
+C -1 ; WX 638 ; N Ccedilla ; B 40 -230 602 683 ;
+C -1 ; WX 596 ; N Eacute ; B 28 0 566 930 ;
+C -1 ; WX 596 ; N Ecircumflex ; B 28 0 566 930 ;
+C -1 ; WX 596 ; N Edieresis ; B 28 0 566 898 ;
+C -1 ; WX 596 ; N Egrave ; B 28 0 566 930 ;
+C -1 ; WX 354 ; N Iacute ; B 29 0 355 930 ;
+C -1 ; WX 354 ; N Icircumflex ; B 23 0 331 930 ;
+C -1 ; WX 354 ; N Idieresis ; B 10 0 344 898 ;
+C -1 ; WX 354 ; N Igrave ; B 12 0 329 930 ;
+C -1 ; WX 727 ; N Ntilde ; B 24 0 711 911 ;
+C -1 ; WX 752 ; N Oacute ; B 40 -17 718 930 ;
+C -1 ; WX 752 ; N Ocircumflex ; B 40 -17 718 930 ;
+C -1 ; WX 752 ; N Odieresis ; B 40 -17 718 898 ;
+C -1 ; WX 752 ; N Ograve ; B 40 -17 718 930 ;
+C -1 ; WX 752 ; N Otilde ; B 40 -17 718 911 ;
+C -1 ; WX 568 ; N Scaron ; B 58 -12 517 930 ;
+C -1 ; WX 705 ; N Uacute ; B 20 -13 695 930 ;
+C -1 ; WX 705 ; N Ucircumflex ; B 20 -13 695 930 ;
+C -1 ; WX 705 ; N Udieresis ; B 20 -13 695 898 ;
+C -1 ; WX 705 ; N Ugrave ; B 20 -13 695 930 ;
+C -1 ; WX 610 ; N Ydieresis ; B -11 0 627 898 ;
+C -1 ; WX 592 ; N Zcaron ; B 44 0 550 930 ;
+C -1 ; WX 544 ; N aacute ; B 40 -10 535 742 ;
+C -1 ; WX 544 ; N acircumflex ; B 40 -10 535 742 ;
+C -1 ; WX 544 ; N adieresis ; B 40 -10 535 710 ;
+C -1 ; WX 544 ; N agrave ; B 40 -10 535 742 ;
+C -1 ; WX 544 ; N aring ; B 40 -10 535 784 ;
+C -1 ; WX 544 ; N atilde ; B 40 -10 535 723 ;
+C -1 ; WX 476 ; N ccedilla ; B 34 -230 464 498 ;
+C -1 ; WX 524 ; N eacute ; B 37 -9 493 742 ;
+C -1 ; WX 524 ; N ecircumflex ; B 37 -9 493 742 ;
+C -1 ; WX 524 ; N edieresis ; B 37 -9 493 710 ;
+C -1 ; WX 524 ; N egrave ; B 37 -9 493 742 ;
+C -1 ; WX 305 ; N iacute ; B 29 0 331 742 ;
+C -1 ; WX 305 ; N icircumflex ; B -2 0 307 742 ;
+C -1 ; WX 305 ; N idieresis ; B -15 0 320 710 ;
+C -1 ; WX 305 ; N igrave ; B -13 0 293 742 ;
+C -1 ; WX 605 ; N ntilde ; B 27 0 594 723 ;
+C -1 ; WX 577 ; N oacute ; B 36 -9 547 742 ;
+C -1 ; WX 577 ; N ocircumflex ; B 36 -9 547 742 ;
+C -1 ; WX 577 ; N odieresis ; B 36 -9 547 710 ;
+C -1 ; WX 577 ; N ograve ; B 36 -9 547 742 ;
+C -1 ; WX 577 ; N otilde ; B 36 -9 547 723 ;
+C -1 ; WX 447 ; N scaron ; B 40 -11 411 742 ;
+C -1 ; WX 600 ; N uacute ; B 22 -10 583 742 ;
+C -1 ; WX 600 ; N ucircumflex ; B 22 -10 583 742 ;
+C -1 ; WX 600 ; N udieresis ; B 22 -10 583 710 ;
+C -1 ; WX 600 ; N ugrave ; B 22 -10 583 742 ;
+C -1 ; WX 515 ; N ydieresis ; B -5 -219 537 710 ;
+C -1 ; WX 495 ; N zcaron ; B 45 0 466 742 ;
+C -1 ; WX 800 ; N trademark ; B 111 398 710 662 ;
+C -1 ; WX 876 ; N copyright ; B 61 -50 825 730 ;
+C -1 ; WX 833 ; N logicalnot ; B 124 175 710 421 ;
+C -1 ; WX 876 ; N registered ; B 61 -50 825 730 ;
+C -1 ; WX 833 ; N minus ; B 124 256 710 340 ;
+C -1 ; WX 716 ; N Eth ; B 15 0 682 672 ;
+C -1 ; WX 587 ; N Thorn ; B 32 0 572 672 ;
+C -1 ; WX 610 ; N Yacute ; B -11 0 627 930 ;
+C -1 ; WX 500 ; N brokenbar ; B 207 -172 294 699 ;
+C -1 ; WX 329 ; N degree ; B 20 424 309 713 ;
+C -1 ; WX 833 ; N divide ; B 124 45 710 551 ;
+C -1 ; WX 569 ; N eth ; B 34 -10 540 744 ;
+C -1 ; WX 578 ; N mu ; B -53 -206 546 433 ;
+C -1 ; WX 833 ; N multiply ; B 139 16 704 581 ;
+C -1 ; WX 899 ; N onehalf ; B 68 -1 869 677 ;
+C -1 ; WX 899 ; N onequarter ; B 68 -18 890 677 ;
+C -1 ; WX 383 ; N onesuperior ; B 71 268 327 677 ;
+C -1 ; WX 833 ; N plusminus ; B 124 7 710 590 ;
+C -1 ; WX 591 ; N thorn ; B 20 -219 562 740 ;
+C -1 ; WX 899 ; N threequarters ; B 26 -18 890 678 ;
+C -1 ; WX 383 ; N threesuperior ; B 27 261 346 678 ;
+C -1 ; WX 383 ; N twosuperior ; B 31 268 352 679 ;
+C -1 ; WX 515 ; N yacute ; B -5 -219 537 742 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 361
+KPX hyphen T -37
+KPX hyphen V -56
+KPX hyphen W -56
+KPX hyphen X -37
+KPX hyphen Y -74
+KPX A quoteright -130
+KPX A T -111
+KPX A U -23
+KPX A V -56
+KPX A W -42
+KPX A Y -42
+KPX A f -19
+KPX A t -19
+KPX A v -32
+KPX A w -46
+KPX A y -23
+KPX A fi -19
+KPX A fl -19
+KPX A quotedblright -130
+KPX B hyphen 37
+KPX B C 19
+KPX B G 19
+KPX B O 19
+KPX B S 19
+KPX B V -37
+KPX B W -19
+KPX B Y -19
+KPX B Oslash 19
+KPX B OE 19
+KPX C quoteright 37
+KPX C hyphen 23
+KPX C A -19
+KPX C S 19
+KPX C quotedblright 37
+KPX C Aring -19
+KPX D hyphen 37
+KPX D A -19
+KPX D V -19
+KPX D Y -19
+KPX D Aring -19
+KPX F comma -190
+KPX F hyphen -74
+KPX F period -190
+KPX F colon -37
+KPX F semicolon -37
+KPX F A -97
+KPX F a -79
+KPX F e -65
+KPX F i -19
+KPX F o -46
+KPX F r -37
+KPX F u -37
+KPX F y -37
+KPX F quotesinglbase -56
+KPX F quotedblbase -56
+KPX F ae -79
+KPX F oslash -46
+KPX F oe -46
+KPX F Aring -97
+KPX G hyphen 19
+KPX G T -19
+KPX G W -19
+KPX G Y -23
+KPX J A -37
+KPX J Aring -37
+KPX K hyphen -37
+KPX K A -23
+KPX K C -28
+KPX K O -28
+KPX K U -37
+KPX K W -37
+KPX K Y -28
+KPX K a 19
+KPX K e -37
+KPX K o -37
+KPX K u -19
+KPX K y -102
+KPX K quotesinglbase 37
+KPX K quotedblbase 37
+KPX K Oslash -28
+KPX K OE -28
+KPX K ae 19
+KPX K oslash -37
+KPX K oe -37
+KPX K Aring -23
+KPX L quoteright -167
+KPX L T -83
+KPX L U -19
+KPX L V -120
+KPX L W -88
+KPX L Y -102
+KPX L quoteleft -74
+KPX L a 19
+KPX L e 19
+KPX L o 19
+KPX L y -56
+KPX L quotedblleft -74
+KPX L quotesinglbase 19
+KPX L quotedblbase 19
+KPX L quotedblright -167
+KPX L ae 19
+KPX L oslash 19
+KPX L oe 19
+KPX O comma -60
+KPX O hyphen 37
+KPX O period -60
+KPX O V -19
+KPX O X -19
+KPX P comma -259
+KPX P hyphen -93
+KPX P period -259
+KPX P colon -37
+KPX P semicolon -37
+KPX P A -93
+KPX P U -19
+KPX P a -37
+KPX P e -37
+KPX P o -32
+KPX P quotesinglbase -93
+KPX P quotedblbase -93
+KPX P ae -37
+KPX P oslash -32
+KPX P oe -32
+KPX P Aring -93
+KPX Q quoteright 19
+KPX Q hyphen 37
+KPX Q quotedblright 19
+KPX R quoteright -37
+KPX R colon -19
+KPX R semicolon -19
+KPX R T -37
+KPX R V -56
+KPX R W -42
+KPX R Y -51
+KPX R quoteleft -37
+KPX R e -37
+KPX R o -37
+KPX R u -37
+KPX R y -46
+KPX R quotedblleft -37
+KPX R quotesinglbase 37
+KPX R quotedblbase 37
+KPX R quotedblright -37
+KPX R oslash -37
+KPX R oe -37
+KPX T quoteright 19
+KPX T comma -148
+KPX T hyphen -130
+KPX T period -148
+KPX T colon -37
+KPX T semicolon -37
+KPX T A -111
+KPX T T 19
+KPX T quoteleft 37
+KPX T a -97
+KPX T c -97
+KPX T e -97
+KPX T i -19
+KPX T o -97
+KPX T r -74
+KPX T s -74
+KPX T u -111
+KPX T w -74
+KPX T y -93
+KPX T quotedblleft 37
+KPX T guillemotleft -37
+KPX T guilsinglleft -37
+KPX T quotedblright 19
+KPX T ae -97
+KPX T oslash -97
+KPX T oe -97
+KPX T Aring -111
+KPX U A -32
+KPX U J -28
+KPX U Aring -32
+KPX V quoteright 37
+KPX V comma -222
+KPX V hyphen -93
+KPX V period -222
+KPX V colon -102
+KPX V semicolon -102
+KPX V A -79
+KPX V O -19
+KPX V a -111
+KPX V e -106
+KPX V i -28
+KPX V o -93
+KPX V u -65
+KPX V y -65
+KPX V quotesinglbase -74
+KPX V quotedblbase -74
+KPX V quotedblright 37
+KPX V Oslash -19
+KPX V OE -19
+KPX V ae -111
+KPX V oslash -93
+KPX V oe -93
+KPX V Aring -79
+KPX W quoteright 19
+KPX W comma -176
+KPX W hyphen -74
+KPX W period -176
+KPX W colon -88
+KPX W semicolon -88
+KPX W A -60
+KPX W a -88
+KPX W e -83
+KPX W i -37
+KPX W o -88
+KPX W r -65
+KPX W u -60
+KPX W y -42
+KPX W quotesinglbase -37
+KPX W quotedblbase -37
+KPX W quotedblright 19
+KPX W ae -88
+KPX W oslash -88
+KPX W oe -88
+KPX W Aring -60
+KPX X hyphen -37
+KPX X A -19
+KPX X C -19
+KPX X O -19
+KPX X e -37
+KPX X quotesinglbase 19
+KPX X quotedblbase 19
+KPX X Oslash -19
+KPX X OE -19
+KPX X Aring -19
+KPX Y quoteright 28
+KPX Y comma -130
+KPX Y hyphen -130
+KPX Y period -130
+KPX Y colon -125
+KPX Y semicolon -125
+KPX Y A -60
+KPX Y C -19
+KPX Y a -116
+KPX Y e -125
+KPX Y i -37
+KPX Y o -116
+KPX Y u -88
+KPX Y guillemotleft -56
+KPX Y guilsinglleft -56
+KPX Y quotesinglbase -37
+KPX Y quotedblbase -37
+KPX Y quotedblright 28
+KPX Y ae -116
+KPX Y oslash -116
+KPX Y oe -116
+KPX Y Aring -60
+KPX quoteleft A -130
+KPX quoteleft J -148
+KPX quoteleft V 56
+KPX quoteleft W 37
+KPX quoteleft X 37
+KPX quoteleft Y 37
+KPX quoteleft v 28
+KPX quoteleft w 19
+KPX quoteleft y 19
+KPX quoteleft AE -111
+KPX quoteleft Aring -130
+KPX f quoteright 74
+KPX f comma -37
+KPX f hyphen -19
+KPX f period -37
+KPX f quoteleft 37
+KPX f quotedblleft 37
+KPX f quotedblright 74
+KPX r comma -111
+KPX r period -111
+KPX r quotesinglbase -37
+KPX r quotedblbase -37
+KPX v quoteright 28
+KPX v comma -120
+KPX v period -120
+KPX v quotedblright 28
+KPX w quoteright 19
+KPX w comma -120
+KPX w period -120
+KPX w quoteleft 19
+KPX w quotedblleft 19
+KPX w quotesinglbase -37
+KPX w quotedblbase -37
+KPX w quotedblright 19
+KPX x e -19
+KPX x o -19
+KPX x oslash -19
+KPX x oe -19
+KPX y comma -134
+KPX y period -134
+KPX y quoteleft 19
+KPX y quotedblleft 19
+KPX quotedblleft A -130
+KPX quotedblleft J -148
+KPX quotedblleft V 56
+KPX quotedblleft W 37
+KPX quotedblleft X 37
+KPX quotedblleft Y 37
+KPX quotedblleft v 28
+KPX quotedblleft w 19
+KPX quotedblleft y 19
+KPX quotedblleft AE -111
+KPX quotedblleft Aring -130
+KPX guilsinglright Y -56
+KPX quotesinglbase A 19
+KPX quotesinglbase V -37
+KPX quotesinglbase X 19
+KPX quotesinglbase Y -19
+KPX quotesinglbase AE 74
+KPX quotesinglbase Aring 19
+KPX quotedblbase A 19
+KPX quotedblbase V -37
+KPX quotedblbase X 19
+KPX quotedblbase Y -19
+KPX quotedblbase AE 74
+KPX quotedblbase Aring 19
+KPX guillemotright Y -56
+KPX AE hyphen 19
+KPX Lslash quoteright -167
+KPX Lslash T -83
+KPX Lslash U -19
+KPX Lslash V -120
+KPX Lslash W -88
+KPX Lslash Y -102
+KPX Lslash quoteleft -74
+KPX Lslash a 19
+KPX Lslash e 19
+KPX Lslash o 19
+KPX Lslash y -56
+KPX Lslash quotedblleft -74
+KPX Lslash quotesinglbase 19
+KPX Lslash quotedblbase 19
+KPX Lslash quotedblright -167
+KPX Lslash ae 19
+KPX Lslash oslash 19
+KPX Lslash oe 19
+KPX Oslash comma -60
+KPX Oslash hyphen 37
+KPX Oslash period -60
+KPX Oslash V -19
+KPX Oslash X -19
+KPX Aring quoteright -130
+KPX Aring T -111
+KPX Aring U -23
+KPX Aring V -56
+KPX Aring W -42
+KPX Aring Y -42
+KPX Aring f -19
+KPX Aring t -19
+KPX Aring v -32
+KPX Aring w -46
+KPX Aring y -23
+KPX Aring fi -19
+KPX Aring fl -19
+KPX Aring quotedblright -130
+KPX Eth hyphen 37
+KPX Eth A -19
+KPX Eth V -19
+KPX Eth Y -19
+KPX Eth Aring -19
+EndKernPairs
+StartTrackKern 3
+TrackKern -1 6 0.10 144 -2.09
+TrackKern -2 6 0.05 144 -4.02
+TrackKern -3 6 0.00 144 -5.96
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0632bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0632bt_.pfb
new file mode 100644
index 00000000..07011728
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0632bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0633bt_.afm b/e2e-tests/cypress/fonts/Type1/c0633bt_.afm
new file mode 100644
index 00000000..d16eb097
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0633bt_.afm
@@ -0,0 +1,645 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Transitional 801
+Comment bitsFontID 0633
+Comment bitsManufacturingDate Tue Nov 6 02:16:48 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530633
+FontName CharterBT-BoldItalic
+FullName Bitstream Charter Bold Italic
+FamilyName Bitstream Charter
+Weight Bold
+ItalicAngle 11.0000
+IsFixedPitch false
+FontBBox -190 -237 1243 972
+UnderlinePosition -109
+UnderlineThickness 91
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 672
+XHeight 495
+Ascender 736
+Descender -218
+StartCharMetrics 228
+C 32 ; WX 293 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 340 ; N exclam ; B 43 -8 295 685 ;
+C 34 ; WX 339 ; N quotedbl ; B 40 418 299 715 ;
+C 35 ; WX 751 ; N numbersign ; B 56 -24 689 710 ;
+C 36 ; WX 586 ; N dollar ; B 24 -105 544 745 ;
+C 37 ; WX 898 ; N percent ; B 54 -12 846 683 ;
+C 38 ; WX 730 ; N ampersand ; B 19 -13 698 685 ;
+C 39 ; WX 261 ; N quoteright ; B 63 395 269 699 ;
+C 40 ; WX 420 ; N parenleft ; B 66 -142 459 718 ;
+C 41 ; WX 420 ; N parenright ; B -62 -142 331 718 ;
+C 42 ; WX 500 ; N asterisk ; B 96 338 490 718 ;
+C 43 ; WX 833 ; N plus ; B 124 0 710 597 ;
+C 44 ; WX 292 ; N comma ; B -57 -176 168 130 ;
+C 45 ; WX 320 ; N hyphen ; B 9 191 282 291 ;
+C 46 ; WX 294 ; N period ; B 22 -6 175 149 ;
+C 47 ; WX 481 ; N slash ; B -119 -93 525 672 ;
+C 48 ; WX 586 ; N zero ; B 29 -13 556 684 ;
+C 49 ; WX 586 ; N one ; B 79 0 438 681 ;
+C 50 ; WX 586 ; N two ; B -4 0 531 683 ;
+C 51 ; WX 586 ; N three ; B 11 -11 540 683 ;
+C 52 ; WX 586 ; N four ; B -2 -33 551 677 ;
+C 53 ; WX 586 ; N five ; B 19 -10 552 672 ;
+C 54 ; WX 586 ; N six ; B 31 -12 532 719 ;
+C 55 ; WX 586 ; N seven ; B 74 -34 612 672 ;
+C 56 ; WX 586 ; N eight ; B 24 -18 549 686 ;
+C 57 ; WX 586 ; N nine ; B 47 -58 558 684 ;
+C 58 ; WX 346 ; N colon ; B 49 -6 263 487 ;
+C 59 ; WX 346 ; N semicolon ; B -25 -176 263 487 ;
+C 60 ; WX 833 ; N less ; B 128 22 704 574 ;
+C 61 ; WX 833 ; N equal ; B 124 156 710 440 ;
+C 62 ; WX 833 ; N greater ; B 129 22 704 574 ;
+C 63 ; WX 492 ; N question ; B 74 -8 471 684 ;
+C 64 ; WX 936 ; N at ; B 76 -154 871 693 ;
+C 65 ; WX 634 ; N A ; B -68 0 613 678 ;
+C 66 ; WX 628 ; N B ; B -24 0 580 672 ;
+C 67 ; WX 625 ; N C ; B 36 -13 637 684 ;
+C 68 ; WX 702 ; N D ; B -26 0 661 672 ;
+C 69 ; WX 581 ; N E ; B -22 0 580 672 ;
+C 70 ; WX 539 ; N F ; B -23 0 570 671 ;
+C 71 ; WX 693 ; N G ; B 38 -12 671 685 ;
+C 72 ; WX 747 ; N H ; B -25 0 768 672 ;
+C 73 ; WX 353 ; N I ; B -21 0 370 672 ;
+C 74 ; WX 474 ; N J ; B -46 -14 497 672 ;
+C 75 ; WX 653 ; N K ; B -26 -7 695 672 ;
+C 76 ; WX 529 ; N L ; B -26 0 489 672 ;
+C 77 ; WX 894 ; N M ; B -25 0 913 672 ;
+C 78 ; WX 712 ; N N ; B -27 0 744 672 ;
+C 79 ; WX 729 ; N O ; B 37 -14 690 684 ;
+C 80 ; WX 581 ; N P ; B -24 0 583 672 ;
+C 81 ; WX 729 ; N Q ; B 36 -165 705 684 ;
+C 82 ; WX 645 ; N R ; B -22 -7 632 671 ;
+C 83 ; WX 553 ; N S ; B 23 -8 509 684 ;
+C 84 ; WX 584 ; N T ; B 37 0 628 672 ;
+C 85 ; WX 701 ; N U ; B 71 -13 735 672 ;
+C 86 ; WX 617 ; N V ; B 26 -3 677 672 ;
+C 87 ; WX 921 ; N W ; B 43 0 963 672 ;
+C 88 ; WX 608 ; N X ; B -66 0 658 672 ;
+C 89 ; WX 586 ; N Y ; B 26 0 656 672 ;
+C 90 ; WX 572 ; N Z ; B -18 0 581 672 ;
+C 91 ; WX 449 ; N bracketleft ; B 51 -133 472 709 ;
+C 92 ; WX 481 ; N backslash ; B 21 -93 505 672 ;
+C 93 ; WX 449 ; N bracketright ; B -43 -133 379 709 ;
+C 94 ; WX 1000 ; N asciicircum ; B 201 437 798 714 ;
+C 95 ; WX 500 ; N underscore ; B 0 -237 500 -152 ;
+C 96 ; WX 261 ; N quoteleft ; B 76 395 282 699 ;
+C 97 ; WX 572 ; N a ; B 18 -9 548 494 ;
+C 98 ; WX 556 ; N b ; B 26 -9 508 736 ;
+C 99 ; WX 437 ; N c ; B 15 -11 410 493 ;
+C 100 ; WX 579 ; N d ; B 21 -9 558 736 ;
+C 101 ; WX 464 ; N e ; B 18 -10 431 491 ;
+C 102 ; WX 325 ; N f ; B -155 -214 447 733 ;
+C 103 ; WX 517 ; N g ; B -31 -218 528 492 ;
+C 104 ; WX 595 ; N h ; B 20 -7 561 736 ;
+C 105 ; WX 318 ; N i ; B 28 -7 294 725 ;
+C 106 ; WX 297 ; N j ; B -146 -215 285 724 ;
+C 107 ; WX 559 ; N k ; B 22 -8 544 736 ;
+C 108 ; WX 307 ; N l ; B 34 -9 280 736 ;
+C 109 ; WX 883 ; N m ; B 32 -7 852 494 ;
+C 110 ; WX 600 ; N n ; B 26 -7 569 494 ;
+C 111 ; WX 550 ; N o ; B 18 -11 501 493 ;
+C 112 ; WX 565 ; N p ; B -64 -218 519 494 ;
+C 113 ; WX 562 ; N q ; B 23 -218 516 496 ;
+C 114 ; WX 449 ; N r ; B 26 0 451 494 ;
+C 115 ; WX 403 ; N s ; B -12 -10 363 494 ;
+C 116 ; WX 366 ; N t ; B 39 -8 370 601 ;
+C 117 ; WX 599 ; N u ; B 28 -10 572 489 ;
+C 118 ; WX 492 ; N v ; B -8 -1 472 495 ;
+C 119 ; WX 768 ; N w ; B -2 0 741 495 ;
+C 120 ; WX 510 ; N x ; B -38 -7 512 495 ;
+C 121 ; WX 494 ; N y ; B -79 -216 514 494 ;
+C 122 ; WX 465 ; N z ; B -20 -14 461 503 ;
+C 123 ; WX 487 ; N braceleft ; B 47 -134 430 705 ;
+C 124 ; WX 500 ; N bar ; B 207 -237 294 764 ;
+C 125 ; WX 487 ; N braceright ; B 58 -134 441 705 ;
+C 126 ; WX 833 ; N asciitilde ; B 86 212 747 384 ;
+C 161 ; WX 340 ; N exclamdown ; B 42 -8 294 685 ;
+C 162 ; WX 586 ; N cent ; B 45 -104 535 610 ;
+C 163 ; WX 586 ; N sterling ; B -12 0 562 677 ;
+C 164 ; WX 167 ; N fraction ; B -190 0 353 672 ;
+C 165 ; WX 601 ; N yen ; B 9 0 652 668 ;
+C 166 ; WX 586 ; N florin ; B -63 -149 586 683 ;
+C 167 ; WX 500 ; N section ; B -11 -142 490 720 ;
+C 168 ; WX 606 ; N currency ; B 36 166 571 699 ;
+C 169 ; WX 175 ; N quotesingle ; B 40 418 135 715 ;
+C 170 ; WX 481 ; N quotedblleft ; B 77 395 504 699 ;
+C 171 ; WX 450 ; N guillemotleft ; B 10 54 429 425 ;
+C 172 ; WX 266 ; N guilsinglleft ; B 10 54 228 425 ;
+C 173 ; WX 266 ; N guilsinglright ; B -13 54 205 425 ;
+C 174 ; WX 621 ; N fi ; B -154 -214 600 735 ;
+C 175 ; WX 629 ; N fl ; B -154 -214 603 736 ;
+C 177 ; WX 500 ; N endash ; B -26 195 498 286 ;
+C 178 ; WX 500 ; N dagger ; B 44 -130 511 718 ;
+C 179 ; WX 500 ; N daggerdbl ; B -25 -132 511 718 ;
+C 180 ; WX 292 ; N periodcentered ; B 70 258 223 413 ;
+C 182 ; WX 492 ; N paragraph ; B 18 -79 467 672 ;
+C 183 ; WX 590 ; N bullet ; B 150 227 439 516 ;
+C 184 ; WX 261 ; N quotesinglbase ; B -54 -174 151 130 ;
+C 185 ; WX 481 ; N quotedblbase ; B -51 -174 375 130 ;
+C 186 ; WX 481 ; N quotedblright ; B 64 395 490 699 ;
+C 187 ; WX 450 ; N guillemotright ; B -13 54 406 425 ;
+C 188 ; WX 1000 ; N ellipsis ; B 41 -6 862 149 ;
+C 189 ; WX 1291 ; N perthousand ; B 54 -12 1243 683 ;
+C 191 ; WX 492 ; N questiondown ; B 6 -8 400 684 ;
+C 193 ; WX 500 ; N grave ; B 164 551 349 742 ;
+C 194 ; WX 500 ; N acute ; B 247 551 506 742 ;
+C 195 ; WX 500 ; N circumflex ; B 137 551 446 742 ;
+C 196 ; WX 500 ; N tilde ; B 132 570 486 723 ;
+C 197 ; WX 500 ; N macron ; B 137 607 482 675 ;
+C 198 ; WX 500 ; N breve ; B 167 567 477 719 ;
+C 199 ; WX 500 ; N dotaccent ; B 237 576 370 713 ;
+C 200 ; WX 500 ; N dieresis ; B 137 578 472 711 ;
+C 202 ; WX 500 ; N ring ; B 199 551 428 780 ;
+C 203 ; WX 500 ; N cedilla ; B 58 -230 279 0 ;
+C 205 ; WX 500 ; N hungarumlaut ; B 150 551 578 742 ;
+C 206 ; WX 500 ; N ogonek ; B 83 -225 243 0 ;
+C 207 ; WX 500 ; N caron ; B 174 551 483 742 ;
+C 208 ; WX 1000 ; N emdash ; B -22 195 1000 286 ;
+C 225 ; WX 894 ; N AE ; B -103 0 896 672 ;
+C 227 ; WX 429 ; N ordfeminine ; B 13 318 411 671 ;
+C 232 ; WX 529 ; N Lslash ; B -26 0 489 672 ;
+C 233 ; WX 729 ; N Oslash ; B 37 -82 691 751 ;
+C 234 ; WX 1003 ; N OE ; B 37 -13 999 684 ;
+C 235 ; WX 413 ; N ordmasculine ; B 13 317 376 671 ;
+C 241 ; WX 719 ; N ae ; B 4 -9 684 494 ;
+C 245 ; WX 318 ; N dotlessi ; B 28 -7 294 494 ;
+C 248 ; WX 307 ; N lslash ; B -17 -9 331 736 ;
+C 249 ; WX 550 ; N oslash ; B 19 -87 501 568 ;
+C 250 ; WX 795 ; N oe ; B 20 -10 759 494 ;
+C 251 ; WX 622 ; N germandbls ; B -161 -214 572 738 ;
+C -1 ; WX 634 ; N Aacute ; B -68 0 613 928 ;
+C -1 ; WX 634 ; N Acircumflex ; B -68 0 613 928 ;
+C -1 ; WX 634 ; N Adieresis ; B -68 0 613 897 ;
+C -1 ; WX 634 ; N Agrave ; B -68 0 613 928 ;
+C -1 ; WX 634 ; N Aring ; B -68 0 613 972 ;
+C -1 ; WX 634 ; N Atilde ; B -68 0 613 909 ;
+C -1 ; WX 625 ; N Ccedilla ; B 36 -230 637 684 ;
+C -1 ; WX 581 ; N Eacute ; B -22 0 580 928 ;
+C -1 ; WX 581 ; N Ecircumflex ; B -22 0 580 928 ;
+C -1 ; WX 581 ; N Edieresis ; B -22 0 580 897 ;
+C -1 ; WX 581 ; N Egrave ; B -22 0 580 928 ;
+C -1 ; WX 353 ; N Iacute ; B -21 0 448 928 ;
+C -1 ; WX 353 ; N Icircumflex ; B -21 0 388 928 ;
+C -1 ; WX 353 ; N Idieresis ; B -21 0 414 897 ;
+C -1 ; WX 353 ; N Igrave ; B -21 0 370 928 ;
+C -1 ; WX 712 ; N Ntilde ; B -27 0 744 909 ;
+C -1 ; WX 729 ; N Oacute ; B 37 -14 690 928 ;
+C -1 ; WX 729 ; N Ocircumflex ; B 37 -14 690 928 ;
+C -1 ; WX 729 ; N Odieresis ; B 37 -14 690 897 ;
+C -1 ; WX 729 ; N Ograve ; B 37 -14 690 928 ;
+C -1 ; WX 729 ; N Otilde ; B 37 -14 690 909 ;
+C -1 ; WX 553 ; N Scaron ; B 23 -8 525 928 ;
+C -1 ; WX 701 ; N Uacute ; B 71 -13 735 928 ;
+C -1 ; WX 701 ; N Ucircumflex ; B 71 -13 735 928 ;
+C -1 ; WX 701 ; N Udieresis ; B 71 -13 735 897 ;
+C -1 ; WX 701 ; N Ugrave ; B 71 -13 735 928 ;
+C -1 ; WX 586 ; N Ydieresis ; B 26 0 656 897 ;
+C -1 ; WX 572 ; N Zcaron ; B -18 0 581 928 ;
+C -1 ; WX 572 ; N aacute ; B 18 -9 548 742 ;
+C -1 ; WX 572 ; N acircumflex ; B 18 -9 548 742 ;
+C -1 ; WX 572 ; N adieresis ; B 18 -9 548 711 ;
+C -1 ; WX 572 ; N agrave ; B 18 -9 548 742 ;
+C -1 ; WX 572 ; N aring ; B 18 -9 548 774 ;
+C -1 ; WX 572 ; N atilde ; B 18 -9 548 723 ;
+C -1 ; WX 437 ; N ccedilla ; B 15 -230 410 493 ;
+C -1 ; WX 464 ; N eacute ; B 18 -10 488 742 ;
+C -1 ; WX 464 ; N ecircumflex ; B 18 -10 431 742 ;
+C -1 ; WX 464 ; N edieresis ; B 18 -10 454 711 ;
+C -1 ; WX 464 ; N egrave ; B 18 -10 431 742 ;
+C -1 ; WX 318 ; N iacute ; B 28 -7 398 742 ;
+C -1 ; WX 318 ; N icircumflex ; B 28 -7 338 742 ;
+C -1 ; WX 318 ; N idieresis ; B 28 -7 364 711 ;
+C -1 ; WX 318 ; N igrave ; B 28 -7 294 742 ;
+C -1 ; WX 600 ; N ntilde ; B 26 -7 569 723 ;
+C -1 ; WX 550 ; N oacute ; B 18 -11 531 742 ;
+C -1 ; WX 550 ; N ocircumflex ; B 18 -11 501 742 ;
+C -1 ; WX 550 ; N odieresis ; B 18 -11 501 711 ;
+C -1 ; WX 550 ; N ograve ; B 18 -11 501 742 ;
+C -1 ; WX 550 ; N otilde ; B 18 -11 511 723 ;
+C -1 ; WX 403 ; N scaron ; B -12 -10 435 742 ;
+C -1 ; WX 599 ; N uacute ; B 28 -10 572 742 ;
+C -1 ; WX 599 ; N ucircumflex ; B 28 -10 572 742 ;
+C -1 ; WX 599 ; N udieresis ; B 28 -10 572 711 ;
+C -1 ; WX 599 ; N ugrave ; B 28 -10 572 742 ;
+C -1 ; WX 494 ; N ydieresis ; B -79 -216 514 711 ;
+C -1 ; WX 465 ; N zcaron ; B -20 -14 466 742 ;
+C -1 ; WX 817 ; N trademark ; B 113 398 724 663 ;
+C -1 ; WX 894 ; N copyright ; B 62 -50 842 730 ;
+C -1 ; WX 833 ; N logicalnot ; B 124 175 710 421 ;
+C -1 ; WX 894 ; N registered ; B 62 -50 842 730 ;
+C -1 ; WX 833 ; N minus ; B 124 256 710 340 ;
+C -1 ; WX 702 ; N Eth ; B -25 0 661 672 ;
+C -1 ; WX 576 ; N Thorn ; B -26 0 553 666 ;
+C -1 ; WX 586 ; N Yacute ; B 26 0 656 928 ;
+C -1 ; WX 500 ; N brokenbar ; B 207 -172 294 699 ;
+C -1 ; WX 329 ; N degree ; B 20 424 309 713 ;
+C -1 ; WX 833 ; N divide ; B 124 45 710 551 ;
+C -1 ; WX 561 ; N eth ; B 25 -12 524 740 ;
+C -1 ; WX 578 ; N mu ; B -53 -206 546 433 ;
+C -1 ; WX 833 ; N multiply ; B 139 16 704 581 ;
+C -1 ; WX 905 ; N onehalf ; B 49 0 871 677 ;
+C -1 ; WX 905 ; N onequarter ; B 49 -19 884 677 ;
+C -1 ; WX 387 ; N onesuperior ; B 52 268 289 677 ;
+C -1 ; WX 833 ; N plusminus ; B 124 7 710 590 ;
+C -1 ; WX 565 ; N thorn ; B -64 -218 519 736 ;
+C -1 ; WX 905 ; N threequarters ; B 6 -19 884 678 ;
+C -1 ; WX 387 ; N threesuperior ; B 7 261 357 679 ;
+C -1 ; WX 387 ; N twosuperior ; B -3 268 351 679 ;
+C -1 ; WX 494 ; N yacute ; B -79 -216 514 742 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 378
+KPX hyphen T -37
+KPX hyphen V -56
+KPX hyphen W -56
+KPX hyphen X -37
+KPX hyphen Y -74
+KPX A quoteright -130
+KPX A colon 19
+KPX A semicolon 19
+KPX A S 19
+KPX A T -37
+KPX A U -23
+KPX A V -56
+KPX A W -42
+KPX A Y -42
+KPX A y -19
+KPX A quotedblright -130
+KPX B hyphen 56
+KPX B S 19
+KPX B V -19
+KPX B W -19
+KPX B Y -19
+KPX C quoteright 37
+KPX C hyphen 23
+KPX C S 19
+KPX C quotedblright 37
+KPX D hyphen 37
+KPX D A -19
+KPX D V -19
+KPX D W -19
+KPX D Y -28
+KPX D Aring -19
+KPX F comma -167
+KPX F hyphen -56
+KPX F period -167
+KPX F colon -37
+KPX F semicolon -37
+KPX F A -32
+KPX F a -60
+KPX F e -65
+KPX F i -19
+KPX F o -46
+KPX F r -19
+KPX F u -19
+KPX F quotesinglbase -37
+KPX F quotedblbase -37
+KPX F ae -60
+KPX F oslash -46
+KPX F oe -46
+KPX F Aring -32
+KPX G hyphen 19
+KPX J A -23
+KPX J Aring -23
+KPX K hyphen -37
+KPX K A -23
+KPX K C -28
+KPX K O -28
+KPX K U -37
+KPX K W -42
+KPX K Y -32
+KPX K a -19
+KPX K e -19
+KPX K o -37
+KPX K u -19
+KPX K y -83
+KPX K quotesinglbase 56
+KPX K quotedblbase 56
+KPX K Oslash -28
+KPX K OE -28
+KPX K ae -19
+KPX K oslash -37
+KPX K oe -37
+KPX K Aring -23
+KPX L quoteright -185
+KPX L hyphen 56
+KPX L A 19
+KPX L O -19
+KPX L T -74
+KPX L U -37
+KPX L V -102
+KPX L W -88
+KPX L Y -88
+KPX L quoteleft -56
+KPX L u -19
+KPX L y -74
+KPX L quotedblleft -56
+KPX L quotesinglbase 19
+KPX L quotedblbase 19
+KPX L quotedblright -185
+KPX L Oslash -19
+KPX L OE -19
+KPX L Aring 19
+KPX O comma -37
+KPX O hyphen 19
+KPX O period -37
+KPX O V -19
+KPX O X -19
+KPX O Y -19
+KPX P comma -250
+KPX P hyphen -56
+KPX P period -250
+KPX P colon -19
+KPX P semicolon -19
+KPX P A -74
+KPX P U -19
+KPX P W -19
+KPX P Y -19
+KPX P a -37
+KPX P e -37
+KPX P i 19
+KPX P n 19
+KPX P o -32
+KPX P r 19
+KPX P u 19
+KPX P y 19
+KPX P quotesinglbase -93
+KPX P quotedblbase -93
+KPX P ae -37
+KPX P oslash -32
+KPX P oe -32
+KPX P Aring -74
+KPX Q quoteright 19
+KPX Q hyphen 19
+KPX Q quotesinglbase 56
+KPX Q quotedblbase 56
+KPX Q quotedblright 19
+KPX R quoteright -37
+KPX R comma 19
+KPX R hyphen -19
+KPX R period 19
+KPX R C -19
+KPX R T -19
+KPX R V -19
+KPX R W -23
+KPX R Y -37
+KPX R quoteleft -19
+KPX R e -19
+KPX R o -19
+KPX R quotedblleft -19
+KPX R quotesinglbase 56
+KPX R quotedblbase 56
+KPX R quotedblright -37
+KPX R oslash -19
+KPX R oe -19
+KPX S A 19
+KPX S G 19
+KPX S O 19
+KPX S Q 19
+KPX S S -19
+KPX S Oslash 19
+KPX S OE 19
+KPX S Aring 19
+KPX T comma -148
+KPX T hyphen -130
+KPX T period -148
+KPX T colon -37
+KPX T semicolon -37
+KPX T A -56
+KPX T T 19
+KPX T a -116
+KPX T c -97
+KPX T e -97
+KPX T i -19
+KPX T o -97
+KPX T r -56
+KPX T s -93
+KPX T u -93
+KPX T w -93
+KPX T y -74
+KPX T guillemotleft -37
+KPX T guilsinglleft -37
+KPX T quotesinglbase -19
+KPX T quotedblbase -19
+KPX T ae -116
+KPX T oslash -97
+KPX T oe -97
+KPX T Aring -56
+KPX U A -28
+KPX U J -19
+KPX U Z -19
+KPX U Aring -28
+KPX V comma -185
+KPX V hyphen -56
+KPX V period -185
+KPX V colon -93
+KPX V semicolon -93
+KPX V A -97
+KPX V O -19
+KPX V a -93
+KPX V e -93
+KPX V i -28
+KPX V o -60
+KPX V u -32
+KPX V y -28
+KPX V quotesinglbase -37
+KPX V quotedblbase -37
+KPX V Oslash -19
+KPX V OE -19
+KPX V ae -93
+KPX V oslash -60
+KPX V oe -60
+KPX V Aring -97
+KPX W comma -134
+KPX W hyphen -37
+KPX W period -134
+KPX W colon -28
+KPX W semicolon -28
+KPX W A -28
+KPX W a -69
+KPX W e -93
+KPX W i -19
+KPX W o -69
+KPX W r -28
+KPX W u -28
+KPX W y -23
+KPX W quotesinglbase -37
+KPX W quotedblbase -37
+KPX W ae -69
+KPX W oslash -69
+KPX W oe -69
+KPX W Aring -28
+KPX X hyphen -19
+KPX X A -19
+KPX X e -37
+KPX X guilsinglright 19
+KPX X quotesinglbase 37
+KPX X quotedblbase 37
+KPX X guillemotright 19
+KPX X Aring -19
+KPX Y quoteright 19
+KPX Y comma -130
+KPX Y hyphen -111
+KPX Y period -130
+KPX Y colon -106
+KPX Y semicolon -106
+KPX Y A -46
+KPX Y a -116
+KPX Y e -116
+KPX Y i -19
+KPX Y o -97
+KPX Y u -56
+KPX Y guillemotleft -37
+KPX Y guilsinglleft -37
+KPX Y quotesinglbase -19
+KPX Y quotedblbase -19
+KPX Y quotedblright 19
+KPX Y ae -116
+KPX Y oslash -97
+KPX Y oe -97
+KPX Y Aring -46
+KPX Z hyphen 37
+KPX quoteleft A -130
+KPX quoteleft J -130
+KPX quoteleft T 19
+KPX quoteleft V 56
+KPX quoteleft W 37
+KPX quoteleft X 37
+KPX quoteleft Y 56
+KPX quoteleft AE -148
+KPX quoteleft Aring -130
+KPX f quoteright 93
+KPX f comma -83
+KPX f hyphen -19
+KPX f period -83
+KPX f quoteleft 37
+KPX f quotedblleft 37
+KPX f quotedblright 93
+KPX r comma -130
+KPX r hyphen -19
+KPX r period -130
+KPX r g -19
+KPX r h -19
+KPX v comma -46
+KPX v hyphen 37
+KPX v period -46
+KPX w comma -56
+KPX w hyphen 19
+KPX w period -56
+KPX y comma -60
+KPX y hyphen 19
+KPX y period -60
+KPX quotedblleft A -130
+KPX quotedblleft J -130
+KPX quotedblleft T 19
+KPX quotedblleft V 56
+KPX quotedblleft W 37
+KPX quotedblleft X 37
+KPX quotedblleft Y 56
+KPX quotedblleft AE -148
+KPX quotedblleft Aring -130
+KPX quotesinglbase A 37
+KPX quotesinglbase C -37
+KPX quotesinglbase D 19
+KPX quotesinglbase F 19
+KPX quotesinglbase G -19
+KPX quotesinglbase H 19
+KPX quotesinglbase J 19
+KPX quotesinglbase T -37
+KPX quotesinglbase V -56
+KPX quotesinglbase W -37
+KPX quotesinglbase X 37
+KPX quotesinglbase Y -37
+KPX quotesinglbase f 37
+KPX quotesinglbase v -37
+KPX quotesinglbase w -37
+KPX quotesinglbase fi 37
+KPX quotesinglbase fl 37
+KPX quotesinglbase AE 74
+KPX quotesinglbase germandbls 37
+KPX quotesinglbase Aring 37
+KPX quotesinglbase Eth 19
+KPX quotedblbase A 37
+KPX quotedblbase C -37
+KPX quotedblbase D 19
+KPX quotedblbase F 19
+KPX quotedblbase G -19
+KPX quotedblbase H 19
+KPX quotedblbase J 19
+KPX quotedblbase T -37
+KPX quotedblbase V -56
+KPX quotedblbase W -37
+KPX quotedblbase X 37
+KPX quotedblbase Y -37
+KPX quotedblbase f 37
+KPX quotedblbase v -37
+KPX quotedblbase w -37
+KPX quotedblbase fi 37
+KPX quotedblbase fl 37
+KPX quotedblbase AE 74
+KPX quotedblbase germandbls 37
+KPX quotedblbase Aring 37
+KPX quotedblbase Eth 19
+KPX AE hyphen 19
+KPX Lslash quoteright -185
+KPX Lslash hyphen 56
+KPX Lslash A 19
+KPX Lslash O -19
+KPX Lslash T -74
+KPX Lslash U -37
+KPX Lslash V -102
+KPX Lslash W -88
+KPX Lslash Y -88
+KPX Lslash quoteleft -56
+KPX Lslash u -19
+KPX Lslash y -74
+KPX Lslash quotedblleft -56
+KPX Lslash quotesinglbase 19
+KPX Lslash quotedblbase 19
+KPX Lslash quotedblright -185
+KPX Lslash Oslash -19
+KPX Lslash OE -19
+KPX Lslash Aring 19
+KPX Oslash comma -37
+KPX Oslash hyphen 19
+KPX Oslash period -37
+KPX Oslash V -19
+KPX Oslash X -19
+KPX Oslash Y -19
+KPX Aring quoteright -130
+KPX Aring colon 19
+KPX Aring semicolon 19
+KPX Aring S 19
+KPX Aring T -37
+KPX Aring U -23
+KPX Aring V -56
+KPX Aring W -42
+KPX Aring Y -42
+KPX Aring y -19
+KPX Aring quotedblright -130
+KPX Eth hyphen 37
+KPX Eth A -19
+KPX Eth V -19
+KPX Eth W -19
+KPX Eth Y -28
+KPX Eth Aring -19
+KPX Thorn quoteright -37
+KPX Thorn comma -148
+KPX Thorn period -148
+KPX Thorn quotedblright -37
+EndKernPairs
+StartTrackKern 3
+TrackKern -1 6 0.10 144 -2.09
+TrackKern -2 6 0.05 144 -4.02
+TrackKern -3 6 0.00 144 -5.96
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0633bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0633bt_.pfb
new file mode 100644
index 00000000..d68f639b
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0633bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0648bt_.afm b/e2e-tests/cypress/fonts/Type1/c0648bt_.afm
new file mode 100644
index 00000000..6d58e7c6
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0648bt_.afm
@@ -0,0 +1,538 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Transitional 801
+Comment bitsFontID 0648
+Comment bitsManufacturingDate Tue Nov 6 02:52:05 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530648
+FontName CharterBT-Roman
+FullName Bitstream Charter
+FamilyName Bitstream Charter
+Weight Normal
+ItalicAngle 0.00
+IsFixedPitch false
+FontBBox -162 -237 1194 963
+UnderlinePosition -109
+UnderlineThickness 61
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 672
+XHeight 482
+Ascender 737
+Descender -218
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 338 ; N exclam ; B 112 -9 226 683 ;
+C 34 ; WX 331 ; N quotedbl ; B 43 421 288 715 ;
+C 35 ; WX 745 ; N numbersign ; B 63 -24 681 710 ;
+C 36 ; WX 556 ; N dollar ; B 57 -102 498 744 ;
+C 37 ; WX 852 ; N percent ; B 30 -12 822 683 ;
+C 38 ; WX 704 ; N ampersand ; B 53 -12 683 683 ;
+C 39 ; WX 201 ; N quoteright ; B 30 442 170 714 ;
+C 40 ; WX 417 ; N parenleft ; B 105 -142 386 718 ;
+C 41 ; WX 417 ; N parenright ; B 31 -142 311 718 ;
+C 42 ; WX 500 ; N asterisk ; B 53 337 447 718 ;
+C 43 ; WX 833 ; N plus ; B 124 0 710 597 ;
+C 44 ; WX 278 ; N comma ; B 39 -169 208 107 ;
+C 45 ; WX 319 ; N hyphen ; B 47 207 272 275 ;
+C 46 ; WX 278 ; N period ; B 75 -10 203 118 ;
+C 47 ; WX 481 ; N slash ; B -29 -93 461 672 ;
+C 48 ; WX 556 ; N zero ; B 40 -12 516 683 ;
+C 49 ; WX 556 ; N one ; B 94 0 460 683 ;
+C 50 ; WX 556 ; N two ; B 42 0 506 684 ;
+C 51 ; WX 556 ; N three ; B 40 -11 492 684 ;
+C 52 ; WX 556 ; N four ; B 26 -38 539 678 ;
+C 53 ; WX 556 ; N five ; B 49 -10 489 672 ;
+C 54 ; WX 556 ; N six ; B 50 -13 526 714 ;
+C 55 ; WX 556 ; N seven ; B 70 -38 532 672 ;
+C 56 ; WX 556 ; N eight ; B 43 -16 507 685 ;
+C 57 ; WX 556 ; N nine ; B 43 -53 512 681 ;
+C 58 ; WX 319 ; N colon ; B 96 -10 224 482 ;
+C 59 ; WX 319 ; N semicolon ; B 64 -169 234 482 ;
+C 60 ; WX 833 ; N less ; B 128 37 704 560 ;
+C 61 ; WX 833 ; N equal ; B 124 175 710 421 ;
+C 62 ; WX 833 ; N greater ; B 129 37 704 560 ;
+C 63 ; WX 486 ; N question ; B 54 -9 410 683 ;
+C 64 ; WX 942 ; N at ; B 76 -154 871 693 ;
+C 65 ; WX 639 ; N A ; B -8 0 651 680 ;
+C 66 ; WX 604 ; N B ; B 32 0 559 672 ;
+C 67 ; WX 632 ; N C ; B 42 -13 588 683 ;
+C 68 ; WX 693 ; N D ; B 32 0 649 672 ;
+C 69 ; WX 576 ; N E ; B 32 0 549 672 ;
+C 70 ; WX 537 ; N F ; B 24 0 506 672 ;
+C 71 ; WX 694 ; N G ; B 42 -13 667 684 ;
+C 72 ; WX 738 ; N H ; B 32 0 706 672 ;
+C 73 ; WX 324 ; N I ; B 35 0 289 672 ;
+C 74 ; WX 444 ; N J ; B 12 -13 440 672 ;
+C 75 ; WX 611 ; N K ; B 32 0 628 672 ;
+C 76 ; WX 520 ; N L ; B 26 0 507 672 ;
+C 77 ; WX 866 ; N M ; B 30 0 835 672 ;
+C 78 ; WX 713 ; N N ; B 26 0 688 672 ;
+C 79 ; WX 731 ; N O ; B 42 -17 689 688 ;
+C 80 ; WX 558 ; N P ; B 24 0 532 672 ;
+C 81 ; WX 731 ; N Q ; B 39 -177 694 689 ;
+C 82 ; WX 646 ; N R ; B 32 -9 657 672 ;
+C 83 ; WX 556 ; N S ; B 60 -12 499 684 ;
+C 84 ; WX 597 ; N T ; B 15 0 582 672 ;
+C 85 ; WX 694 ; N U ; B 24 -12 680 672 ;
+C 86 ; WX 618 ; N V ; B -23 -5 638 672 ;
+C 87 ; WX 928 ; N W ; B 0 0 928 672 ;
+C 88 ; WX 600 ; N X ; B -9 0 610 672 ;
+C 89 ; WX 586 ; N Y ; B -14 0 607 672 ;
+C 90 ; WX 586 ; N Z ; B 45 0 540 672 ;
+C 91 ; WX 421 ; N bracketleft ; B 138 -133 376 709 ;
+C 92 ; WX 481 ; N backslash ; B 12 -93 502 672 ;
+C 93 ; WX 421 ; N bracketright ; B 45 -133 283 709 ;
+C 94 ; WX 1000 ; N asciicircum ; B 201 437 798 714 ;
+C 95 ; WX 500 ; N underscore ; B 0 -237 500 -178 ;
+C 96 ; WX 201 ; N quoteleft ; B 34 441 174 713 ;
+C 97 ; WX 507 ; N a ; B 41 -7 489 492 ;
+C 98 ; WX 539 ; N b ; B 8 0 504 737 ;
+C 99 ; WX 446 ; N c ; B 37 -7 426 491 ;
+C 100 ; WX 565 ; N d ; B 36 -10 531 737 ;
+C 101 ; WX 491 ; N e ; B 37 -10 449 491 ;
+C 102 ; WX 321 ; N f ; B 28 0 381 744 ;
+C 103 ; WX 523 ; N g ; B 39 -219 513 492 ;
+C 104 ; WX 564 ; N h ; B 18 0 547 737 ;
+C 105 ; WX 280 ; N i ; B 34 0 261 709 ;
+C 106 ; WX 266 ; N j ; B -79 -218 204 709 ;
+C 107 ; WX 517 ; N k ; B 18 0 528 737 ;
+C 108 ; WX 282 ; N l ; B 26 0 262 737 ;
+C 109 ; WX 843 ; N m ; B 30 0 826 491 ;
+C 110 ; WX 568 ; N n ; B 30 0 551 491 ;
+C 111 ; WX 539 ; N o ; B 37 -10 503 491 ;
+C 112 ; WX 551 ; N p ; B 23 -218 517 491 ;
+C 113 ; WX 531 ; N q ; B 36 -218 527 492 ;
+C 114 ; WX 382 ; N r ; B 29 0 377 492 ;
+C 115 ; WX 400 ; N s ; B 41 -10 359 492 ;
+C 116 ; WX 334 ; N t ; B 24 -4 323 575 ;
+C 117 ; WX 569 ; N u ; B 26 -10 542 491 ;
+C 118 ; WX 494 ; N v ; B -6 0 508 482 ;
+C 119 ; WX 771 ; N w ; B -3 0 772 482 ;
+C 120 ; WX 503 ; N x ; B 12 0 501 482 ;
+C 121 ; WX 495 ; N y ; B -2 -218 512 482 ;
+C 122 ; WX 468 ; N z ; B 45 0 431 482 ;
+C 123 ; WX 486 ; N braceleft ; B 64 -135 418 703 ;
+C 124 ; WX 500 ; N bar ; B 219 -237 282 764 ;
+C 125 ; WX 486 ; N braceright ; B 64 -135 418 703 ;
+C 126 ; WX 833 ; N asciitilde ; B 86 225 747 371 ;
+C 161 ; WX 338 ; N exclamdown ; B 112 -9 226 683 ;
+C 162 ; WX 556 ; N cent ; B 64 -98 464 602 ;
+C 163 ; WX 556 ; N sterling ; B 37 0 509 683 ;
+C 164 ; WX 167 ; N fraction ; B -162 0 328 672 ;
+C 165 ; WX 556 ; N yen ; B -6 0 560 672 ;
+C 166 ; WX 556 ; N florin ; B 6 -169 507 683 ;
+C 167 ; WX 500 ; N section ; B 62 -141 437 718 ;
+C 168 ; WX 606 ; N currency ; B 41 171 566 694 ;
+C 169 ; WX 170 ; N quotesingle ; B 43 421 127 715 ;
+C 170 ; WX 403 ; N quotedblleft ; B 34 441 376 713 ;
+C 171 ; WX 442 ; N guillemotleft ; B 39 67 401 413 ;
+C 172 ; WX 245 ; N guilsinglleft ; B 39 67 204 413 ;
+C 173 ; WX 245 ; N guilsinglright ; B 45 67 210 413 ;
+C 174 ; WX 574 ; N fi ; B 28 0 544 744 ;
+C 175 ; WX 579 ; N fl ; B 28 0 561 744 ;
+C 177 ; WX 500 ; N endash ; B 0 210 500 271 ;
+C 178 ; WX 500 ; N dagger ; B 17 -130 484 718 ;
+C 179 ; WX 500 ; N daggerdbl ; B 17 -132 484 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 75 271 203 400 ;
+C 182 ; WX 484 ; N paragraph ; B 25 -79 459 672 ;
+C 183 ; WX 590 ; N bullet ; B 150 227 439 516 ;
+C 184 ; WX 201 ; N quotesinglbase ; B 31 -165 171 107 ;
+C 185 ; WX 403 ; N quotedblbase ; B 31 -165 372 107 ;
+C 186 ; WX 403 ; N quotedblright ; B 30 442 371 714 ;
+C 187 ; WX 442 ; N guillemotright ; B 45 67 407 413 ;
+C 188 ; WX 1000 ; N ellipsis ; B 102 -10 898 118 ;
+C 189 ; WX 1225 ; N perthousand ; B 30 -12 1194 683 ;
+C 191 ; WX 486 ; N questiondown ; B 60 -9 415 683 ;
+C 193 ; WX 500 ; N grave ; B 104 546 300 737 ;
+C 194 ; WX 500 ; N acute ; B 212 546 409 737 ;
+C 195 ; WX 500 ; N circumflex ; B 107 546 393 737 ;
+C 196 ; WX 500 ; N tilde ; B 97 572 403 709 ;
+C 197 ; WX 500 ; N macron ; B 101 607 403 668 ;
+C 198 ; WX 500 ; N breve ; B 108 567 392 709 ;
+C 199 ; WX 500 ; N dotaccent ; B 196 589 304 697 ;
+C 200 ; WX 500 ; N dieresis ; B 106 589 394 691 ;
+C 202 ; WX 500 ; N ring ; B 132 546 368 782 ;
+C 203 ; WX 500 ; N cedilla ; B 179 -224 368 0 ;
+C 205 ; WX 500 ; N hungarumlaut ; B 133 546 473 737 ;
+C 206 ; WX 500 ; N ogonek ; B 182 -217 330 0 ;
+C 207 ; WX 500 ; N caron ; B 107 545 393 736 ;
+C 208 ; WX 1000 ; N emdash ; B 0 210 1000 271 ;
+C 225 ; WX 866 ; N AE ; B -57 0 838 672 ;
+C 227 ; WX 380 ; N ordfeminine ; B 30 329 367 679 ;
+C 232 ; WX 520 ; N Lslash ; B 10 0 507 672 ;
+C 233 ; WX 731 ; N Oslash ; B 42 -78 689 748 ;
+C 234 ; WX 993 ; N OE ; B 42 -8 965 680 ;
+C 235 ; WX 404 ; N ordmasculine ; B 27 327 378 678 ;
+C 241 ; WX 725 ; N ae ; B 43 -10 683 492 ;
+C 245 ; WX 280 ; N dotlessi ; B 34 0 261 487 ;
+C 248 ; WX 282 ; N lslash ; B 4 0 300 737 ;
+C 249 ; WX 539 ; N oslash ; B 37 -81 503 560 ;
+C 250 ; WX 817 ; N oe ; B 36 -10 776 491 ;
+C 251 ; WX 609 ; N germandbls ; B 18 -7 581 741 ;
+C -1 ; WX 639 ; N Aacute ; B -8 0 651 934 ;
+C -1 ; WX 639 ; N Acircumflex ; B -8 0 651 934 ;
+C -1 ; WX 639 ; N Adieresis ; B -8 0 651 888 ;
+C -1 ; WX 639 ; N Agrave ; B -8 0 651 934 ;
+C -1 ; WX 639 ; N Aring ; B -8 0 651 963 ;
+C -1 ; WX 639 ; N Atilde ; B -8 0 651 906 ;
+C -1 ; WX 632 ; N Ccedilla ; B 42 -224 588 683 ;
+C -1 ; WX 576 ; N Eacute ; B 32 0 549 934 ;
+C -1 ; WX 576 ; N Ecircumflex ; B 32 0 549 934 ;
+C -1 ; WX 576 ; N Edieresis ; B 32 0 549 888 ;
+C -1 ; WX 576 ; N Egrave ; B 32 0 549 934 ;
+C -1 ; WX 324 ; N Iacute ; B 35 0 321 934 ;
+C -1 ; WX 324 ; N Icircumflex ; B 19 0 305 934 ;
+C -1 ; WX 324 ; N Idieresis ; B 18 0 306 888 ;
+C -1 ; WX 324 ; N Igrave ; B 16 0 289 934 ;
+C -1 ; WX 713 ; N Ntilde ; B 26 0 688 906 ;
+C -1 ; WX 731 ; N Oacute ; B 42 -17 689 934 ;
+C -1 ; WX 731 ; N Ocircumflex ; B 42 -17 689 934 ;
+C -1 ; WX 731 ; N Odieresis ; B 42 -17 689 888 ;
+C -1 ; WX 731 ; N Ograve ; B 42 -17 689 934 ;
+C -1 ; WX 731 ; N Otilde ; B 42 -17 689 906 ;
+C -1 ; WX 556 ; N Scaron ; B 60 -12 499 933 ;
+C -1 ; WX 694 ; N Uacute ; B 24 -12 680 934 ;
+C -1 ; WX 694 ; N Ucircumflex ; B 24 -12 680 934 ;
+C -1 ; WX 694 ; N Udieresis ; B 24 -12 680 888 ;
+C -1 ; WX 694 ; N Ugrave ; B 24 -12 680 934 ;
+C -1 ; WX 586 ; N Ydieresis ; B -14 0 607 888 ;
+C -1 ; WX 586 ; N Zcaron ; B 45 0 540 933 ;
+C -1 ; WX 507 ; N aacute ; B 41 -7 489 737 ;
+C -1 ; WX 507 ; N acircumflex ; B 41 -7 489 737 ;
+C -1 ; WX 507 ; N adieresis ; B 41 -7 489 691 ;
+C -1 ; WX 507 ; N agrave ; B 41 -7 489 737 ;
+C -1 ; WX 507 ; N aring ; B 41 -7 489 782 ;
+C -1 ; WX 507 ; N atilde ; B 41 -7 489 709 ;
+C -1 ; WX 446 ; N ccedilla ; B 37 -224 426 491 ;
+C -1 ; WX 491 ; N eacute ; B 37 -10 449 737 ;
+C -1 ; WX 491 ; N ecircumflex ; B 37 -10 449 737 ;
+C -1 ; WX 491 ; N edieresis ; B 37 -10 449 691 ;
+C -1 ; WX 491 ; N egrave ; B 37 -10 449 737 ;
+C -1 ; WX 280 ; N iacute ; B 34 0 299 737 ;
+C -1 ; WX 280 ; N icircumflex ; B -3 0 283 737 ;
+C -1 ; WX 280 ; N idieresis ; B -4 0 284 691 ;
+C -1 ; WX 280 ; N igrave ; B -6 0 261 737 ;
+C -1 ; WX 568 ; N ntilde ; B 30 0 551 709 ;
+C -1 ; WX 539 ; N oacute ; B 37 -10 503 737 ;
+C -1 ; WX 539 ; N ocircumflex ; B 37 -10 503 737 ;
+C -1 ; WX 539 ; N odieresis ; B 37 -10 503 691 ;
+C -1 ; WX 539 ; N ograve ; B 37 -10 503 737 ;
+C -1 ; WX 539 ; N otilde ; B 37 -10 503 709 ;
+C -1 ; WX 400 ; N scaron ; B 41 -10 359 736 ;
+C -1 ; WX 569 ; N uacute ; B 26 -10 542 737 ;
+C -1 ; WX 569 ; N ucircumflex ; B 26 -10 542 737 ;
+C -1 ; WX 569 ; N udieresis ; B 26 -10 542 691 ;
+C -1 ; WX 569 ; N ugrave ; B 26 -10 542 737 ;
+C -1 ; WX 495 ; N ydieresis ; B -2 -218 512 691 ;
+C -1 ; WX 468 ; N zcaron ; B 45 0 431 736 ;
+C -1 ; WX 822 ; N trademark ; B 118 398 716 663 ;
+C -1 ; WX 900 ; N copyright ; B 66 -46 838 726 ;
+C -1 ; WX 833 ; N logicalnot ; B 124 174 710 419 ;
+C -1 ; WX 900 ; N registered ; B 66 -46 838 726 ;
+C -1 ; WX 833 ; N minus ; B 124 269 710 328 ;
+C -1 ; WX 693 ; N Eth ; B 14 0 649 672 ;
+C -1 ; WX 558 ; N Thorn ; B 35 0 534 672 ;
+C -1 ; WX 586 ; N Yacute ; B -14 0 607 934 ;
+C -1 ; WX 500 ; N brokenbar ; B 219 -172 282 699 ;
+C -1 ; WX 329 ; N degree ; B 26 434 303 710 ;
+C -1 ; WX 833 ; N divide ; B 124 66 710 531 ;
+C -1 ; WX 528 ; N eth ; B 33 -11 493 734 ;
+C -1 ; WX 547 ; N mu ; B -39 -204 532 433 ;
+C -1 ; WX 833 ; N multiply ; B 146 26 691 571 ;
+C -1 ; WX 867 ; N onehalf ; B 59 0 836 678 ;
+C -1 ; WX 867 ; N onequarter ; B 59 -22 857 678 ;
+C -1 ; WX 367 ; N onesuperior ; B 62 268 304 679 ;
+C -1 ; WX 833 ; N plusminus ; B 124 20 710 577 ;
+C -1 ; WX 551 ; N thorn ; B 20 -218 521 737 ;
+C -1 ; WX 868 ; N threequarters ; B 25 -22 857 679 ;
+C -1 ; WX 367 ; N threesuperior ; B 26 261 325 679 ;
+C -1 ; WX 367 ; N twosuperior ; B 27 268 334 679 ;
+C -1 ; WX 495 ; N yacute ; B -2 -218 512 737 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 271
+KPX hyphen T -37
+KPX hyphen V -56
+KPX hyphen W -56
+KPX hyphen X -37
+KPX hyphen Y -74
+KPX A quoteright -130
+KPX A T -111
+KPX A U -23
+KPX A V -56
+KPX A W -42
+KPX A Y -42
+KPX A f -19
+KPX A t -19
+KPX A v -32
+KPX A w -46
+KPX A y -23
+KPX A fi -19
+KPX A fl -19
+KPX A quotedblright -130
+KPX B hyphen 37
+KPX B C 19
+KPX B G 19
+KPX B O 19
+KPX B S 19
+KPX B V -37
+KPX B W -19
+KPX B Y -19
+KPX B Oslash 19
+KPX B OE 19
+KPX C quoteright 37
+KPX C hyphen 23
+KPX C A -19
+KPX C S 19
+KPX C quotedblright 37
+KPX C Aring -19
+KPX D hyphen 37
+KPX D A -19
+KPX D V -19
+KPX D Y -19
+KPX D Aring -19
+KPX F comma -190
+KPX F hyphen -93
+KPX F period -190
+KPX F colon -37
+KPX F semicolon -37
+KPX F A -97
+KPX F a -79
+KPX F e -65
+KPX F o -65
+KPX F ae -79
+KPX F oslash -65
+KPX F oe -65
+KPX F Aring -97
+KPX G hyphen 19
+KPX G T -19
+KPX G W -19
+KPX G Y -23
+KPX J A -37
+KPX J Aring -37
+KPX K hyphen -37
+KPX K A -23
+KPX K C -28
+KPX K O -28
+KPX K U -37
+KPX K W -37
+KPX K Y -28
+KPX K e -19
+KPX K o -19
+KPX K u -19
+KPX K y -28
+KPX K Oslash -28
+KPX K OE -28
+KPX K oslash -19
+KPX K oe -19
+KPX K Aring -23
+KPX L quoteright -241
+KPX L T -83
+KPX L U -19
+KPX L V -120
+KPX L W -88
+KPX L Y -102
+KPX L y -19
+KPX L quotedblright -241
+KPX O comma -60
+KPX O hyphen 37
+KPX O period -60
+KPX O V -19
+KPX O X -19
+KPX P comma -259
+KPX P hyphen -93
+KPX P period -259
+KPX P colon -37
+KPX P semicolon -37
+KPX P A -93
+KPX P U -19
+KPX P a -56
+KPX P e -56
+KPX P o -51
+KPX P s -32
+KPX P ae -56
+KPX P oslash -51
+KPX P oe -51
+KPX P Aring -93
+KPX Q quoteright 19
+KPX Q hyphen 37
+KPX Q quotedblright 19
+KPX R quoteright -37
+KPX R colon -19
+KPX R semicolon -19
+KPX R T -37
+KPX R V -56
+KPX R W -42
+KPX R Y -51
+KPX R quoteleft -37
+KPX R e -37
+KPX R o -37
+KPX R u -37
+KPX R y -46
+KPX R quotedblleft -37
+KPX R quotesinglbase 37
+KPX R quotedblbase 37
+KPX R quotedblright -37
+KPX R oslash -37
+KPX R oe -37
+KPX T quoteright 19
+KPX T comma -148
+KPX T hyphen -130
+KPX T period -148
+KPX T colon -37
+KPX T semicolon -37
+KPX T A -111
+KPX T T 19
+KPX T quoteleft 37
+KPX T a -97
+KPX T c -97
+KPX T e -97
+KPX T i -19
+KPX T o -97
+KPX T r -74
+KPX T s -74
+KPX T u -111
+KPX T w -74
+KPX T y -93
+KPX T quotedblleft 37
+KPX T guillemotleft -37
+KPX T guilsinglleft -37
+KPX T quotedblright 19
+KPX T ae -97
+KPX T oslash -97
+KPX T oe -97
+KPX T Aring -111
+KPX U A -32
+KPX U J -28
+KPX U Aring -32
+KPX V quoteright 37
+KPX V comma -222
+KPX V hyphen -93
+KPX V period -222
+KPX V colon -102
+KPX V semicolon -102
+KPX V A -79
+KPX V O -19
+KPX V a -111
+KPX V e -106
+KPX V i -28
+KPX V o -93
+KPX V u -65
+KPX V y -65
+KPX V quotedblright 37
+KPX V Oslash -19
+KPX V OE -19
+KPX V ae -111
+KPX V oslash -93
+KPX V oe -93
+KPX V Aring -79
+KPX W quoteright 19
+KPX W comma -176
+KPX W hyphen -74
+KPX W period -176
+KPX W colon -88
+KPX W semicolon -88
+KPX W A -60
+KPX W a -69
+KPX W e -83
+KPX W i -19
+KPX W o -69
+KPX W r -46
+KPX W u -42
+KPX W y -23
+KPX W quotedblright 19
+KPX W ae -69
+KPX W oslash -69
+KPX W oe -69
+KPX W Aring -60
+KPX X hyphen -37
+KPX X A -19
+KPX X C -19
+KPX X O -19
+KPX X Oslash -19
+KPX X OE -19
+KPX X Aring -19
+KPX Y comma -130
+KPX Y hyphen -130
+KPX Y period -130
+KPX Y colon -125
+KPX Y semicolon -125
+KPX Y A -60
+KPX Y C -19
+KPX Y a -97
+KPX Y e -106
+KPX Y i -37
+KPX Y o -97
+KPX Y u -69
+KPX Y ae -97
+KPX Y oslash -97
+KPX Y oe -97
+KPX Y Aring -60
+KPX quoteleft A -130
+KPX quoteleft J -167
+KPX quoteleft AE -111
+KPX quoteleft Aring -130
+KPX f quoteright 74
+KPX f comma -37
+KPX f hyphen -19
+KPX f period -37
+KPX f quotedblright 74
+KPX r comma -111
+KPX r period -111
+KPX v comma -120
+KPX v period -120
+KPX w comma -120
+KPX w period -120
+KPX y comma -134
+KPX y period -134
+KPX quotedblleft A -130
+KPX quotedblleft J -167
+KPX quotedblleft AE -111
+KPX quotedblleft Aring -130
+KPX AE hyphen 19
+KPX Lslash quoteright -241
+KPX Lslash T -83
+KPX Lslash U -19
+KPX Lslash V -120
+KPX Lslash W -88
+KPX Lslash Y -102
+KPX Lslash y -19
+KPX Lslash quotedblright -241
+KPX Oslash comma -60
+KPX Oslash hyphen 37
+KPX Oslash period -60
+KPX Oslash V -19
+KPX Oslash X -19
+KPX Aring quoteright -130
+KPX Aring T -111
+KPX Aring U -23
+KPX Aring V -56
+KPX Aring W -42
+KPX Aring Y -42
+KPX Aring f -19
+KPX Aring t -19
+KPX Aring v -32
+KPX Aring w -46
+KPX Aring y -23
+KPX Aring fi -19
+KPX Aring fl -19
+KPX Aring quotedblright -130
+KPX Eth hyphen 37
+KPX Eth A -19
+KPX Eth V -19
+KPX Eth Y -19
+KPX Eth Aring -19
+EndKernPairs
+StartTrackKern 3
+TrackKern -1 6 0.10 144 -2.09
+TrackKern -2 6 0.05 144 -4.02
+TrackKern -3 6 0.00 144 -5.96
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0648bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0648bt_.pfb
new file mode 100644
index 00000000..72a1606b
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0648bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/c0649bt_.afm b/e2e-tests/cypress/fonts/Type1/c0649bt_.afm
new file mode 100644
index 00000000..0f721845
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/c0649bt_.afm
@@ -0,0 +1,547 @@
+StartFontMetrics 2.0
+Comment Bitstream AFM Data
+Comment Copyright 1987-1990 as an unpublished work by Bitstream Inc., Cambridge, MA.
+Comment All rights reserved
+Comment Confidential and proprietary to Bitstream Inc.
+Comment Bitstream is a registered trademark of Bitstream Inc.
+Comment bitsClassification Transitional 801
+Comment bitsFontID 0649
+Comment bitsManufacturingDate Tue Nov 6 02:55:16 1990
+Comment bitsLayoutName clayout.adobe.text228.new
+Comment UniqueID 15530649
+FontName CharterBT-Italic
+FullName Bitstream Charter Italic
+FamilyName Bitstream Charter
+Weight Normal
+ItalicAngle 11.0000
+IsFixedPitch false
+FontBBox -226 -237 1175 980
+UnderlinePosition -109
+UnderlineThickness 61
+Version 1.0 [UFO]
+Notice Copyright 1987-1990 as an unpublished work by Bitstream Inc. All rights reserved. Confidential.
+EncodingScheme AdobeStandardEncoding
+CapHeight 672
+XHeight 486
+Ascender 737
+Descender -218
+StartCharMetrics 228
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 338 ; N exclam ; B 63 -9 281 683 ;
+C 34 ; WX 331 ; N quotedbl ; B 43 421 288 715 ;
+C 35 ; WX 745 ; N numbersign ; B 63 -24 681 710 ;
+C 36 ; WX 556 ; N dollar ; B 21 -102 514 744 ;
+C 37 ; WX 852 ; N percent ; B 49 -12 802 683 ;
+C 38 ; WX 704 ; N ampersand ; B 19 -12 665 684 ;
+C 39 ; WX 201 ; N quoteright ; B 51 442 227 714 ;
+C 40 ; WX 419 ; N parenleft ; B 79 -142 460 718 ;
+C 41 ; WX 419 ; N parenright ; B -67 -142 313 718 ;
+C 42 ; WX 500 ; N asterisk ; B 98 337 492 718 ;
+C 43 ; WX 833 ; N plus ; B 124 0 710 597 ;
+C 44 ; WX 278 ; N comma ; B -58 -169 149 107 ;
+C 45 ; WX 319 ; N hyphen ; B 22 207 260 275 ;
+C 46 ; WX 278 ; N period ; B 25 -6 145 114 ;
+C 47 ; WX 481 ; N slash ; B -111 -93 525 672 ;
+C 48 ; WX 556 ; N zero ; B 27 -12 528 683 ;
+C 49 ; WX 556 ; N one ; B 82 0 405 683 ;
+C 50 ; WX 556 ; N two ; B -22 0 518 684 ;
+C 51 ; WX 556 ; N three ; B 0 -12 512 684 ;
+C 52 ; WX 556 ; N four ; B -1 -38 524 678 ;
+C 53 ; WX 556 ; N five ; B 3 -13 519 672 ;
+C 54 ; WX 556 ; N six ; B 31 -13 501 716 ;
+C 55 ; WX 556 ; N seven ; B 44 -38 596 672 ;
+C 56 ; WX 556 ; N eight ; B 18 -18 518 685 ;
+C 57 ; WX 556 ; N nine ; B 28 -56 523 684 ;
+C 58 ; WX 319 ; N colon ; B 46 -6 235 478 ;
+C 59 ; WX 319 ; N semicolon ; B -33 -169 235 478 ;
+C 60 ; WX 833 ; N less ; B 128 37 704 560 ;
+C 61 ; WX 833 ; N equal ; B 124 175 710 421 ;
+C 62 ; WX 833 ; N greater ; B 129 37 704 560 ;
+C 63 ; WX 486 ; N question ; B 94 -9 446 683 ;
+C 64 ; WX 942 ; N at ; B 76 -154 871 693 ;
+C 65 ; WX 606 ; N A ; B -79 0 585 677 ;
+C 66 ; WX 588 ; N B ; B -29 0 543 672 ;
+C 67 ; WX 604 ; N C ; B 41 -12 622 683 ;
+C 68 ; WX 671 ; N D ; B -28 0 623 672 ;
+C 69 ; WX 546 ; N E ; B -25 0 554 672 ;
+C 70 ; WX 509 ; N F ; B -27 0 540 671 ;
+C 71 ; WX 664 ; N G ; B 39 -12 650 684 ;
+C 72 ; WX 712 ; N H ; B -29 0 741 672 ;
+C 73 ; WX 312 ; N I ; B -23 0 333 672 ;
+C 74 ; WX 447 ; N J ; B -43 -12 472 672 ;
+C 75 ; WX 625 ; N K ; B -30 -5 660 672 ;
+C 76 ; WX 498 ; N L ; B -29 0 453 672 ;
+C 77 ; WX 839 ; N M ; B -28 0 868 672 ;
+C 78 ; WX 683 ; N N ; B -31 0 720 672 ;
+C 79 ; WX 708 ; N O ; B 40 -13 669 683 ;
+C 80 ; WX 542 ; N P ; B -29 0 543 672 ;
+C 81 ; WX 708 ; N Q ; B 40 -160 700 682 ;
+C 82 ; WX 602 ; N R ; B -30 -6 591 671 ;
+C 83 ; WX 537 ; N S ; B 9 -13 511 683 ;
+C 84 ; WX 565 ; N T ; B 43 0 610 672 ;
+C 85 ; WX 664 ; N U ; B 64 -12 705 672 ;
+C 86 ; WX 590 ; N V ; B 30 -6 649 672 ;
+C 87 ; WX 898 ; N W ; B 51 0 952 672 ;
+C 88 ; WX 569 ; N X ; B -83 0 633 672 ;
+C 89 ; WX 562 ; N Y ; B 31 0 642 672 ;
+C 90 ; WX 556 ; N Z ; B -26 0 572 672 ;
+C 91 ; WX 421 ; N bracketleft ; B 49 -133 448 709 ;
+C 92 ; WX 481 ; N backslash ; B 34 -93 489 672 ;
+C 93 ; WX 421 ; N bracketright ; B -45 -133 354 709 ;
+C 94 ; WX 1000 ; N asciicircum ; B 201 437 798 714 ;
+C 95 ; WX 500 ; N underscore ; B 0 -237 500 -178 ;
+C 96 ; WX 201 ; N quoteleft ; B 70 441 247 713 ;
+C 97 ; WX 525 ; N a ; B 17 -9 488 483 ;
+C 98 ; WX 507 ; N b ; B 24 -10 453 737 ;
+C 99 ; WX 394 ; N c ; B 14 -10 370 486 ;
+C 100 ; WX 523 ; N d ; B 20 -9 501 737 ;
+C 101 ; WX 424 ; N e ; B 20 -10 378 483 ;
+C 102 ; WX 292 ; N f ; B -151 -216 404 733 ;
+C 103 ; WX 481 ; N g ; B -31 -218 480 483 ;
+C 104 ; WX 551 ; N h ; B 23 -6 505 737 ;
+C 105 ; WX 287 ; N i ; B 32 -7 255 705 ;
+C 106 ; WX 269 ; N j ; B -128 -216 249 701 ;
+C 107 ; WX 514 ; N k ; B 25 -6 494 737 ;
+C 108 ; WX 275 ; N l ; B 35 -10 241 737 ;
+C 109 ; WX 815 ; N m ; B 31 -6 773 483 ;
+C 110 ; WX 556 ; N n ; B 32 -7 515 483 ;
+C 111 ; WX 502 ; N o ; B 21 -9 450 483 ;
+C 112 ; WX 516 ; N p ; B -70 -218 461 483 ;
+C 113 ; WX 512 ; N q ; B 24 -218 463 488 ;
+C 114 ; WX 398 ; N r ; B 27 0 400 482 ;
+C 115 ; WX 370 ; N s ; B -17 -9 324 483 ;
+C 116 ; WX 333 ; N t ; B 43 -7 337 580 ;
+C 117 ; WX 553 ; N u ; B 30 -9 513 483 ;
+C 118 ; WX 454 ; N v ; B -9 -2 435 484 ;
+C 119 ; WX 713 ; N w ; B -1 0 689 485 ;
+C 120 ; WX 477 ; N x ; B -47 -9 495 486 ;
+C 121 ; WX 475 ; N y ; B -113 -218 485 485 ;
+C 122 ; WX 440 ; N z ; B -15 -12 434 490 ;
+C 123 ; WX 486 ; N braceleft ; B 64 -135 418 703 ;
+C 124 ; WX 500 ; N bar ; B 219 -237 282 764 ;
+C 125 ; WX 486 ; N braceright ; B 64 -135 418 703 ;
+C 126 ; WX 833 ; N asciitilde ; B 86 225 747 371 ;
+C 161 ; WX 338 ; N exclamdown ; B 60 -9 278 683 ;
+C 162 ; WX 556 ; N cent ; B 41 -98 492 602 ;
+C 163 ; WX 556 ; N sterling ; B -22 0 547 683 ;
+C 164 ; WX 167 ; N fraction ; B -226 0 392 672 ;
+C 165 ; WX 556 ; N yen ; B 2 0 616 665 ;
+C 166 ; WX 556 ; N florin ; B -81 -169 563 683 ;
+C 167 ; WX 500 ; N section ; B 3 -141 475 718 ;
+C 168 ; WX 606 ; N currency ; B 41 171 566 694 ;
+C 169 ; WX 170 ; N quotesingle ; B 43 421 127 715 ;
+C 170 ; WX 403 ; N quotedblleft ; B 70 441 448 713 ;
+C 171 ; WX 442 ; N guillemotleft ; B 13 67 416 413 ;
+C 172 ; WX 245 ; N guilsinglleft ; B 13 67 218 413 ;
+C 173 ; WX 245 ; N guilsinglright ; B -7 67 199 413 ;
+C 174 ; WX 574 ; N fi ; B -151 -216 547 733 ;
+C 175 ; WX 579 ; N fl ; B -151 -216 544 737 ;
+C 177 ; WX 500 ; N endash ; B -25 210 488 271 ;
+C 178 ; WX 500 ; N dagger ; B 45 -130 512 718 ;
+C 179 ; WX 500 ; N daggerdbl ; B -27 -132 512 718 ;
+C 180 ; WX 278 ; N periodcentered ; B 79 276 199 396 ;
+C 182 ; WX 484 ; N paragraph ; B 25 -79 459 672 ;
+C 183 ; WX 590 ; N bullet ; B 150 227 439 516 ;
+C 184 ; WX 201 ; N quotesinglbase ; B -65 -165 111 107 ;
+C 185 ; WX 403 ; N quotedblbase ; B -65 -165 313 107 ;
+C 186 ; WX 403 ; N quotedblright ; B 51 442 429 714 ;
+C 187 ; WX 442 ; N guillemotright ; B -7 67 397 413 ;
+C 188 ; WX 1000 ; N ellipsis ; B 52 -6 840 114 ;
+C 189 ; WX 1225 ; N perthousand ; B 49 -12 1175 683 ;
+C 191 ; WX 486 ; N questiondown ; B 27 -9 379 683 ;
+C 193 ; WX 500 ; N grave ; B 181 546 341 737 ;
+C 194 ; WX 500 ; N acute ; B 252 546 485 737 ;
+C 195 ; WX 500 ; N circumflex ; B 148 546 433 737 ;
+C 196 ; WX 500 ; N tilde ; B 143 572 474 709 ;
+C 197 ; WX 500 ; N macron ; B 154 614 465 667 ;
+C 198 ; WX 500 ; N breve ; B 177 567 464 709 ;
+C 199 ; WX 500 ; N dotaccent ; B 248 584 361 697 ;
+C 200 ; WX 500 ; N dieresis ; B 163 589 452 692 ;
+C 202 ; WX 500 ; N ring ; B 209 557 426 774 ;
+C 203 ; WX 500 ; N cedilla ; B 61 -224 267 0 ;
+C 205 ; WX 500 ; N hungarumlaut ; B 175 546 569 737 ;
+C 206 ; WX 500 ; N ogonek ; B 90 -217 235 0 ;
+C 207 ; WX 500 ; N caron ; B 184 545 470 736 ;
+C 208 ; WX 1000 ; N emdash ; B -19 210 991 271 ;
+C 225 ; WX 873 ; N AE ; B -115 0 879 672 ;
+C 227 ; WX 394 ; N ordfeminine ; B 12 325 366 671 ;
+C 232 ; WX 498 ; N Lslash ; B -29 0 453 672 ;
+C 233 ; WX 708 ; N Oslash ; B 41 -74 669 744 ;
+C 234 ; WX 1007 ; N OE ; B 40 -13 1004 682 ;
+C 235 ; WX 377 ; N ordmasculine ; B 15 325 338 671 ;
+C 241 ; WX 671 ; N ae ; B 1 -10 628 483 ;
+C 245 ; WX 287 ; N dotlessi ; B 32 -7 255 483 ;
+C 248 ; WX 275 ; N lslash ; B -14 -10 293 737 ;
+C 249 ; WX 502 ; N oslash ; B 22 -80 450 548 ;
+C 250 ; WX 750 ; N oe ; B 21 -9 704 483 ;
+C 251 ; WX 574 ; N germandbls ; B -151 -216 522 739 ;
+C -1 ; WX 606 ; N Aacute ; B -79 0 585 930 ;
+C -1 ; WX 606 ; N Acircumflex ; B -79 0 585 930 ;
+C -1 ; WX 606 ; N Adieresis ; B -79 0 585 885 ;
+C -1 ; WX 606 ; N Agrave ; B -79 0 585 930 ;
+C -1 ; WX 606 ; N Aring ; B -79 0 585 980 ;
+C -1 ; WX 606 ; N Atilde ; B -79 0 585 902 ;
+C -1 ; WX 604 ; N Ccedilla ; B 41 -224 622 683 ;
+C -1 ; WX 546 ; N Eacute ; B -25 0 554 930 ;
+C -1 ; WX 546 ; N Ecircumflex ; B -25 0 554 930 ;
+C -1 ; WX 546 ; N Edieresis ; B -25 0 554 885 ;
+C -1 ; WX 546 ; N Egrave ; B -25 0 554 930 ;
+C -1 ; WX 312 ; N Iacute ; B -23 0 418 930 ;
+C -1 ; WX 312 ; N Icircumflex ; B -23 0 366 930 ;
+C -1 ; WX 312 ; N Idieresis ; B -23 0 385 885 ;
+C -1 ; WX 312 ; N Igrave ; B -23 0 333 930 ;
+C -1 ; WX 683 ; N Ntilde ; B -31 0 720 902 ;
+C -1 ; WX 708 ; N Oacute ; B 40 -13 669 930 ;
+C -1 ; WX 708 ; N Ocircumflex ; B 40 -13 669 930 ;
+C -1 ; WX 708 ; N Odieresis ; B 40 -13 669 885 ;
+C -1 ; WX 708 ; N Ograve ; B 40 -13 669 930 ;
+C -1 ; WX 708 ; N Otilde ; B 40 -13 669 902 ;
+C -1 ; WX 537 ; N Scaron ; B 9 -13 516 929 ;
+C -1 ; WX 664 ; N Uacute ; B 64 -12 705 930 ;
+C -1 ; WX 664 ; N Ucircumflex ; B 64 -12 705 930 ;
+C -1 ; WX 664 ; N Udieresis ; B 64 -12 705 885 ;
+C -1 ; WX 664 ; N Ugrave ; B 64 -12 705 930 ;
+C -1 ; WX 562 ; N Ydieresis ; B 31 0 642 885 ;
+C -1 ; WX 556 ; N Zcaron ; B -26 0 572 929 ;
+C -1 ; WX 525 ; N aacute ; B 17 -9 498 737 ;
+C -1 ; WX 525 ; N acircumflex ; B 17 -9 488 737 ;
+C -1 ; WX 525 ; N adieresis ; B 17 -9 488 692 ;
+C -1 ; WX 525 ; N agrave ; B 17 -9 488 737 ;
+C -1 ; WX 525 ; N aring ; B 17 -9 488 762 ;
+C -1 ; WX 525 ; N atilde ; B 17 -9 488 709 ;
+C -1 ; WX 394 ; N ccedilla ; B 8 -224 370 486 ;
+C -1 ; WX 424 ; N eacute ; B 20 -10 460 737 ;
+C -1 ; WX 424 ; N ecircumflex ; B 20 -10 408 737 ;
+C -1 ; WX 424 ; N edieresis ; B 20 -10 427 692 ;
+C -1 ; WX 424 ; N egrave ; B 20 -10 378 737 ;
+C -1 ; WX 287 ; N iacute ; B 32 -7 379 737 ;
+C -1 ; WX 287 ; N icircumflex ; B 32 -7 327 737 ;
+C -1 ; WX 287 ; N idieresis ; B 32 -7 346 692 ;
+C -1 ; WX 287 ; N igrave ; B 32 -7 255 737 ;
+C -1 ; WX 556 ; N ntilde ; B 32 -7 515 709 ;
+C -1 ; WX 502 ; N oacute ; B 21 -9 486 737 ;
+C -1 ; WX 502 ; N ocircumflex ; B 21 -9 450 737 ;
+C -1 ; WX 502 ; N odieresis ; B 21 -9 453 692 ;
+C -1 ; WX 502 ; N ograve ; B 21 -9 450 737 ;
+C -1 ; WX 502 ; N otilde ; B 21 -9 475 709 ;
+C -1 ; WX 370 ; N scaron ; B -17 -9 405 736 ;
+C -1 ; WX 553 ; N uacute ; B 30 -9 513 737 ;
+C -1 ; WX 553 ; N ucircumflex ; B 30 -9 513 737 ;
+C -1 ; WX 553 ; N udieresis ; B 30 -9 513 692 ;
+C -1 ; WX 553 ; N ugrave ; B 30 -9 513 737 ;
+C -1 ; WX 475 ; N ydieresis ; B -113 -218 485 692 ;
+C -1 ; WX 440 ; N zcaron ; B -15 -12 440 736 ;
+C -1 ; WX 822 ; N trademark ; B 118 398 716 663 ;
+C -1 ; WX 900 ; N copyright ; B 66 -46 838 726 ;
+C -1 ; WX 833 ; N logicalnot ; B 124 174 710 419 ;
+C -1 ; WX 900 ; N registered ; B 66 -46 838 726 ;
+C -1 ; WX 833 ; N minus ; B 124 269 710 328 ;
+C -1 ; WX 671 ; N Eth ; B -28 0 624 672 ;
+C -1 ; WX 532 ; N Thorn ; B -30 0 518 672 ;
+C -1 ; WX 562 ; N Yacute ; B 31 0 642 930 ;
+C -1 ; WX 500 ; N brokenbar ; B 219 -172 282 699 ;
+C -1 ; WX 329 ; N degree ; B 26 434 303 710 ;
+C -1 ; WX 833 ; N divide ; B 124 66 710 531 ;
+C -1 ; WX 500 ; N eth ; B 21 -9 464 726 ;
+C -1 ; WX 547 ; N mu ; B -39 -204 532 433 ;
+C -1 ; WX 833 ; N multiply ; B 146 26 691 571 ;
+C -1 ; WX 867 ; N onehalf ; B 51 0 844 678 ;
+C -1 ; WX 867 ; N onequarter ; B 51 -22 848 678 ;
+C -1 ; WX 367 ; N onesuperior ; B 54 268 267 679 ;
+C -1 ; WX 833 ; N plusminus ; B 124 20 710 577 ;
+C -1 ; WX 516 ; N thorn ; B -70 -218 461 737 ;
+C -1 ; WX 868 ; N threequarters ; B 0 -22 848 679 ;
+C -1 ; WX 367 ; N threesuperior ; B 0 261 338 679 ;
+C -1 ; WX 367 ; N twosuperior ; B -15 268 342 679 ;
+C -1 ; WX 475 ; N yacute ; B -113 -218 485 737 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 280
+KPX hyphen T -37
+KPX hyphen V -56
+KPX hyphen W -56
+KPX hyphen X -37
+KPX hyphen Y -74
+KPX A quoteright -130
+KPX A colon 19
+KPX A semicolon 19
+KPX A S 19
+KPX A T -37
+KPX A U -23
+KPX A V -56
+KPX A W -42
+KPX A Y -42
+KPX A y -19
+KPX A quotedblright -130
+KPX B hyphen 56
+KPX B S 19
+KPX B V -19
+KPX B W -19
+KPX B Y -19
+KPX C quoteright 37
+KPX C hyphen 23
+KPX C S 19
+KPX C quotedblright 37
+KPX D hyphen 37
+KPX D A -19
+KPX D V -19
+KPX D W -19
+KPX D Y -28
+KPX D Aring -19
+KPX F comma -167
+KPX F hyphen -56
+KPX F period -167
+KPX F colon -37
+KPX F semicolon -37
+KPX F A -32
+KPX F a -42
+KPX F e -46
+KPX F o -46
+KPX F ae -42
+KPX F oslash -46
+KPX F oe -46
+KPX F Aring -32
+KPX G hyphen 19
+KPX J A -23
+KPX J Aring -23
+KPX K hyphen -37
+KPX K A -23
+KPX K C -28
+KPX K O -28
+KPX K U -37
+KPX K W -42
+KPX K Y -32
+KPX K a -19
+KPX K e -56
+KPX K o -56
+KPX K u -56
+KPX K y -83
+KPX K Oslash -28
+KPX K OE -28
+KPX K ae -19
+KPX K oslash -56
+KPX K oe -56
+KPX K Aring -23
+KPX L quoteright -185
+KPX L hyphen 56
+KPX L A 19
+KPX L T -74
+KPX L U -19
+KPX L V -102
+KPX L W -88
+KPX L Y -88
+KPX L u -19
+KPX L y -37
+KPX L quotesinglbase 19
+KPX L quotedblbase 19
+KPX L quotedblright -185
+KPX L Aring 19
+KPX O comma -37
+KPX O hyphen 19
+KPX O period -37
+KPX O V -19
+KPX O X -19
+KPX O Y -19
+KPX P comma -250
+KPX P hyphen -74
+KPX P period -250
+KPX P colon -19
+KPX P semicolon -19
+KPX P A -56
+KPX P U -19
+KPX P W -19
+KPX P Y -19
+KPX P a -37
+KPX P e -56
+KPX P o -51
+KPX P s -32
+KPX P ae -37
+KPX P oslash -51
+KPX P oe -51
+KPX P Aring -56
+KPX Q quoteright 19
+KPX Q hyphen 19
+KPX Q quotedblright 19
+KPX R quoteright -37
+KPX R comma 19
+KPX R hyphen -19
+KPX R period 19
+KPX R T -19
+KPX R V -19
+KPX R W -23
+KPX R Y -37
+KPX R quoteleft -19
+KPX R a -19
+KPX R e -19
+KPX R o -19
+KPX R y -19
+KPX R quotedblleft -19
+KPX R quotedblright -37
+KPX R ae -19
+KPX R oslash -19
+KPX R oe -19
+KPX S A 37
+KPX S G 19
+KPX S O 19
+KPX S Q 19
+KPX S S 19
+KPX S Oslash 19
+KPX S OE 19
+KPX S Aring 37
+KPX T comma -148
+KPX T hyphen -130
+KPX T period -148
+KPX T colon -37
+KPX T semicolon -37
+KPX T A -56
+KPX T T 19
+KPX T a -116
+KPX T c -97
+KPX T e -97
+KPX T i -19
+KPX T o -116
+KPX T r -74
+KPX T s -93
+KPX T u -93
+KPX T w -93
+KPX T y -74
+KPX T ae -116
+KPX T oslash -116
+KPX T oe -116
+KPX T Aring -56
+KPX U A -28
+KPX U J -19
+KPX U Z -19
+KPX U Aring -28
+KPX V comma -185
+KPX V hyphen -56
+KPX V period -185
+KPX V colon -93
+KPX V semicolon -93
+KPX V A -79
+KPX V O -19
+KPX V a -93
+KPX V e -93
+KPX V i -28
+KPX V o -60
+KPX V u -32
+KPX V y -46
+KPX V Oslash -19
+KPX V OE -19
+KPX V ae -93
+KPX V oslash -60
+KPX V oe -60
+KPX V Aring -79
+KPX W comma -134
+KPX W hyphen -37
+KPX W period -134
+KPX W colon -28
+KPX W semicolon -28
+KPX W A -28
+KPX W a -51
+KPX W e -74
+KPX W i -19
+KPX W o -51
+KPX W r -28
+KPX W u -28
+KPX W y -23
+KPX W ae -51
+KPX W oslash -51
+KPX W oe -51
+KPX W Aring -28
+KPX X hyphen -19
+KPX X A -19
+KPX X Aring -19
+KPX Y comma -130
+KPX Y hyphen -111
+KPX Y period -130
+KPX Y colon -106
+KPX Y semicolon -106
+KPX Y A -46
+KPX Y a -116
+KPX Y e -116
+KPX Y i -19
+KPX Y o -97
+KPX Y u -56
+KPX Y ae -116
+KPX Y oslash -97
+KPX Y oe -97
+KPX Y Aring -46
+KPX Z hyphen 37
+KPX quoteleft A -130
+KPX quoteleft J -130
+KPX quoteleft V 19
+KPX quoteleft AE -111
+KPX quoteleft Aring -130
+KPX f quoteright 93
+KPX f comma -83
+KPX f hyphen -19
+KPX f period -83
+KPX f quotedblright 93
+KPX r comma -130
+KPX r hyphen -19
+KPX r period -130
+KPX v comma -46
+KPX v hyphen 37
+KPX v period -46
+KPX w comma -56
+KPX w hyphen 19
+KPX w period -56
+KPX y comma -60
+KPX y hyphen 19
+KPX y period -60
+KPX quotedblleft A -130
+KPX quotedblleft J -130
+KPX quotedblleft V 19
+KPX quotedblleft AE -111
+KPX quotedblleft Aring -130
+KPX AE hyphen 19
+KPX Lslash quoteright -185
+KPX Lslash hyphen 56
+KPX Lslash A 19
+KPX Lslash T -74
+KPX Lslash U -19
+KPX Lslash V -102
+KPX Lslash W -88
+KPX Lslash Y -88
+KPX Lslash u -19
+KPX Lslash y -37
+KPX Lslash quotesinglbase 19
+KPX Lslash quotedblbase 19
+KPX Lslash quotedblright -185
+KPX Lslash Aring 19
+KPX Oslash comma -37
+KPX Oslash hyphen 19
+KPX Oslash period -37
+KPX Oslash V -19
+KPX Oslash X -19
+KPX Oslash Y -19
+KPX Aring quoteright -130
+KPX Aring colon 19
+KPX Aring semicolon 19
+KPX Aring S 19
+KPX Aring T -37
+KPX Aring U -23
+KPX Aring V -56
+KPX Aring W -42
+KPX Aring Y -42
+KPX Aring y -19
+KPX Aring quotedblright -130
+KPX Eth hyphen 37
+KPX Eth A -19
+KPX Eth V -19
+KPX Eth W -19
+KPX Eth Y -28
+KPX Eth Aring -19
+KPX Thorn quoteright -37
+KPX Thorn comma -148
+KPX Thorn period -148
+KPX Thorn quotedblright -37
+EndKernPairs
+StartTrackKern 3
+TrackKern -1 6 0.10 144 -2.09
+TrackKern -2 6 0.05 144 -4.02
+TrackKern -3 6 0.00 144 -5.96
+EndTrackKern
+EndKernData
+EndFontMetrics
diff --git a/e2e-tests/cypress/fonts/Type1/c0649bt_.pfb b/e2e-tests/cypress/fonts/Type1/c0649bt_.pfb
new file mode 100644
index 00000000..b5d4ded6
Binary files /dev/null and b/e2e-tests/cypress/fonts/Type1/c0649bt_.pfb differ
diff --git a/e2e-tests/cypress/fonts/Type1/cursor.pfa b/e2e-tests/cypress/fonts/Type1/cursor.pfa
new file mode 100644
index 00000000..24e674df
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/cursor.pfa
@@ -0,0 +1,954 @@
+%!PS-AdobeFont-1.0: Cursor 001.001
+% $XFree86$
+11 dict begin
+/FontInfo 10 dict dup begin
+/version (001.001) readonly def
+/Notice (Copyright (c) 2000 XFree86, Inc.) readonly def
+/FullName (Cursor) readonly def
+/FamilyName (Cursor) readonly def
+/Weight (Medium) readonly def
+/ItalicAngle 0 def
+/isFixedPitch false def
+end readonly def
+/FontName /Cursor def
+/Encoding 256 array
+0 1 255 {1 index exch /.notdef put} for
+dup 0 /X_cursor put
+dup 1 /X_cursor_mask put
+dup 2 /arrow put
+dup 3 /arrow_mask put
+dup 4 /based_arrow_down put
+dup 5 /based_arrow_down_mask put
+dup 6 /based_arrow_up put
+dup 7 /based_arrow_up_mask put
+dup 8 /boat put
+dup 9 /boat_mask put
+dup 10 /bogosity put
+dup 11 /bogosity_mask put
+dup 12 /bottom_left_corner put
+dup 13 /bottom_left_corner_mask put
+dup 14 /bottom_right_corner put
+dup 15 /bottom_right_corner_mask put
+dup 16 /bottom_side put
+dup 17 /bottom_side_mask put
+dup 18 /bottom_tee put
+dup 19 /bottom_tee_mask put
+dup 20 /box_spiral put
+dup 21 /box_spiral_mask put
+dup 22 /center_ptr put
+dup 23 /center_ptr_mask put
+dup 24 /circle put
+dup 25 /circle_mask put
+dup 26 /clock put
+dup 27 /clock_mask put
+dup 28 /coffee_mug put
+dup 29 /coffee_mug_mask put
+dup 30 /cross put
+dup 31 /cross_mask put
+dup 32 /cross_reverse put
+dup 33 /cross_reverse_mask put
+dup 34 /crosshair put
+dup 35 /crosshair_mask put
+dup 36 /diamond_cross put
+dup 37 /diamond_cross_mask put
+dup 38 /dot put
+dup 39 /dot_mask put
+dup 40 /dotbox put
+dup 41 /dotbox_mask put
+dup 42 /double_arrow put
+dup 43 /double_arrow_mask put
+dup 44 /draft_large put
+dup 45 /draft_large_mask put
+dup 46 /draft_small put
+dup 47 /draft_small_mask put
+dup 48 /draped_box put
+dup 49 /draped_box_mask put
+dup 50 /exchange put
+dup 51 /exchange_mask put
+dup 52 /fleur put
+dup 53 /fleur_mask put
+dup 54 /gobbler put
+dup 55 /gobbler_mask put
+dup 56 /gumby put
+dup 57 /gumby_mask put
+dup 58 /hand1 put
+dup 59 /hand1_mask put
+dup 60 /hand2 put
+dup 61 /hand2_mask put
+dup 62 /heart put
+dup 63 /heart_mask put
+dup 64 /icon put
+dup 65 /icon_mask put
+dup 66 /iron_cross put
+dup 67 /iron_cross_mask put
+dup 68 /left_ptr put
+dup 69 /left_ptr_mask put
+dup 70 /left_side put
+dup 71 /left_side_mask put
+dup 72 /left_tee put
+dup 73 /left_tee_mask put
+dup 74 /leftbutton put
+dup 75 /leftbutton_mask put
+dup 76 /ll_angle put
+dup 77 /ll_angle_mask put
+dup 78 /lr_angle put
+dup 79 /lr_angle_mask put
+dup 80 /man put
+dup 81 /man_mask put
+dup 82 /middlebutton put
+dup 83 /middlebutton_mask put
+dup 84 /mouse put
+dup 85 /mouse_mask put
+dup 86 /pencil put
+dup 87 /pencil_mask put
+dup 88 /pirate put
+dup 89 /pirate_mask put
+dup 90 /plus put
+dup 91 /plus_mask put
+dup 92 /question_arrow put
+dup 93 /question_arrow_mask put
+dup 94 /right_ptr put
+dup 95 /right_ptr_mask put
+dup 96 /right_side put
+dup 97 /right_side_mask put
+dup 98 /right_tee put
+dup 99 /right_tee_mask put
+dup 100 /rightbutton put
+dup 101 /rightbutton_mask put
+dup 102 /rtl_logo put
+dup 103 /rtl_logo_mask put
+dup 104 /sailboat put
+dup 105 /sailboat_mask put
+dup 106 /sb_down_arrow put
+dup 107 /sb_down_arrow_mask put
+dup 108 /sb_h_double_arrow put
+dup 109 /sb_h_double_arrow_mask put
+dup 110 /sb_left_arrow put
+dup 111 /sb_left_arrow_mask put
+dup 112 /sb_right_arrow put
+dup 113 /sb_right_arrow_mask put
+dup 114 /sb_up_arrow put
+dup 115 /sb_up_arrow_mask put
+dup 116 /sb_v_double_arrow put
+dup 117 /sb_v_double_arrow_mask put
+dup 118 /shuttle put
+dup 119 /shuttle_mask put
+dup 120 /sizing put
+dup 121 /sizing_mask put
+dup 122 /spider put
+dup 123 /spider_mask put
+dup 124 /spraycan put
+dup 125 /spraycan_mask put
+dup 126 /star put
+dup 127 /star_mask put
+dup 128 /target put
+dup 129 /target_mask put
+dup 130 /tcross put
+dup 131 /tcross_mask put
+dup 132 /top_left_arrow put
+dup 133 /top_left_arrow_mask put
+dup 134 /top_left_corner put
+dup 135 /top_left_corner_mask put
+dup 136 /top_right_corner put
+dup 137 /top_right_corner_mask put
+dup 138 /top_side put
+dup 139 /top_side_mask put
+dup 140 /top_tee put
+dup 141 /top_tee_mask put
+dup 142 /trek put
+dup 143 /trek_mask put
+dup 144 /ul_angle put
+dup 145 /ul_angle_mask put
+dup 146 /umbrella put
+dup 147 /umbrella_mask put
+dup 148 /ur_angle put
+dup 149 /ur_angle_mask put
+dup 150 /watch put
+dup 151 /watch_mask put
+dup 152 /xterm put
+dup 153 /xterm_mask put
+readonly def
+/PaintType 0 def
+/FontType 1 def
+/FontMatrix [0.001 0 0 0.001 0 0] readonly def
+/FontBBox {-484 -517 517 484} readonly def
+currentdict end
+currentfile eexec
+d9d66f633b846a989b9974b0179fc6cc445bc7c8a959a39a32e9dce7faef17ee
+3bec9f509ea0bb8adf2cfac3626f0443046fbcbb211c518d48ebccdd3cf3d833
+4877f4ef1dab34759b6d38f757cf015a5ccf12d00a3239084905ecae5ec9d399
+ff3b917ce3cd4e85a1c49a750549d42f00f90f5110c822aa441ea813981685af
+17a0e41240e5c924b9c12d51f92ed0f3ef4fbc72c8b3eaef402c3befd6a52631
+47ff2e91ace4fcc6f381161f004dcd1db73271726d2ad25185a9b1ad0097955e
+b319d5f6d40a9f7163a0e106a361ebf256f5d931e06b8d9fe4e84e785bd29b7d
+fb70d68d41d66196ab5099bc38e613d11db08b2e9194806b651615dbdad9c797
+4bf4ad35bd0ce5d087baaee81b670d70d72475c0236751e9f2ba029abcb75682
+ffd70e77bbc2fa605cd824d77659de79f11275b5d6185386f0f39988ae683aff
+876ab78964fff6118dcb35b502ed5c7a38c6e7f898a352c307c6ddd0558702cb
+f9c763be246b45355d103ca1bf99d92e0f7743000d0019d4e66bac2292422b81
+7dd20274357d55680301b21eeded828c8836a5c47b12daaf00bdecf88289692a
+da641e1893c2eb202c614afd6b70524c9e2d6385de26e6f48a5959f587f80a69
+5401aeaae3c78d5928dc524e12cf874a89961e67d9d71db9aafbbfd960e6c395
+1f0ba3a4af64ec2379e956b2715829f009faa9fedfc79e06559cff13a4c8b860
+4a07ec9c6565dd8530acee97e2d2f0b92eab28e5299648f00bfedf9c87bd0223
+02495fde3f194d735815acc2e09bf4abc693de6e975f0c2b6b0051c90d727988
+c6a90a3dd10bc83e264984752520880d26f1138dddd805001ec71b935a8461cc
+0d6aa7cb0cb6c7c7cbbc99af3a513ac06848f7e6a222363f8696cf4ba2409054
+9b616ffe2d893a8f5066e6e7cdb21656530c2a720578af66d456de1723b3dc3d
+1406bbf7dfeda3aa3f649077ea7ec19f8bb18b28b876ce1f8e61dfc50d7b4db3
+968efa987e8479766356441906d6298fb426a428a1109ee9058d4a71235ff03e
+205a60f64b77d3e0920790200fe13d6676c77f78e4ced35f23912234da3b568d
+e729baa323bb7ebb41d72994261eac9f2c02709de342ae5c070d9e7b394a3ca1
+aad2e95d98c05cd4696759cdf12d16c3e000f3b2a7b5ea03d3fbb204c6d59b48
+e4dd5094a54aacd3f8f62d63b93e3524bdfa95fcbf75fd8be02544bbe661c485
+9f4a12191be62ca8b60cfcfba14097f4f7daaea4af8675fdec8f6ac22dd822e6
+bd4cbb2380c0ce2f6e7e4db197ba213942cefeca436f33249a881c60c77e58e0
+93100c28caaa5f861d8557cd2779911d42563f5fb8f7c890c8e24bebf300c3f7
+15d0854bb460462de7364c007c0be17a57c23959bda2282f9fa8bfc1571d7e2e
+5cd033129735be336dfe0efe4bbe19669e7c1a3f3d21e11c8ca081ec442fcaf9
+e4d5a1eac515db6b686f9da7757475c7b47cd2dafc56aa63208f5e0bdc127a3c
+51b9bd6796842d3089641604bf32ecd04874ecbcaa8c4be805cbb6bbd3988040
+8505740a2023924894097f466e1736c0bd795cddb95d80937d662592c70763b8
+97a8dad80e3467ee8192d4d8667cae0ec40a5bc9192d59eece9a405dd140f988
+ecb324c75704e502d088b5184cac0ee0857289ec1bfe9a2bee359911b9bb30ea
+8daade18b1ad9ea1c28b2d9aacd6e93833d9d8acb46349225b4d6b0e32cd7848
+fe8ec185522eee670d0cbeaaeaab94fe7904232249027292be36d2aa06ce4b53
+adb71f29d4746fe19cec148ada960820ec7eca5ebce18f19de5a7974b0d15b14
+2c0f1d0af773b4bc5ab79c7f16747b468ff0a9ce39970cf439720bab853ae9da
+c6cd1e282d0860f012ca867969474e3d396370a876dd9f603a6b64afcccecab7
+dc0186a089a815288a68564f62db84d3a295f31efe6c1b40d1602e40c8fc8ab7
+713c11ec7f175b31389f70bd3255ba32c10b2db0215aa5fddb1c02f74bf8c33b
+d4059f1f3640a4dbcbe0fd59bbc830881326e28b7c8d5b9704f95827e5e21d8a
+f720e3ce0c1bd124963ea970e1c4eaa6d340eae793aff5f8257f471937147c59
+fdddaeed8b547b3b98adf6b5bc81a86dbf9436d010d14b3b5ecc4ed9aecf3ffa
+9a4ae3b08b9cfd79370f4c6ade8406e7808ab8fe30694ae7bd9008c3507786da
+543465aa40f8ddcbcd1d561a5871be7f39c95ecb0e3fd7754469b1103c71d18d
+446ab34f5ea212f0b5c4d3fa8c50658466d770adc17f53706ff5449729b9c23d
+72cb4ec02402c38924dccbc49c976312dfc8f086050dd5a38132eb139b43422c
+756a95879b56babe8f77771f221f4f1f25d1a308064ec10eecfbb306eb55ee78
+2692db518eb3d477c4b52a182fe897e45c0c3d302aaa0fdc69ab3c26e55728e5
+5e12e7a51d28af01f104e5dbc61bd210daaa526c4dd9a93abf8867178cfb178a
+70dabac26de06f5abb43392964a7acb193fbf31070de491ab477a4c5492f4fc6
+4c3bc40d760d41ab31d8a3ec657814086a133801701ba8fc4dc5ba92b03b463a
+0f8bbd73b9bfcd6eba567e0c84b5ad5b32dce09e5edf39bd276d75c3cec89124
+a4a8bdf208fdc212469bc9ac96ab17e9df27ddd8b085dce1ba68ab50ea445bcd
+0f002681a8dd517660794d96bba265fbfeef267d26fa2a1bda94235f763209c1
+1f817081f930f20d2d0cefb9261e22eea39cd0a390626fe71d312fc0fcf11db4
+6b0b70010dfadb9c7725f419c1c89f34fcbcf3b268c7a4f838d94cefd82685ec
+b619d3f6df7571031a82e2d2a8bb98b8471d8c9febeb853ce0d38d7747c0bd63
+fe04e36dd00d2ef30b0c842d2a59457b3b41e2b8cda904a5d281290219814b59
+c4a75469375e9e0b3a358434a910bf1774657bbdc64cd408c680e2a09ca26507
+06f95e8c16735cdcca195f20c9a03a2a0ed60184c03f4e3478859c6639ca8ac7
+241b33432661741333406add1d9439690f07564d09f4b67152f32943cf93e247
+3f2a17a476bfd897ff69d4fefed1f4a994fdf535e3e45b1a3d46d560d5ac55dc
+2e4c20408c8ceb82d7bc21016bfcdddb8d5daa9e902c327261549ba62090dc4b
+27ad160070880cbdff5d022dc9c7d6f3f51a49af6dfb713308dc590c7dae5236
+74d7946c1db33177e4caa604ecf655b792be55e14272e0daa0cceb2afad8f3b1
+6558426e102f72ee42a5d9dff66d65d672451316fe551fc2923e4b24d9788091
+66457f55f9c83350a62bc5bf590f7135929b365c0dd003a95e98760472ce988e
+0e5affd7c654bb99eb4255bcf030fa008bde51daa55ed736086953c599bdc3be
+16efb2368acc883bd670d753fc7bbc65ec5bd797d0cecc14ac15f6423f0f22d1
+6b630dfe8736c2db51b45774f99280ea1274ae1c207ef3574e55478f68bac3b4
+8e24180f8891a2f3e8c0c9f74c22744830f1617b99a78feba5fbd752134775f2
+5ab542c17ce32465058ffb9e3dea8030dfe1ec82a1818c9bd1e3db30e25d8128
+9ecfaffe85bdcad5126fd461b6d5137d2b15cee2d97486f3266e4c49964d46b0
+db16d96593bbf257efaf0dc10f18e2f4f14a347cdcc220e8ba87eb215a66030f
+8f30d405308dc15ee9f7d7c92c607140a6bd1cd8c8274a507c5f12293327b9a9
+e5c3a13eec859919a805f12aad2f1c665d054a17fd4dbff471890585a35f4e0b
+5e64c4df9b687526be10da6eca391eb351ae661e16b930ad77ad33ad2a2777a4
+ef8881b5be91a83cbffd8b95a8f7c91fd86c71061e7717e4cb2f44f4ba2cf5ba
+a64c4f7290690036427ec227446192efc7c14479525994dd0e86e7bca081a9bb
+00d55ec51e1bdbb504c4d43463902c05ff0c1d67f3125b8ec48f5d1d394c7f13
+54e70eee5ec074d6a7e0dd6f3d2f006fe8a527b56a560685cfdbdbc9f5c905bb
+1dd508ec7829a13a2d49cad2db823dfae5b85f3d7c04c0d8ba1fe9878964534d
+c23e890fe460c0b25700c5f6432f420f3698c6a655296104eef778a5b125c74d
+79ca60a8f987e278421b0d4a311bc4ccd59fc3f85972091516c8a4662275060c
+8851c2532e2a1f72dca5c350a03fe829db297d0accf6458b10836d22cc461b68
+452cd2f3764a3abbf2314105e414fefa39686715463afcb6fb6782eddd94781b
+5130a77d16bbf0689bf64fcb46c14682df23e70f7a571aaef0aecfda9c889562
+080d3bf39422821ae0093fcab7bc29f9c4281eac9ca14454adf3ec397855c008
+fe7a9d64ad134fe9122f7650e37c17bce7bfca7661a0a11b45b42bdffff2decc
+47df5d33671daa487d7bd6a5aadb32e7257dc8d0feb9b7f0a4a7825fdb8a5cba
+a31a988477486d0f09858c772b8c468b794ab2db0defa88c3b4927b615bb34a5
+8ac9d9af50a150e991e6f4330786f8a4f98654558a4fec8144eb2e46a6190b4b
+5043ae396e7fca1eaea3e9a96c0b200826cc065f49c47c2797ddfeecc6e9b95a
+f2deca74b594224dbf75041f00fb37f3aa70b0b0eceb4343a9331e3047e86548
+c3a7b1a42e7af0792e02a5bbd2ab548f7cb8cd112babb89213f66913dc109d8f
+c1fd5307e7c20d7340372935cc459a795ebc9c4ada9136b71fdb3f68aac98729
+16052f949b6657114a04472fe931a761c4fc5f9b8ed340c16035889172f7669c
+c6dfcd50bdf910c615e5f3c64101e2e6546f252cc4e0ecec03c5e19249b37269
+a0bd2e68296f9627e4ff72e37bf3c2be74e3b34d259aaecab472a595af08b2fe
+db51394d6b59c390046ad76dcb8285a32c247c7cd181d2e386c6d5abdcbb3789
+3e8d9859a02c13a72743047d8880606488a521185ca446499f7adfc9a25c8ba5
+1b1c84ba24c0612ea3d957e76bc398457821e25844bbd906defed4d3ae434c39
+9e52e60fedecc1acf4ddecfe92452e9841eca92acf99c4ca9c2cb32afab1dc46
+d0ad255a851a78cbebac54fd314846c35d35cb7ea10596734ab79be35e11dbb4
+9d30fc29b140d5823f429eb8eb3d1fabd90b9c1509faf33f609b3ae40a48f873
+763b3a5d5c9236aa0a2be1661be917921b58ba7fba139796d4c125127885779b
+6c71427cdc5812dbb6725153a83809446d26f9bc64bfc2a2a4bdca0c57a20918
+4415951c5d56cff5a3824ba8f8c0652e91a5d9c04d1ed0d928b33801b98d36bb
+04af8348436aef4abd549a063eaed12f7a4655f5183515b067eccf550f7bf906
+abf1dc63139fdb1e8a1d2472133fe920777df1acb2d12f5b655e568bd42e381d
+861daebf84ed9c6db8d307de2bed017dece82881ed7677b5f26dfa34ac25e19d
+5837f76dcaf237de6391c1d6679dc1a981737ea3e07e729060a0472090d5c76a
+dca9262a3823f24b1ecb646aaedd7de4a319f091a948935480d53376703049bc
+a45851f924ead472f1b100ee7b4b388c6d37e6e9cb850ce7da81896c1ab68677
+c3298a9e5aa71ae6379990327ec585aa1e291354e44ad972d3193424a9805226
+f43b07992c5f1c5e01cb5d049ce2922dd2921ddb5c4dbce968dc7a340ba0449d
+11ed4160c846a508b5227bb5ae574c8c7dc5d43a0db7a0bebcc638814f8933b9
+03404c4e720929833b4a5d5b3e54b806adb35724c2ceb47d77d39e43465e7a12
+51da0d53106f41313f92fbf9cd992bcfa4d13054bb7154965dcf530b2e0cd18c
+92690f54a6c6d7bb38ff4c59d8e35359eb3ee07061356ec6506b2081878986de
+80a2ba253a34519189e706fee9906a723bba749cef1c15abe3653eb31c59e486
+a6191335292bdbba1c442b229b56e7180be446a319e811327ed75f939936cb0f
+56e123a274c843f27b94b35babf938717b4d02c2a673df0a88d35c35833c4d84
+1bc1d5cfb8a00a181cc1bb68f9dba8fb10ce8ff9a520d938ed21fefb140b5634
+8f2c237d2d1701e50d3132ff93ba3c0543694c83dd500000b829b705e3268ec6
+f94887386593f0e629f6e74d3cd192a48c20d0b1a00ec7b18aef659a62777caa
+3692512ea0efe4b8a4f2a138631b10b59f0df0fe27be23f2d3e1b195c2375be9
+6e307b3b8404d2cc6f798fc995c1547354c24e04cbe50d273b50967dfce4ec7c
+ba9bd4cdefee3ff4310358014dc02ef0be941c4a8273cce80ca9b8f75eef61be
+854b698a2ca1100f40987aa622fe8fd1932984e77e8c9781c691734caf0c4e86
+566def79a60cd4d3a3cd79f59332ac99315844424a627e2d6d38b5d6e81838fa
+357f0d137804d5a29abdd374afb2e3c4f5901cbaa94b8d15a5a8988a62fb92a2
+e06cf8788f37a1aee7adc0e67626d0de2881d822f940dd658b5f310a1c4f1bff
+cb7fc9fdb347ad0372127ab143e4d80657e5f0c364f4721d5d68ff624705da2c
+27090a53f2d481492c3b15bd70fc050f909edbc2bbfcea26ae850abf2c2bbcbf
+fc4ab4d34d006a1a41469e2c8a8f5b4ca5f9820d105ca12f1d1240dd32d955ce
+ef9de1e096ba539a3fb11415bdc0851356a40e0571a1033aaa55cc3f618f372d
+8c71ec445768cfc42b2c10c04e45e98a649e770a324defb330b84952d46e5287
+65c417a6a82deec37727a517c12bb296eeb5e6c285dfd16a479369bb85289a0b
+e24254e6ab5de4e3a0343e7f901c6e39d79902e7bf437087fc894556dca62fc7
+114b344d4052b3d7f84b07ef17199a938f69b2586ce6a9bb840c7dc1336e185f
+6264b644cb411a2eecdc43e1dd26ec51a837401cf78dfad608a3b9038f9a3a40
+9fc464ff2214599f8ed573112ac944b593ae83348f92c54ae5d6db5956de20d7
+4d48f8f6c685a70265766a97367ec2d7c6e1ac00c30fd3b0fa4d2891d013d840
+693f61956cd4dd4ad134886868a55d9244f58c27f101b321ddb99b4e2eb23137
+a2e596ee6d131304df4265d6e72f9425127729028cf2c3375a4bba7ca9363e30
+9278afe6c282536537034c787be5e9ef1a0053c64b461ff496050cfc8da1182b
+c1abe0eb88089e2d4a994108b1d24a2205f33f3293954b8e8102be94aee1e8f8
+d725d52e87e57a2de1f79c68ca6b5b36d05ac31c2d2f73cf5b955b602a1f2eb1
+6091edaca5e90cf43a1fc23a19e2fa06eeba82b15704ccb5b1b93587b2762517
+abd0e6375409f518e74d727b145daac480a52602ef35aa037647d7c0d403408c
+095daa192800fcbb56e5af904fb72d069f6a15d4f95897f530bff36828630bc6
+e4f83f82a6f0814a943a49fe2288eda1452db8c48cad67b1b6398e481ca1835d
+b0f9f9257fd3c6aa11a7242786b47fe196cb4860964cba7d224acc20d22fffad
+c9e11d0c6a4487b53654719506c04b2e1ec53b3ca316d5232b821e5256fcbc7e
+7ee284e54eca74acb23fc3efeee59e1dabdd4e9c46c7a4234f7e3e5784b3e65d
+32ebba3709c63dc9ebc2dd6891e39c87bf846c3fe524e434d98e2ad199230432
+c84c95422e8950c4323caff03a9de5daa32a5ad3d07b5ee831bd589c0e331888
+33ac8dd264a563457fe1213d23d0d14536ab8c1e75e5af941e2068d1ba9a74d3
+ada91585fde5c2797229599749c420c694108595966b815c96adf3328894e76a
+a0cd504efacc06434c27cbcf96009947a0f80315879d2108bd1611d6f4a5455b
+178b5a85b5ea739546dcf226c6889dc02dc08b5dbad34b702fb77b82069777fc
+3892034144ce7995712e3295cb7e31095f430622eec4117d98fcfd6075a4f0f3
+01794fb45f3a05e725588fccca124eeaebce479b9a835bac7b113dddab302dee
+6c50e47c8e87718b3867c99025be81d05a905b2a9335068b59de14733d656b89
+97d21bb34893b9548830c45071921d0f13add9d9b75a6f38ebffe39f1d2cc4a2
+2482a80bf04e13b0e3614d1326e97e86b3dd77757764a9cbf6a0eaa530450928
+0c5926cf98b98bda505da3a35684fd0fe656e71aa67ae6e7693d3ae11425e6c3
+7fe01890b287f354f747e4de8d5bd48451ce4f28ed4bbaca7fac03eae6525174
+6e75b117e4c7fddb6979ed288f81053783f49f4ecf3af9cda24a7dd6647368ac
+4f2b8108451496612276dd42d7281d6365810a5ef3cdb304fa2630432c5fed60
+ace8924e807b54a77d63185e7c7ec65c887ff17a3633322c0d4c8367affe2ab1
+0845fa3f85262ed12a0ca9c74cf1c0f87ff1233868b83238831f911e41845abd
+73a346e7ac01c2b07dac7153de91318619f576a5ff1c0f9ba90315c5e89b8f37
+cb2d8d413daf121e0c1b3aa7bf1a62a2ce28e39d7c43da5275e8f0972aa73986
+98dac6dca24adbaecc997ad0488bd9cd356dc86c15756ecb6fe2fac61d4553b3
+0b996e98157e29e174bab3af12176094b414bdd13f4145144f087c349b8a7042
+b1e5b9e705c93ade44defd86ffe43bb513b3254e8310ec64409e79743c2f897e
+40270fd5154140e0e65566556892c49762a351845f767621f5f5e5cbe0b67109
+84e30ad39832cc5f8857c23aa7b19981ffdec5e4497cb52da24fd1ebb53855f7
+8f87823ff65f6650f362d6741f496c21f8db8a609af33f6e60f858e7bd5ab91d
+9ac64226eae4994670acf610130e10e7e3a6b1e6f3a07b87d4e6b712d243a493
+d5280549b827378ace2f866acaf117f9094c2b15c2430624d2f1c9a8ed3c92aa
+7c3ceee15ef6eba0e90686949aee9f375acb97a49bc96942d20abeae79fd8e04
+fdabe92b3c8b493f3d7efb13ca6bb9b8d98c41fc4c4f8e171dfb2ac038aba4e6
+60724b873c96a9e7b7b4edb24f09e22c937c8068589afba95bfab3c0618aec42
+fc7b68f1fcecea13a54f421c23e2df1b04785dd471f82587616fbd140758e21d
+17a1ff6dc30b301925fb1d2956cfe57f8b1baa1ccf4d171fe03c9336bdd0678f
+4640dffdc2ea1834e5eebfe68d4c26c9d009cc04d0cb8ee8f4f0bc477df068c9
+95eb41e087a9a4c390ac5ecc87667aa713ada2cc1991262a20fcda33625eb9c9
+1f419c6723f2875f40de2258fbaeb821e2a398dc0ec62119c4efc1e7da92fbb6
+c0175c56d32e975115a12697fead8852b8fb75b23cef1bc53b8deaf493c7cf1e
+9e9a9ce177bd9a78ec55f3dbfd247f9367974984f31ba071365fd79bfaacb882
+fab5934f730081165771963587130b5a1826b618ee51001ea81ee3372507ed03
+545d33d977d4bbdd73e0d1f235e7a2e020a28759dc53d383a980958ea0e49865
+b3658294acf2a04e0d93bc0806150eb192c8fd02c5dda1fb457bbaf0f71fd063
+29b87327fb05fe02ff88f7850d17b65ef608e7857d2876fb37fb0665e634cb39
+2ef9a4e3751103c087fa26998f68fe730c0ae114e34a3d412e715c7a12df3b56
+862f24f1fa06f6cc83769ba4d310467b07a91ef2dd6bb873deee11f1f326c40d
+89aeeb8f7ab1ba650444b2d3f077dfcf18803f38942f3d30f22d93cbad14fe1c
+1d968b62b018525144a5d2ed4bc636a0c9064940ef84d9ed682004774d8b09da
+998824b6cd1e7efb724fe553a8566b51bf2c6ec6f949501ce0bbc54a9309eaa4
+378e143ce3e035d57beb62873b294d07b920fece0f58d5ad1c5c4c67d3180a3b
+33eae3ce510ab906c841ecd9366ff615f07a905df9810a163ddf8d681920bd96
+d7b2212c74a05cea57e1e474be64eea47b8433755d72e5c1f79b8fccbcf7106c
+b70022721bd1bc779bc3f59301fb393d4429bc19084e228034291413f21fbd79
+83661ca0f6d331c1657fa69e2117464b8fe708520655c58b3f346f9213630fe0
+a31a55ecfc61f5466ccda3138f52cbbd575a56d4abe425266cce9e490d03bdb1
+0f9f959cbd9e34505dc46b9a439167931828d18f8e2b8a0b5d33b4bfb64df5dd
+b3825d34568453b740127811f4ef2995d0e7a09f29d432e0e0097343d5726719
+b04a72b4a5e0aa2804d6f7e155e1f89a168dfac8d593a8af33c075e213c0ab66
+366fe474ddb9ca17e6e7bce12b473ede15fc938239f21583bac0d6951648cc94
+cedaef748a18fd237b8b50f36fa97dad4b0a2b9e4bca1eb243a60b0eea59df8b
+74c76ea6067e77c0fa5b0aa43330690462851f923f6503ca2076f6b3d10483d5
+a1e8871c08434f6f2707f7cdf3a68d7dbeb4f2c7332d01435e37787877a7a29e
+a59c1493f2a959981026d58da06923d04c47502c7b5bb7f151ad5f0ebaf72c93
+db1db631865b261af7835ab3b596e4efa81cbaa8e6ec72f784e0b268f71d268d
+517cfd39804d0759486f8968ec149b1952be695da8553900c7e7c6878b63c664
+078dde438b8c75888e32c0d3285f6bed05132ff8e81e393b8be800662356b8d7
+b0fbff19c35c9ba9326b36586a179ae3e8869af85ca655406e1bd5a36c94ada6
+f280d1352577263444122341f71b118c95ed413a1237a72a78b59d12eda3da5a
+3d9fa119572142e43ae69551a024fbce71d153a1a1c211288f878bdb3f8a9502
+4a6420b3ff69f0ffa246d5ae44370a24576118dcd8c5f28f6988fd922ffa8cc3
+af68ab7dc10d2c3bd5900b05060b984a063bfe7b03932a1a4427de62b8e4bc21
+4ef0c83fe86919a766478406f57d148ddc4ae0500e6e15246e31dcf0a7149ff9
+1800ade772d44268a63d11ee0f3f8ad11b01cb96d04a52d57d827fd931e7aa12
+f31373e2de4027c293a1329311510a63a268af6d73bb839ce767b3bbd4da9680
+4e36dad7495374b17f506f899ff61fe23f71310b8ee2f530bdb6d8c951fdb34e
+8fcfe71b12a555f174cd72428acc96eefa57652a2f5dc1a446d9f363c7994c53
+0159b89ec2694fc59cc9e083f506698db58d841d03f3961897168b96a3965a92
+500e29b74743e35681fcf93debff5785569e3b0e42b2b586dacacb72ceb0dfa9
+6c2aa8902fb26f1c3293177dcc7f70b54d85cf1ea4ca1a980e0e053bedde3eca
+0aca76c214fbed2982cd51892bdc35baa7e3ddd34da26a5b9302ded1de8099ee
+0e87410403d8f782f46987be974b25ad7766608525de89cd18bb00f831c8b3f0
+54db8cb4ab0ee2a9b1e63c8840ece040c3d7d763c7f65bf4ffcb33e7ba98679e
+2f2f77ea277314b42e94e936751b1c649afe60fbc8cbba2655c4bba13e622e92
+4f72a5612bd507388c7cfc8077209c68c4252a81f8efbe8b8f9799a947594894
+8a1d1087bd458d2436f837ddce87ebea385e3c44f433f2b775f6257b1f80dd96
+2bbb0fdcc8c78d32dc1e09521bcd9810fe25117326f2ba159a60c26d5aebae75
+8d8a422e14feebf2cc3d30e6c70e320d3448eb3edc08ab72e44824e5175790fb
+4a6401326b5ffe81fac99839bdc39a9af68eea930ffcb80223bf21e301c531a6
+2a523ac9985cbac3bd9e426dd9cf6ef112817313ec460f8b90a4a2349f819d74
+3444ed4872acee556af837375eff1b44dcc310f4ef67758731f7be7c1cfec351
+ada83a533c06701f63d2d8b7b2084afc5e7949ff250340351eb4b2886d16ebc9
+7cca76190f3a4a58995fb281a0dae019ed92b64190656f7bc5bda4b2532c52c0
+aa2d5ec6168e24b120ad5ccfc0bd31d631345c2e2cf3c7fe1ef9ca9cd4322b9b
+9d587204a24e18ae280b2ece030626bbed9f66f2979131ba38043a92d19429b8
+53e3475c7b933e054616aafdf0d3fb6345ef888e3fcda12985c2b2afeb2e59ad
+ed76cd456c6ac11d453deceb45655fe71a3ad5f5f770a7124a2976639a0a5847
+b79379244738b5a7b176207080dcbbda96f43a68370b434b348ab5631dbb1d04
+1f1082c04c564b44191a51afdfb3950c2b65f9c4ed4afb5f4bde68fb7a09d710
+4bbaeaab1612c1a0b1060fafeb385da056099a2765d69388d9a276601bc13a54
+fd114f2ce447e68ef452a6703d6fad6acc3bb591c3da0581c45de6f2edd02422
+aaed358aa940272865f7f93acc28460c8f0f5222454c05fdd3eb100219113eef
+5b0c02314cd29c2ff20f4e86b7866ddc9b8eca4c7256e9434091319ed99f9446
+f606dd0fe2e5b86e3237b2af55df6bf9624b286a1a9970759fe83b3bde380b8b
+df7132016d4be25517449ad88c5e6da4e292da5632eb662a898bf2c76714b963
+ebf7e7cfd268198ba84cbbfecb7c3fd01612f7c6a9f118aef4cd59307cdde680
+25d9f98dcc9542ecfe6a8bd8b2d09caf77f29e4644bb7b6ae295277eb8f5b733
+617f4ed5a2111a5d9eff858db8ebec3e59e8fe318c2e7a474f07c9f4234c9add
+fabaf11cac0a958be217bdce993dc99865333aff57576ea0e43fc70016c23077
+8ba456546e8ffba24581ab50e72fc463e2f524a07cc6bc11a347584d4c9f40aa
+8fe8a6873b680b5e7871b47e1103a77f0fdf2110b9801e8f24ca8218e4a9724d
+021144c9449ded25b0532a33d410bfef2382e1ef629e04ba1b5fc09d86b85481
+bd8b1f1d719cb01352c49e1cc54c34d115a6c1d0f07a83afa2acea650ce304bd
+919eda435e6f5a044ce65aeba0884f5099d860fbbc90dabbf0ae5cb1dd59555d
+2ada2f748f3a74010519af69b8ffcbdde7e88de7ed1b0f53c255cbfb8e41fcb4
+8c80603c8be3f3965b3af9f94b027dac3754764f75509ca2f554ac1de60bea69
+a53d7a9c4973b51a41aa7c0386117b6353555b31670c803db4e475c330be39da
+4d10f247ecf2992f3e3b35cc56bc19ec40a3914da09c914005419448a633518e
+3d4a0ff8c39f6ea57917fc87f890ef834dd2c6531b3fab0eba79e0f580c19291
+9974c58cc1320d8e66765886ac966a55e594abf6fa69fd1ef3ad7dbe2d964495
+08dac3b161a45e836755af3ce6b0c3e3247fa56d71723e41838bed42de4404b3
+967c900ae419a7006f0832bc24559a56f417921ff6c83120e70fb9bacc6ed431
+8563d3a82877b5c1422cf56cda1305e7154ad1f63873237de5ac6f382217c4c1
+17559be71ea1491edfc3afeb9809fe0b4e9b6e830a48af9798e70c9411acd054
+9d835fb1466aacfb17b7a863c7a1cdc81811c220007ca99454e8c423ac95b8bf
+13ea2b29bd1af8ba4e56662df2eca3ac252b683df1e9ef0cd47326252131649f
+e64086725efa8a6886b8c6dbf56490143d5d91ccd1c1f3db4c78ec614c3f6ee0
+3a5cb384ad5b7f3c7e1fa13ac9cdbed4eec6c5012de7f3f48b41531a79708f53
+d5f16ac0b89c06267e11a5fb32aae3ab16a1782489945c14492824724206dddf
+98cbf03cd9d969a543ea1d516df6664b4a839281b179d1096a434bdf08b3b6a8
+b0231c8857f9d4b4436f9c02211b6af4bd22d073d008bb8789545b7f93635082
+641c6e475c12ce89c0a4361e91dadca8ec20583003b1c9306122accc59fd089d
+28757e3e3324c4bde7ec7a161b49bc994084a42880693ffb8676366879ed7554
+43661ba1326baaf4dc099884f753648a05a37c71be30b4a3425d768b049a00e3
+2a3f3c3b12ca5c99bbf419fd25714cbd00a850c7d84a0b4c3e5346cdb1964a0e
+9cd1ac316f017f6dff9679091a6e93a70fb6d26072a2dd886baf9b4bde98cfc8
+372927bc92f28bb3eaf14252c7661be1c656129af8b3e468456ce20f872bf151
+22ea95d1c79560268976be3ee9bacb2a468e1e84c8c387ac41d3531742ff094b
+c3e14b27444d2d0dd8f4dc738e0af4133cfdf7b0808498646e6d28b738e80117
+870bb74f8d4947a15100bd891188a1609560000bb7bfdcc210508903dbdff736
+07dcc542b58699beef7545cf0456b8ecda464126c86ae3db47c7ef6a6477808e
+a8c1dc20768895055bacbe2562921d078fe91794be3ef5ac268f410e4ea47f0b
+68bbe84e54fe139dc9f18808574ae9316e15d0f3db78ccb4c0a8ac63ede19a31
+09749a0829ae1302fc18d38d27e43cc24dd43b091a58ec394f998c5fd4145781
+d6ef576e99c850fd41d46450fbe5d5cd4063d0a24c8bad4a45753335937176df
+36d2d60a677fbcf63d8342232b3d9c0fe3062ce4f1ba853b40bed54da124d913
+4ccd113e18fe2d0c4bd55b58fd5c5fe6321996316eebcdaead6751004dc26676
+1f47c198fd7c0b686705a63a8d2d7e79fe826edbba1d166822aa804858c48f3f
+2a126fb0e7b2d0b4f2f082b78b98c2702f5ff3e4307e1b55fd99ddd861bbaadc
+90075adb2581c3d4e5ecbfa5bf8fc2a4cf6df7bdc0e3382bf12e612cdf016479
+3e237ab37bb1049ba63bbbc0da17d1ba3f98d88ba38f98d7133f53c2b9e87889
+5304664b4d5b6a43fb2577c5ae332fef6d7b7982e0ffb57b265d0f51cb569936
+8fac060e6ac472317597615711d6c13058b2f192982a9e858c093f854a60ae49
+8687141101d45bcb53dc69d432e0632265d8704cfb3766836caa3b09b2a44a1e
+bee69f88f9b8e62d36dc16fad6fbbad7f15c91f1d2431ec61b659e9072fa34d6
+8306c715e2d111f834f7d9fc93a902976ae995bada6bdcfe09d5d3a67b247c74
+1e45d070c6e02c7a9e4aa540a8fe3a41df7fb4b60dc943bea339219bae869762
+223c7492de1434dd6a75c0366624f936179c494f7166085416d126a6bedd962e
+1321f9a2e1b359ba90023ac18942f3d19f8dc7ea36468d055d68c9e758c0bd3c
+7bd2b10e7567282b6fe689a2c713c3272a664df39acf1eff9e3a64a353195290
+4e100ec249c9de6196020e30791284f028f8d98e4447330cafe620642db22f16
+e4dc191d9c2d4ac9b54c1de35a59b2d3138fd686741e836d55d4b12ecdf1cc85
+5bcce70a55d9dccbd33005e654150899527801be8ca74b238a7be4fa846ed624
+9e4d5a6a0fa190963db0d0b8c5f982aa477bd202ce0fa2fd54c3cd4d788d3eb0
+1317a307682c2a2813b48ffd7603a4b40d79209d49eb456064e66cdf7551161e
+9204ccd7d84c0deb458cad1a4556a1cc1e7df0c815da189fd9499e58c9d22610
+f387782f059929860518d3ad42cb245278dd67b01c9e3ad391e0a57b19fee753
+824cb856bd538a05898ded31e347c26ad123ff3669ac5b39e137d655b1c05899
+a9f73546d556290347af154004be5e9ade507c27292e9c60d8a2bdef92d57a90
+89ddeba1dfb5bd781c8ee4d036bb8b67ede9cdd533269c40a7f862a4ded71e16
+cd04e4f144cd4e2070379f4aee9c47781a15b46878ca5d640e0f434010cc15fd
+220aec4821174563c17e19cd934cef3290b3462ef9cb001b84991f29e8de05ff
+686787bd434149a2dadd81e4bc4079c31b7f19ba28eb7240a06b9cd5b5e11490
+66573636e87e5c0a10d0ba380117618f8bcdf3152826b110a7218c329e49fedd
+c3d494bda6c0fed8ff7da20da6a9e0b54ec79af50c7fd1577bc4c5fe1bcf35d6
+891091bdbc81d468935fdc75756067516080fa9f6cdffc557537b959d150d86d
+9e0e1794de22b011073e79eec4ea6f14d6ffe0a75677d20fc0dd0391603569bc
+8e7ffee11b6b4f75f081fe4e7a9eb847a49d11d8531bdad6a73f1b9146625294
+85db9550e9ce21213904a4f9997a83c1bb9d53f5c2099ba94f56bb7985fb1cfd
+be30d619af77363e943b7e94b12f88fe3f3a1900d722107350b0376291296f94
+8515d1b41e6bbb5addaf252985596f30df0aaa37b0e16936dd786cdc624c68d3
+2d796eb0faa89df09f0b41980b8b86009b682dca85eea7c16638d99ea711c631
+35a79c1d426ca9e8782007c61e424c8ede1d72dbf919267d25c73727882557eb
+f78da42d7e816cc83d80b95f3bb86356314e65083022a08f82148044d071cdaf
+db03c7e44017af773f0e7d03c2124e3bb8ef458736c1051f70fa18536395e521
+cbbe933c514747710cb9edfc30f774f75bba103b091e24e4811b4a8b9ea2b492
+de7c5ce1ed103b70c913261fd04caf13727c6df5d246a419c52f175e580b9f73
+fa96742e4f119405174784450411374263d2fa6d277cc0a9dd60c97e9bd91498
+a7942547c6888fb80f8f9a4a3640aff4c00b2181e9a89f367a680cd1932b7ccf
+6e1604774602cf412130503d43f8bc54bee8514a24da005c733295f022f6755b
+e5387da8e24854cdb5a73c39b1c34c4d1884a4d234adfa8d104c004efacdd2eb
+43f20ee50840d297bff0095bd290ed796f726b506e316224907130eac9d99a69
+3a62927d3ef4bafe96258b2b391b373b255a84d6e9d03797f0b1db056052a5d7
+4d4754284dac23a05ad8f2a0c823a8a9add8669678c9acc3d1d086ef67e116aa
+2542986d3a021f8bfcc00f66e812c83e1235990d4adeb39dec07e62c10089c93
+f1801ab8f6210bd328aaf6d44e0a4df0d5b2ee8ad1c8f2a21f43e4f9b897ada3
+88653a83ff3cceabaee4fe26de33ad5fb3337e48e35d14423718642c4071a56d
+f39725f27ad0f2a71b67a14708e70007bac926be651398bb19746f14d1271873
+b7d77ac822aed13b6689b311ca57b895287e16acaf3a03cb9424dc840ba5fd17
+543bf2b93b4a11cc9cb7f9cdf118feaab5d22244096b4ace849d8e51c9b86d4e
+4c14d5874bb374b75e8e468a10db921c066c5c095c5886f4f21b4cf32801a1ce
+ba7968215ea52e217b2f892722f3749460b65838227b45b4c697c2f7d9ddc59e
+e76a83887354180d4eb39a789f0c4ce770f54802b55c238b5189e8809bc29775
+e02104bddfab6edfb13614960b68a49f8e903e6c53d38faeb9472c8bafed7516
+0fad6c4b186735d5de8f6a655ca46f880fac0981383b4720fbb3cb90c757de45
+8f0036a7281b877143b5ae8d0bcfcfbf15f7fe641e216d09aa391ade483d0b4a
+f304f07036ba5b7f32d9f2d0c8969c4f392fa855dbb57940e80e56b3209853cc
+f140c62c9af0a44b180280b95c9af522e77b0f32de23eb5f7625251bfda99170
+e9dcab1f6ddfc1fc7d9ce75c8b9d9350a73823790b789a41b33bdd12e5f527c3
+6785611b74f8232deae963e873156e473f2282f7598d6b05275e8c5ebeac183d
+5239da1e92b56d987cd9e8d817b2be1730651955754e028b4041189921f28683
+8f80a3097ce2e58784a85a18477617d1565160895c588601d71584c097711d07
+fceb3809a57de8185ef6f4492f0ed8691693cb3c83a889f4116684657f1aca5c
+d954d0c87ab67b71e6f13358a83c1a651805a7d3571c0c9791cb761014129388
+de2dea7ba4d0e10c3e3055155fe5f713e51e35d927ad9bbb85b033b9c6d94737
+22cda751a32c178a3e9a526ec9f50d3ea4dc1c4e979597a1f85e3d4342e25e4d
+2bc78e24765ab3cfabd4db25c870488c2d5962756d737bb1c27ec5ea049e6ae1
+91ea186f69bfa8c8969c6c0453a47b73d9f6550131e73f00140249cd8dfe74c6
+c585467610842babae638f2c67ddddde42cf80d561cb19fa06a3b9ef7dbeec22
+e0083cd8916482c5badbcbf03e88f28aa322480baed5cf147f7077da4368da03
+9ba3ed7a96798b9bf4af20946fca2d5cce88915df1e03bccc0a8e8c8c65bd6dd
+6b2195c57bb04dd20928222a230f22f564f51b3e9fe79564525eaef5d017872f
+3020c9326b49682962c78064c067c2b46fa3b94567647e04e9e059287bf2895d
+9d9d169816cd864157c41b432c86626680157cd9e3bf1554965ccf68d033123c
+3667deb2680ca27d3241d6ad950b50ad22aa3166f566c91128cf9448777a33e5
+15a5bf3acd90e0148c7798a5a3875c7aeef66ac2d30c16e9ce00c71c82484f9d
+39ad43a5a30dc01aa0e0bf5e18d0f91f0fdfcf8c30fb6bbea79a848af26d02c8
+431733f1f7e82fa4b37afae125d374fc172d1c9197ead90f986afc0da1243ff9
+97d02fc4b4b5c340a748429841b3a8b56cf3f72aedd6a4d90575fe7c781667ec
+155153ca416dc9eaf8df3830eb21e077653f595ddad569d8bef8802ba2d2e2b2
+7123b7f55745380a83080ea954ba9f2c9039867777b33d1d3dd812d4f8166baf
+9e1124da1b7abdd6c69596cdeefb999777943235314d3098beb54f8f9ed68ec6
+b6acb5aa9d3347b138636a0718f19a06285f194e7a7a81fc94e4d54110561e5f
+61b8fee49f71566c8109162f879f16b80e3ef0342c1000e0cb32bb3b81dff1a4
+dbff77e91e6a561630848aebd0d1c40c5f3129e409da578238889870a7bce285
+b744707adac54c8091b1b1f50390f2886144294e036008c3e4f20267974310da
+229b68182c2bb1af96139268da254217c47e5e2bc279200d858ff72a885fb791
+43ae0be2efd2ba6233620c039a1d47d8b111cd330609cb1d810279a7882aab28
+0a20204aa812c580712c7179488815e2366103d96cb0d564afb0f5a020493076
+46cdb743dd9df4f304a60871e597bda480624939ff921d7d36957afc64d5a507
+a48f8a087585bd0548b1f2c5a4c72533cef1db0694faae62c035bab307b765ca
+422f221a5536c51eb201ff03b81834aa581f36c62916dfb1dac0a680f1f4ad6b
+2353c8603e960f4a70b2aecf44c0d134374c35c88a77e0a62b542efe834e48cd
+4de9d5a989923c8a6da285b3f6ba2e8ba4df07e63a16fe98bdd392df72759073
+f7af0cc58e8f279a232763a8a9d18dcb205350cefa25012c0183713203ec50d0
+f2d6bf83edbe9a6d933ccda8b1745b183adf413617af83605120075f49db2a24
+df5dab6f8085d71c4eec8944dde3ec0c1e51b4800a90116b25204a3e51def988
+eaa3ddb44d44a052477e2ecdde55d0282a5a9c5f4fe31f1d0bc7f40d1f76866f
+c31db401b2865de254b1699ef0660727bc944470949955feb0a8077be04a333d
+72de1f17e061040ebb66504544fcca2ae6b1b58dec1478967b7b17659a4e8c9c
+fb5cce0a95f52d3b5b9fae59451608518b907aef2e1e94144b72fc846b1990a4
+69674c263d050b38ee61ca447b0b25043c980d68fadf6114bfe9319f27ece2cb
+2437c5e78984c9599761d264bf15379d554540b13a2adebe9d5dde18b5663650
+025f9db1e17d5ebdd776125112f32c157d87978c564afc1ab4bb9d734689edf2
+e165cd4ed1eb93b699350d5253f3f869953eeabb6add680ba6a2d840bb7d90b4
+c4e478460059c38a99be0b7c07173b2fac2c68e242e9d3a2eae427c6fb733698
+e429a14c8b04fa08bf6b9980895b4706da12e7f8cf42fe3eb14aaadc87638a45
+704b14972f1df00b8e7a9354c15462ae600a6a8c4b95f33acd11a7da207e49a1
+192cf225d16b58ded179c8ae20fa7d6ef7fd384e7941a588fb7f8c5c29d98172
+4a848cd2a7109333f2a5963f2f42c6835170a57f92c54f03fb2bd434364d6a55
+d5adc8f90b553cf66848ead1de1197b267061f83442021bcd668fa332f3f2034
+143168d02812313f6abea888a321affb1a540a20df468fa35562ea4e45640c54
+e03f932f5581de862bf2bd2c05d2c995bf6ee2039a22ca68c9a71990cb499081
+60b1ee8c5dc10c9174e88f2aa5791908fdc7f7d37e7bada45f04043ee35d1dc9
+ec3704d315bb4e372ea62da6580a5cb5192ec21865ed4ded15de27bbb1672531
+bc5b4c3b2ac266dea3812192324b38c50e264f2bfd995e3a33119e6b47f23fd5
+51271d0a7603da01412889b76031948f722f9fdc54012b4ff53cc77e9a787946
+b79a81d8a32577317f3c7f715f8d3f0daf03877d06d4161503cc441c723166f7
+40e4727f23a4c75bec756e4c40b0cde10315afed35bf03d523d6ae00bad2dbd0
+358e691bf92867237ab36951657aa0c4d79726dc75d8c1c272da1b3d0b47f95d
+2436d59ca941577a232e540928bbf58673d51715188aecfbee7aaa56fa672850
+b047bb56b9c8ef5104b10f8117402b229e483c863d4831b9a1efaf164e99cb35
+39c0299e73158510e5e1f861a6715b979a9982dfd800b0e38a394f169f0f3d9b
+f4f931de232db24c616bf79f59719e6bad4e451df62e9769ef3b2e74033bb0d1
+437baee3efb900853193f6ce69c7d28a871449e9f84909a96e594db7fd08efc1
+217482e67b2e02065e60f4dd931136556c80f39641860d84b7354e86feef096f
+06e84d35dc3645f05626b80e24f01df1031a4e1ee3df4fbf139d27f95926ad14
+fec86931515bb225c1334c86a2a59dbbfb13d6558d389a7202e0fedce9df3d06
+3c4559b11255d23e5a45709756d857060f186c9dd2cdea53f0c3031a4f0392d1
+4beeea8c705f18ae48fd22c8412b71aa4c62231bb9f34d2220e361cbe109818d
+1b6b0be26b2ff128546d68029dc837e42204d73a35ca2a18e13c46d488fbf3bf
+7f6a6f623085e42743ad29b3b9c042efbd195dadc87928ca26487e711463969f
+e972c925d517c754fbf308c3a4ec5c604359a4c8fa57e8db4ab77341dc6364cc
+c6d0640113d541476389195295d68984a2f25b33441d7d7bdf581229503759d5
+e955ff29ec234f2a7ddef7fa898dc3ef0e82da664087a42b6dc28d665622cd30
+92868e09703aee3764518038ca3969947a1b4e63cea5d6a7ec7aad6e7577b88d
+2c9fbe177a04401747ef15b436b75d660c7cbbeb77bb44e430e9835ab409c2a9
+108f94117951832a5a0af0a5acd4e8233fe636a348f252d22fbfea95a5eae39d
+d145abddd1467561b7fb12660b7cbfd5cf9cd84ee2988ddb6d56429e4b87fc44
+6cf4b70f45e8fc082d7020bd6e8ee33c6b94fefc99336031d219145fd293f4ac
+bce0f52f8ea974d74f2b858a3daddfbfcfd50c3a778ddadd1d1090abf318fb58
+2129ab1f2acd880911be86a256af0d0f74657d53a339a4f527e0e9809e47914f
+d7719db5db1ab21f486d85dc8852e9e2cdce6d35f5ae66b361980bc0516d1d12
+51f9444d3830473e30c9060a76edc532e8d74c4fd22b5bec03021bdb293a3a60
+9d8fc3aa0ff599c7a4593e555e1b82f0186671be699c73a26a281aed93af14d8
+5f9961d44c7f4954d3c878f8bfa0c5632b31d9a0c978442e917fc9ee88666818
+9e13fb731cdecca15c658320272ac9e968ae89bcb8fae5765998c8b4d98aa59a
+26e36ec1543032f391f712dafaeaff23be8821b3a23ca765270cb0bfc601f57d
+da601e2e28bac03b7b171101ac26c195863ec701a6ad7b9db8177aad94be5ede
+eee3b11860892cec50ec4059b6adb16c63482233de6c780a26109f8639a029f4
+5c2d3e6a2d95ba25de8097aba992609cdd0a76683f8fc50acca1533e04f0eec3
+96859b3a92dfdc7d8d34f2add87cd55ff8cbdfc446e7154adea4dda504f6a835
+8e2b3433070f23fb153bf3838e13a23672dc8a1f88ad096693212d994c523955
+75b925b8ee182522d63a882f6d6a3ba45fe37eb499f44ec274666b1256595d2e
+537684f807838d60f07ab06603e23f360d6ab50c94cc48d3ac9b31926cb8342e
+b5a881221c1e34888336699c7cee8199dcf5318cdb661f9188be6dab543d2bd6
+9016f5094d7b04677de039ec3fc22732fa1ace506320899074a31d1e9e580063
+39613072d3eae526b88b5933a0c5c1a438558372ae20cb80e0d4682571f75a1b
+47d69c6868ee47dec033c4ac744d0f958e2915b2c5115a73b5268aa94ede8f3d
+182e89777695d9bc36217ecc7ac1bd1e460a6503d1f1a33919e1bf91349ad296
+45cfef1aa1dffa8a68d4cce0d3f6f5dfad069c8363c68924647562f23fb98a00
+62212b4b82a5ca2d9c98353b7863909d6c1de93a2bea9327757528701415eb45
+43472b40f1a044cd32ba7ca9737ebc5e98d7a0bb808b66d616526ec48e60f788
+d5051dfb52801b9d3ba318da1354dfcc13edb89536e9eb6216bf35a77a1f4b7e
+5eca0840226eac5a9ca586ad811aad53a1938860af4fbf5fdf1af521c07ba519
+e2c2fa0dd7dbfc1153a5a77bda9d78b0ec5f28e832943ec8268a9fe453bbb53c
+6a45a9e2f679126e323cf885ba754bcef56ff40bd992f0372bd85c303930be6f
+14087eb0f5b26e5900f236d2a8895aa299e3b769767af33f88da11e5374eb32c
+6f5cbc9cdd8b067abc22e26504fa960e4464803f95af1ad57b249f0826e3b092
+4a5508b46c8ac2eb5d113da479890a7be56a7a5a4896fb097e2374f2ac7d2e05
+ef4d4a31d8e9fae9a0df448f198cfb26ee8462dc01c448af7389076577d4814b
+65fa2e4cd81d22d81db6ac028c2c0419cf6c6b0c8678e1c8bbeab1b89637fb5d
+f04d600e7ea27f4768bb2200fea08e1872d2643cf36b7dc9a1d45425c2c330d2
+44dd92eb0631c87d8e6a275ce000dc7891a91f71a326d191c50c6426c7a5f677
+17a628260eabd753444083e6ff60c1a4257d97c6026a15efb19af06c73de3845
+878b618e10ecf189126b9c1f440b5114bf0d736226f86753da676fa34ea9958b
+863e2f5e085ffcce7ba461ed3191701eafcace0c98abdd6ec4e5e37cdd1375a0
+cde67542bede694bea4bd5a92bcb296e6ff79e40f730490fb4ffa1c0b88e5d7d
+cf6819b545612c1978c34ddd242ce9f05bd2de8c6bff8e79fb747035c89f0c1c
+8bd4f55f14b6c510060105605dac0e571cfc02a8a580390292473b7b1705175d
+33efe45867e7fbc9029106d0e66341ed9fa6ecdd6c8b4c50ce213a022a4d16f6
+adc0ef53f46870add3d7d1038523d8fff0b2443a58f087f910fe374b3181c982
+732b86c0e23bc8f9b657d5c49f4925868ece6e5257b6aa08f08b2df97380cc7f
+d9c8e09d7b6886fe8adb6184751bb1a776b0f0b365dbca4d2e470a9f33c0e424
+318ce60407abf86c71cf2373e4226cc556491a91f4234f75d8a974a5b4c9748b
+25e143d76d91150a4796b1f668e9d709b8d92c47173ca4323a5b1594c97409c1
+82a52797bf1f74a8cdebbe221e9d1868cfa963b57a28f27ced76ec79dad9eafa
+c9dbc14b8a0542e93eb35bee947ea0c0fc373170406275d6f94483b5adbc3c48
+1d877a83805751d169c08a772f3dbab227d3ff467c9804ca7ccbf700a75ca77b
+14f32e0fd063e6462ef94bec9bdb4e8a392db2d4e392a270e982b05cfd2dbe37
+02eaa1954bb92e0c74873c3e7b780e7ac3341635c5a48ab40a855052f1f769b5
+48b78ce8549bdc2d972b41b2ff8d4c188a6c6a3009b3cfbf20c9320c78b823cd
+dd1b836a827ab16d2479c289f766029f35fe1fa278ee20c0c8bc21b02837a586
+2112128d04df3d2677260b123ff58fb1b7e6075f1aa0f6c7d56b9cb593ca07f1
+7e5ee7ca93720a395140b558549933bda0106ed4fb29284ea2b76eeddf0e1ca6
+89aa2a31f3a655c6886efc94ab37c6f2bd2cd81e351554ac3f569683ff1876da
+ca0e0f2183c0613ff4266f9c4399f11dc9db5257a48f44ae2994dbce930db588
+46ba3d42b1e318a207645ee9b90ab34aa0ef3e113744471e5b827a90fb5f7717
+27f72758ca2e49aa0fbb427df80dbc2dca243e2d6c332d205fd8e8ccf4efb9dc
+be143004845e09908024e4e59e8828f3108da8b9a590decc82e5f821cf3154c5
+319038719c08714b54082e645fe0baa6667f6ef885a19de9033e25f6f2274f77
+8e8fe9b9bf35e5b8186e9cc7ab124d0e49a8df2343922ce64f0f65a00a5ffd47
+9dcf079a301f59192a3b9f6115845ed1909643e3171ab6f105cedf73ef9652ac
+c7f9c68b0b0fd090ea8bd3d3838af25fa3cefa1580faf0c50bffe0e964a12859
+e27e017bd84f8d89dd445f0afbf8da3a73904ca77d2b1ebfed76c96ec2f8a1ca
+d38cd76b2343f3b3ffae2ce64de4a0e3dbd9f0e265807f731f6f01e1d61bedfc
+f214ee2ab782eb6d3cf4746fbc372acea6153853b94f37f3f8771c5c01d46e1e
+1d307cd73f2963313f46b0ece915298005a2e37a20e8b0da1eba7f0c06a7e2c8
+5bb8385b2034401837bf414b33e4cfe4c40f46efaeb4eb66d8c73c82798b4dd8
+ad3e231d2fd7ab77e3bbab8f4e7b15bb6c93f92139beae8b92fe3903aa35db17
+35cc096486740c95add2ee1c3acab366b335d361f39b441d0e22794696cb2a89
+279f434cf1ca1cc2263218f4084261de8635c0f87e38240a07f5a0b8706201d5
+e84cde169961dfa50d0aeb38a6606b76289c8234a918a37c766877fd4b254b55
+44b31946a76521b83b6175e892670905956aebb55876d19cbb009346b8f6f10f
+a6a7a4c86da23716364edff21cfe7427d6bceae58e5fffe9e3ae92f3f88d9ef0
+786ec2ce0f1440cf0ab52ee2249d0eb924f20687e6716af698f8ab76f919d5c7
+4b4a860d28647c916c23421c50754d52749551e7349f72a5afdde899ae41a0a7
+074f2099a53d4403e3bebc0270bab5d191672b4fd9b24a3a32bd62c863ab113d
+0c98bd274aeb746e8c592c72010af8b3806c86d1434113fd3a915e11bdfd4e3e
+b975ee34dce95ef509c14f5cbc0ad0dc6b2eaccbfcea974baf685a369aaf802c
+0df511bd0d2250813063525c128eef1a2ce9e0015937a9ab1d590a71587a9a7c
+b75c6a13ab9072c934953e93ee05c36b99d54a765cb1b1062e5a312de7a5cb2c
+092a5b06a9ac8914f57212df83432441a1eead79584163d3aaf35be581bacb6d
+b70a2e42bc886f8bd7224c1e98dbd4fded0f293650901d03b9015b6a6f761978
+770bcb647758f87eac41a98633abeb13226e46171db24a7f0cb2f94e43cb7775
+e389dc2cc772f99b17f04dc1baecab2cbb77fd8810e1d818be0a22fc3e313d2d
+462d439e45ba764422692d68b01e7e18fac49e91501607974c9c1cc61f2c9a67
+1cabd1ea8ce615786bfee65314b414fc14528e59befb56f4863056b4fc18bc21
+d61bb8c0984bc7c16b491267a7f80255249179c6f3378c9485514ac7c2c84f61
+00e5d81d362e49f278fafc1b6bf0aa9d76c98476a3e3635165d53a904dd6b699
+d6ca2e9cbcd609d11cc9373f4c1179891d2c370681c8010ec75ccf893b040b7a
+a413663f8e262d4c054bca74f0c5fe40e24d57ef4994db6543bbd561f2b33504
+0356430cd25451126ec91f82bfc73ba6d843c07f304472aeecb7cdd660a34a2e
+05c1e3c06be790aedb514288ec1e029bdcbcfc13689377be037674051972e96c
+df6753b2e31da945b56c0a7316ed42adaf78a531d7412b3a26e33c3fb926446a
+4a8abcd35799ef47481ac60948245280236668ae7df01f3cba2384dc88945c36
+7c9d70039f6adecabc5be2bfeaa8dfc69bc1d40b593140c974587905557c4a68
+71ff97652466fd44e8f18d31e9855bcb25640f15a398c257292c61ca77b752bd
+aee91ff16e070da93ba5a9cb1d2e137fac8d78499b015ec2c3f8e38422c058b6
+8ced660fe0af345c9d4513983a7662a0c679236b9deca23879c983788e49b143
+67ce3f436353b3a86148ec21402c05a5e815ae1e375de539628557a8caa5af62
+7bc56019eb0c11f753193b3475c6512f1af8cd4032b4e13ceae3fa51c167a692
+01d054707f40b3cf7d7fbd23a6cd3b680d5c20e0181208d4b102e0f96841b6e7
+ec818a881bca226699ccf9587bac922a6b51b545d8222e9fec0fc90bb7b88496
+01a7c60dc2adcbcd798c8d3b67a6c4b6978388d83ee7f052e58d117c15067d4c
+7e0c8bcc3064287cf2cf511ab902872592280b67295c0cfc7b1e51b3f8b71fda
+b9e285f6f0de7030ce9b57e50df59fb2ad6ed36d423f7f2e148246e66d343396
+82ea928d4a3153a382a9638e9bac663b48e2d1f565ecf22d2a2eed624f3eb980
+444697474a2b983697ea5614c7a867d48363a1ddadf4efaedbdcf6306543d62a
+4e2c4b153ea731c1e44b1f362870621b3f7b992c85fce0d4889b78b25d1a1a1e
+c017613de16f6da6ccea3e6c0958d27aad413ef02097a1e379a965ccc3a75b04
+73dd0b82caf8ec8788d4bdd46665810855f8bac3daffb60ad8707c728fae783b
+e9c8a8d34d949caa5122f37fea44063211379c4b88c5fdc06f451d1c06b1fb81
+3a25044a5f8d348b4eaabdbe4f9c1ca8943d2af204c41dddc2c1a2bbf979128b
+665b9f84cdd35c82fe3fc9f7457513bf4a908c3bc2a0d917d8af2417ad776767
+9b8c7be588b373753077761f99fe50c8df8a8f85eff5e4fd53d36644b49e7cae
+3f65634f6feb5838017ed45a4ca1dba9adc2d4c525d1f952090d31a49b8e2aee
+6b45e0423385f52628d8e43e0cfe89d07640971455c629da5d938f551f8e60fd
+03eb7fc642bcded570f8f592eab13c71ea7c9e833ca2d2d08c453f84b13fbdaf
+7cdcd6383ff2c103fced1d16cc4d8a21d57a9e65d89f913f1a028250cc3226b7
+0fd0d223f4d715e233caa1aee5e9fe4ffaaea243b0e13c198bb8dc8af12fee06
+09be128117b9c7bc338f21434dbc92bba4f7034b062a3232f73132558ede0fb4
+5ef02c7bb6c146e4290f0d897ca4d72eb8d5312a9a5fbd0b1fb878aa3ac69c5c
+3330a735239e9181ae7f29267cfb5249fbe39470d119ca8dc6e0ddc18d5c277d
+b2f4a12cc489660685fd824e4db3a9053cd6fca133c94c75f8ac1485ca36045f
+8ff8c6640b95d2eb5a7b0d4c5931ea75418c151157bd25f93e05cba374c43c38
+f04e4e89cd5cda0b60e1e5d4713396e2b553727a20bb76a33273bfa660ecfcee
+2d3df3bce3c339a423fa84d97a637e3a3d99aa19ed8d6e5a68246ddb6d3d609d
+7515cb0f19df665d9f5cefbdbb745a89d3d69887e5a0bb1b4e59f4c5bc0c1a03
+67568e924f8c50654474b48003c443652a2e7a32d5c1775e9b930cb0c99f1a3f
+e6ed3e505ef02e231e9ba5a08c77571fb397b28df3f6334a59d62e87b9b73bb4
+8b3f911be84cbf41565870434642285ab575d94cf326ac39d601db42c83371ff
+55b1f9d5471f81754a3ec6233b47f444e28c0f537d91d8d13f57be0b8cce06ad
+f946dfcd3b272ddc13c3360225b79919f97bd2218313b06a80e4d76d899e17e8
+90a00622db00345e86a42f406a6f4185fda063e80b2f8b98b80bac88b400eb82
+12676ec8390a716d56e244320febf16f4ecb70e06caba17d7459801da82232ef
+71c80a924edaacb29af50d26be34bde0c1554671f4b0153a40a8693790d3ad6a
+5422eb65661cdca9379e47e813ddf9e1240cb259990bd8b92d96ae9576df599c
+743168ff8f0c64c37e870e3ef25146a9231c46578ef77cc3464adf25ce35131d
+640e067c5843d0d628a36b9f5fba003cf19d15ec0d693a8862685d46a30a18eb
+7d66a20b365b258edc2e9d9a6b414ed0f1ac1894ecdd4bc2d707d88833a1e34e
+a72389fc95d26ec2c10e8474bb00dc4200aae15adce11e1d8c4014d7e13c5c0b
+6caef835ae542019f4ea14c7283fff973eaa8a310f40de4de0ff56807bd4c46d
+c45523fc703fd8c6d08b32ee6e31943dcb52a47b30dad4d3d2a5cb4d647febff
+ca269375f1cd0455dc0a81e1886a1dd4b78a9d30138e03517011550a22997fbc
+4c6a833d32631d4a6cb9bfce3d8a98fc0393aca086a6bedc8ff833bb9c455e2d
+1cfd953d3f4e60f06e8226c561ec5dc4994ed8745d934e231d95765a0809b952
+48e85d325ebf37d8084619d4de9cc4aec1204c9122b918279b1ab7e63d9eb477
+74654bfa2cc04b8ca5984e2ed6cf7358df124b5bd67b523c4785046780708e2d
+982f3a7f3e6e266a5b3b72c1d9d13cd7d055a957d96015b11ac1e05dcd9fd6db
+aa69fec04dfd90eda81bf71aa205d1587eb8d1ce5b97cf43f32bb9bb2a41ba55
+2fe72fbcf8573a998a4be2e870380d4f6cb4d086ce926c86db0e248b4e594d4a
+e233bc13dbc6a55c5e7fc9c9ec994876cdc9795e0d3d0535f702f13c8cf64659
+b0248033530ea3b00295bc96240e3a41fd97a7e175b544242eaf7146a6d413fe
+fd9d4c4bc1362f20a1678b8d6ab03d7a72d9cf8d79a9fedcf826274efea6ae0b
+837eed3e717c09f713dd29b6ed9f5a6bd7ed0676b6dfea8c9c02a32fe56d704e
+bf1271604acfc1ea301d86e270427c417baa917c5d6e8e24af0b352d4f6b419e
+b63ac268e581a5aa86c7eeef14a718e33f4dfe5bf05a94675368e780b8f81ef5
+ade6d7647f4282ec319bf5991a09714594dd1854ba9c20d6b00ffab76e54f388
+a60780ec12af64779f2adc061dd71135447bca3daafcccfa6183fcc78522b533
+1f39f30e8750c0a3337a44766cc3f7f210c1a8e10f4ed1cc54d0729b26f1fefa
+7367aee66b33e8dd0e78cbec4bee5a70ca809ac39f84b72f1d119e4353c2e4d6
+20c7f6383ab7129b62270e6f7e75a1648d1da0536e79d60787aa3149b96a284f
+2ed1e0d2c9c4de7be5db53b35d360c1c6210362d4a35f91c6633daba2c2272ce
+d87c53f85ca1b55002d4e8680cbef6bf500632752827c0a5f19a062bbad900c1
+11442036e66d360743faeb0636e1f31b554b6567e4540d07abd6e1ce39313277
+5029e890b63ab4708ad351725bccd79d75e49cdb612da57efe3b44a2e7870e67
+a5d9686eef4b57c2a17f2d83cecdeb54914610e5a3401a97050fa517f1662297
+e06d29442d0fd89ba10f21584e71eb8bd5b03fbd56f5a538f72c9cc8ba5da11d
+9e4a7f8d9dc2fe56f2976d3e6bfc901de356a3d0690a0aa715ec6d339d72061a
+530bfb5323a8d038b8ad3adae140668e505bd100bdb20b98371228b841d0873e
+832309912ac7e9d4c7ee9ce81285c7f8e9afa8e2077e635bb36167c9e9c6153b
+c73fe799800c39fe343d70e38f30a721cf2796839096add3c82b675f0b659f6c
+99441f9352a332065c9df5b419a020d9b2fff06efd3295250d5335f7a256ef97
+992005ea2fd03f408a7a923ad8afbc8224daa112e8e4faf2ca974af2cafa92d3
+d5e9fc80b0bb00d933f50ab9010d671751b3a12196919e073678c2b047435a8b
+b826809e33bea0f12379ab1a0392d37f27766ccf15b3648e574e7dd1aff8295d
+df3db858bc078153072770fbe45196c3107a12df7f48d7bb6e4b99cd3553e01d
+59bc130f7877b18b65b15ea50db1146c8ffb2da5180b603d920c099d15aa9533
+b594791c144a3822f337abcba4d33d36299bbcd94190f85e0c28f4fd51f9cbb8
+9d301eb3f41179389b4e3323bc407c1ab223ba1608a5f8cea8d08a13dc4fc307
+2f93d59a3c8b32cf0721dd80c369e4f590d6fca9930d4ff110611d755b5fafd9
+9645e29003540c72f70f1ba5b1a884d1938f77ad689e6e70afa7216f31988919
+86df34a1089f9a009926b8983b05663bebbc603d90ed348ec33afc5a549af5db
+86b90fd34f534b24234f229409f960e2e921575cf45520f2d651c62bce7ab637
+6c2fcc99345aa8880a8cb7a67c54ca398cd61ad14162c9561ed8e4e9737aa9e6
+b513003edb220ac92894ab112e100509608504afe0fcf85b7bef8000adb3afa0
+3f85f52b856c23a68f58bc28a601e5430ae318ffafc24975eec7836b147a5a50
+d0369b90f111aeac20b4db1abe66c32bf4231006d79ef4c303e7aefeca4cd5b3
+00b6900a26b2b9d98da2809a9135dc6e16644dc932f106b016fa02730f1d6dc2
+b1c9e10b610f2bd769fc071e06ee636ef566bfc05899c740bf3ba380cd7164da
+7fa355324aab667f33fe518cdb19060cf3fae8a0363e4460a6c2c9b70bf39054
+b2d3eb81093b0610cb03e2e7180523561cb05761b5ca8cbaaf5a35f7179b1d4c
+18d0b7eedde1634f120fc0f6134e55a6de259aee6d440444e0ef0fb7db8a9c63
+95f3200e5d40b854ea844860529d1b694585b8826d988f432be1f620629691ae
+f282e79b2874754f96c4a4a1155c2ea07450c2a256b9d8e33eee33c232c99860
+1e34dbb63812ca08aff99255ae6def9dc3ee2789e9bc1d956de9fb255bd51d5b
+09da88ffc9d8744f61e002da68f41aab3afd0314ef876318c8785f87b04dfbcc
+bcf681dc8a0d615e24ce8f6322705a59a0824b5d1f0d35c7b04062d13f5cce04
+7885cf30dada61ba4cda74df52c4d4afa32206ca799ff9e9ba2bf939deaad2fb
+5b14cff0283dcb41869cfbab91eed9065d69a6ddc91cb241634df75a81fcb4f1
+490bf1195ff98db50e9fc3c82f10d91968259bb1b07645881c274d7751e5c2d9
+40e9e8a23de33344c3e15b8c0a3801cf73eb1dc8e9df066e7ec61667ed349f0f
+45ba6b0832143d4e6e7901d4ab7b461ea6489f419b59ef6fa892663b01a222e2
+7bc2f4cbc81b1982f595dd2cc94d6388312cbb61ff2bfecd27b1220dec43e9ac
+143a83b5a63c057e2ae6393054710b5d616316ed94db2e6698c4c1ddeab6d9ee
+2444408d659c7b68d4cf5c294dfd4299507c41190068c3905bce2425f652c49b
+de244eb94d28d3d5297e9a11adaa7c9a9138f00659f2f4a42fa34db8b8e29246
+15ebbafefcdc538911625e0e0e9ac8eca76662bf295c8fc2f871fd9aab87ef3a
+3a7b293875328e89d018f40c760006fba666b8c1aec0c64231793e22825df5da
+923979c72db39f5541e7f489fb5d9510674e8be697a1aa7a0249dac954fbad79
+06d11031ce4605ea491e9920524f4a60ae55c85371fcc9f046c1b83bf3e5812e
+2a2f06cd724c777d2238fc465bb016e8a78ce7c86808698a852d474b63c811f5
+43591f5d8391fe4a4d2dc3abc5005757a17922b6eb0be4b9c238fd162f9224e7
+475456f639c59a26c8c6107ec20330c8724d6f6842edf3875cd0f87dc434ee65
+421155bbf5226c31de48374d426bf3f3d07b9bd718ec54f8eb9a6f88a9de594f
+ec2c9d4b2b32827fedec8078a6851bbd269993b246b0c902242f0c7ea422d799
+65338a2ed8a27e49a0be708911bff10f485b753574d8ac8313caeab496e0ccd9
+5b67f10cc0c074ca7613d05a67bd45df6b72f6e30eaba836812c91a2a0a4bd42
+2141d7443ab359be588901f46a22d52fcddf6bc04e1fb006a32d37dda270565a
+575354b40af0869c9b1f8a85696326f546e9a25417b38b9b5d972bd0d933226b
+f8b34f70b324255750f5a64c85318907900c3320f070b1f789620e4f5db6f37d
+461f4a2f15e884b948327d53420dadcc4b073b37ee4fe935c8f520d9828ee209
+976fd2c26c71e3bad6583784ffd61a2f419981d51592a6f8f47b4718b6736a2a
+1ed7f729a521155a39394d038c34d8a76e9c6f91ca369da29fed83edfc05a219
+853e5ebaf1f3d5c6ec932e69b7a0dc19d248f81cf541348c85853acbb8334b12
+26ce33140f3965db1a6f79ae0abb24b93b6916496b1698057d4c2d4a6a72305c
+11b731a8a07ff1eecb84fb6e3f3c3706b09a0cef4b1c2012bb9278b76f5189a9
+38d5b9fdd4cb9028f43d16dd39fbbd10a684582951236a05e0692bd1c139742d
+7e75a21cf14fe99d51dcf0a0b71b267a0ad42b8c1d7f46ba46972c8ba7bdf849
+15fa6c5a922dc93fbaa9fc50c7696494e6c372f3467695da5a6d7eb87c0437da
+d7b68ee36874203d741e373e931425d4ce521b2728633a25dbb435f963399f37
+8d0adde8345439cc5295ba3d255a72039ddb1592dce1ea47a92d7b641182d0c6
+85d0616a85361c3b78bede0190834c1de0d4d132f459d8f29586dc73f974dcd0
+5def570984a9c2bc5a720999111c7de6a28dc0c6e9c51f82d8c55a26ff5ed760
+4337606960f847382671bb144d5ae5032c74e0581b19b21eea8ce4529eb792c1
+044418fb0e3da356a5b33cd97a0dce15ee9279b076405e23683f68c63b6b09fe
+c3ce3e1b30a4cb3a1e72455a372b6eccc7e4c97336df45d0bdf94aea01a108fa
+f28d98d7bacd750c7c25874e0fe3fe20d8e1d51043eca67c546278dc13716d9e
+f81a8fe2945a6a49c82b653804d54f626238396d8e4a9bdec4946c365d1164e2
+fed12929b769cb13712ad61c764d31798a87a3e12e1502cab14ca07efb524ca3
+dc31cf17f73feace77494de837790d812dabd36cf4c78123cf76b504886d0cae
+13fc6757bb6878f792a86d52d0e90913d089d7c81faefc56dfdbc3acc2195981
+0728dffd98a2254e39eebfb51742657ac9088cf8adc21208f3a333a1f45beafd
+69ae647754161c6bf0a76800edcfb6a2339f03e4b5ba4308b924ca56344770ea
+1c7322f692c297794b03f3c2a180b645ebf850afb290465ba050ebb9c8298f2e
+50b75571a15a599650a7fb1fde8263e49a68773b5184c30da3fe59a7c75943ae
+c5d26594361b53c26162410d3a1bced7c1cd0053390c1e2f8848e6aeaf75319b
+7ac5654a16cb93832bf981b9d54aebb8daecf97894b560ea75f51bb39a2bdc9f
+f59d01321b51b23a10827e1845e2db316d86d7dce368313f99ce7cab2a8e7c50
+8f91f0faf03626ff87247ba86dc7f3d837020e6aa83921385df0f401e4084237
+95251c27e8944749aa33b0b697456453ad8a5be8324fd22b192adcb22e03c74e
+6a3c9e6f7e452ca491db52e6b18016592cb09955f05365ec73300dc3e179c32a
+d08a443de2753d56f0c968db00d9cd58f1af7e3811202398bd58a337e7ddfd84
+1c8c8efd21aac90582130416d261f3a1b9f36ca431c8d4efceb9eee03d680165
+70ac1f37a1698c0b4da94cfd4c57ff54edc1b25fb713177fbf209928a1272cbc
+25655632d43e6d14c052763452d51cfaa32c8df47c57112ed37ed239f8c00268
+c78d7f3a0a965fd9e3f3c1aef2dcdf74f663a72b15fa09d7309f2d15b232efcd
+baed8a66f17b0dd190081a95a5619684a184603491a32114a5ef2c5076caddef
+0b4eb2197fd59ecacc6416398da20414cdb6a9a3e9ead646c34ae4ace4cd57e4
+430cf5bf55ae076419c0b79095d903753ad7f46dcc1b93f782127d33a309f245
+33484156f9ffcbf9e841d0c71ab0b12594cb2c327112508593573cbd95d75739
+f4ed866beaa48bcfe3599f62b0a848d6f63fbf79937966f3a0ee4c6163ed16f0
+4b8bd51ba483da59fd1d719499ea3aa49fe4917e10e2da13301b0a8b50f3841b
+6e80904dec24e5428b97ec17e66889170d12a27c028cfac9adb2eef49815f433
+b6089c9d6ef39f058f0cc655b27f4241ac74993af39e1fc17d5f9e27c569ade9
+ffdf46027fdd633bec61d8c5fe318810180f4437f356001633e14e098c992da7
+52f392cdb417d2bd30698612dd7b2ac00d0e5b4b3d7a20b99d0858d5e744d4af
+bc9acac37005c3841103a6ffdf3192ff85ed425dd221e4bd71ca3db6a739c7fb
+9602c130a1cbfb5b9bb9342099c2f60497642ed1029e5f9308a5097d6f33c815
+0f9a31a988ab5c162e5aa56357c8186ec5914533aede037fa99820ec322c8fcf
+1ae1a6aa6e048c99d2af1b96ee8b313ca0453923f5fa095aff28e7cf79c4be83
+70058ec3619ae4eb31fae28947355290e967453cf351ecd976ad1c5da7573f0a
+ddadb5b6661c47a0dc3dd22d7c175f9391bb9e359bfec4eb2b687a5a6f2fc8e2
+3ab232c34a95f54cf924fdf8011148145466799cd44a8440a0ac99b7f119e470
+a7fdcf6ff7a57a588f407de565b6a45e7fd5a1925efa770209110c54af8815ea
+ac1af9a0b5a790f6df301f7f9a105e776ba30e2b56d1dc2c0811990c8d2a0d59
+d733ee5bb53fe30323632057131ba4ad1d49cadad65d386ccd7a2c70bccc068e
+e881a538aa3471ea3b66ae24127dcaadbcb01a9abc8df00353c568aab8a971f8
+fe42a15ae22fcba141317584119f38d44e479a0d230273fb235a7895972f3f12
+c9964d1fff7ae88b9d39bd0c948a5ecbb2d858a26c01d57f3ae91ecca2475bad
+2d9c5d8053119644ee9ac5c3448c412b9fa18b8c5cd666b58714124c3c1f7d24
+62b31fe9605544652c1b62a4c16c03876977228b7808919fd49da8c14e12971f
+bf157f70c41e54927f569a8ded69e9ac4af29fc679a278a7c6b0b1a605bc8f3d
+d28c0da975292d88fb0b27a12c56ebb71bd93802ed8634e6615f92622e598726
+f1964331f9f3353638adfc0b3ae281842a8ab707b753f94cdc80a4133dc2fa76
+0e723c34a5962c79901e1c3be3644d926abf5a71cd581c2753042a0264fc5a5e
+f6196bbfbfc152730227798be62643e38c512d889e61b5123e58c173ae6cb8ea
+1f3c5358d9ee649afaa3c0fa0efc9d018be248b664f9b16ff484989e0ed8a199
+85abe77acc50b0da312164a738dd0b0f8156eb2b18b9c18ad7c08ad8b61de543
+84d1f5ea363354272e3d937a2f770a74d30910f195d337780b3b65791a0546ab
+18a8ad95353637c276b4fba66000886be4c478fdf68bdeab674527bbb86d75f5
+f05702581626ac0e693f73f08005b929bc154a5cb26f1c1447a34cf441aec12c
+e061d5c75dabf9b6055131b7e27edfa1197492525b8ecb98a74753f10324b4be
+61c87eed22b31f59c4fc0a434df9d34e3ef204268df01fe50866c937fdd83de8
+be67591219361b44441676d4e24fbffdf0fe6ffc1246c94de5de84d0e2ce54f6
+4001a4d5056dddaf20dab145f59405e14629138ce2aef7f7df4c3ac3c34c8270
+0195c65a7e6770d2e8211b5d6ee9ee826c316acd3063c634114d96b68f9e7be6
+d6f09e1004eb05a1b8d5b8602e7dcc4e9942a1a07cbce0515a5f651df70bd369
+31149d9d07e155b69fb7aa2ee350c52fec11cd82b0bd41dbe5af51b22a0d1aa2
+42bb655f8bc0cf68708f4891d0152ea90a8ace90fabedb75807baefc45b6fdbc
+ce98089dbe4004fb84cf3ca808b355a401bfb7cb0bdf3d34c56920ea7c9fd810
+1022917883c46cea4f9c53a1b735206870f51b55f688da9c8569dea21b033a7d
+26739a6dc374059371d7f0e4f1369f24abac48e2bd6c37033106921804790ef9
+8cd1e8b8e954dda81ce530dfd6b96e68e9e65f68893704d4e1d462e7fcb38be8
+9687baab78161f5e6957db5340f62b08b6a15b34574f8e66a0e3ef88db94a571
+2d01ac0f7b3971c994b8c4867c26294e63e18b06748cdf7067f521acdd28f928
+efd5ba043ff030c24f8dd60f472c254442493e4d5ba92cf85023881b5a222ec6
+5a37ebd19b3cf67310f5fb521ac4f8434ee8e998339f7fed4cb830c5f84198e9
+c5ca9e7347186fd6dc622118c624b6c5ec9554641de66beff929d05364b9ff44
+dd342e36818f8af5a81208e7d92eb91647f1b68e4bfbe708c0ea70e90a1bc7fb
+7fe7d3d81f207dcf44c20fe86ea34cebd111b7c5cf7e0c4ace99eef31ebf1160
+ac50564b361d8d039b8f622e3c4d6e85474d232bceeab726bf66b307ce147c32
+b1351681a3b6be40d13fd8da37cb71bd787b78a922f3e9f70969a716a76c2c1e
+fb6c315bb455ecc8d2475b1e61aea14f788f235f8b5a7cf987b78910b2da28d5
+288a7fdc879b66f78d5aaaed5293a17a1d96d0715e2d0006feb9a9d7e4837fb3
+91f3ff93e02ccee1ddb2b47716abca344666c1573ee338bcf35d7956b9cc82ce
+5083ef6d3155e28f8755aa7f8dc6dde81dce8c37736e78b97c4459be3b471336
+5b93671811cc5a397a4a943b1a82f712a7526d9010fd07514856505621d5d667
+68820a69f5ea83b0d810ab8ed4e5c9acdd54d4c19f958add35d3d21f7089ddae
+f69e4fddb831a94cc51f7be19410c5c6f3b0ae57026c4a2ff33bc8f91afa9844
+abf3f6ce
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
+cleartomark
diff --git a/e2e-tests/cypress/fonts/Type1/fonts.dir b/e2e-tests/cypress/fonts/Type1/fonts.dir
new file mode 100644
index 00000000..7d0a5f39
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/fonts.dir
@@ -0,0 +1,26 @@
+25
+UTBI____.pfa -adobe-utopia-bold-i-normal--0-0-0-0-p-0-iso10646-1
+UTBI____.pfa -adobe-utopia-bold-i-normal--0-0-0-0-p-0-iso8859-1
+UTB_____.pfa -adobe-utopia-bold-r-normal--0-0-0-0-p-0-iso10646-1
+UTB_____.pfa -adobe-utopia-bold-r-normal--0-0-0-0-p-0-iso8859-1
+UTI_____.pfa -adobe-utopia-medium-i-normal--0-0-0-0-p-0-iso10646-1
+UTI_____.pfa -adobe-utopia-medium-i-normal--0-0-0-0-p-0-iso8859-1
+UTRG____.pfa -adobe-utopia-medium-r-normal--0-0-0-0-p-0-iso10646-1
+UTRG____.pfa -adobe-utopia-medium-r-normal--0-0-0-0-p-0-iso8859-1
+c0419bt_.pfb -bitstream-courier 10 pitch-medium-r-normal--0-0-0-0-m-0-iso10646-1
+c0419bt_.pfb -bitstream-courier 10 pitch-medium-r-normal--0-0-0-0-m-0-iso8859-1
+c0582bt_.pfb -bitstream-courier 10 pitch-medium-i-normal--0-0-0-0-m-0-iso10646-1
+c0582bt_.pfb -bitstream-courier 10 pitch-medium-i-normal--0-0-0-0-m-0-iso8859-1
+c0583bt_.pfb -bitstream-courier 10 pitch-bold-r-normal--0-0-0-0-m-0-iso10646-1
+c0583bt_.pfb -bitstream-courier 10 pitch-bold-r-normal--0-0-0-0-m-0-iso8859-1
+c0611bt_.pfb -bitstream-courier 10 pitch-bold-i-normal--0-0-0-0-m-0-iso10646-1
+c0611bt_.pfb -bitstream-courier 10 pitch-bold-i-normal--0-0-0-0-m-0-iso8859-1
+c0632bt_.pfb -bitstream-bitstream charter-bold-r-normal--0-0-0-0-p-0-iso10646-1
+c0632bt_.pfb -bitstream-bitstream charter-bold-r-normal--0-0-0-0-p-0-iso8859-1
+c0633bt_.pfb -bitstream-bitstream charter-bold-i-normal--0-0-0-0-p-0-iso10646-1
+c0633bt_.pfb -bitstream-bitstream charter-bold-i-normal--0-0-0-0-p-0-iso8859-1
+c0648bt_.pfb -bitstream-bitstream charter-medium-r-normal--0-0-0-0-p-0-iso10646-1
+c0648bt_.pfb -bitstream-bitstream charter-medium-r-normal--0-0-0-0-p-0-iso8859-1
+c0649bt_.pfb -bitstream-bitstream charter-medium-i-normal--0-0-0-0-p-0-iso10646-1
+c0649bt_.pfb -bitstream-bitstream charter-medium-i-normal--0-0-0-0-p-0-iso8859-1
+cursor.pfa -xfree86-cursor-medium-r-normal--0-0-0-0-p-0-adobe-fontspecific
diff --git a/e2e-tests/cypress/fonts/Type1/fonts.scale b/e2e-tests/cypress/fonts/Type1/fonts.scale
new file mode 100644
index 00000000..7d0a5f39
--- /dev/null
+++ b/e2e-tests/cypress/fonts/Type1/fonts.scale
@@ -0,0 +1,26 @@
+25
+UTBI____.pfa -adobe-utopia-bold-i-normal--0-0-0-0-p-0-iso10646-1
+UTBI____.pfa -adobe-utopia-bold-i-normal--0-0-0-0-p-0-iso8859-1
+UTB_____.pfa -adobe-utopia-bold-r-normal--0-0-0-0-p-0-iso10646-1
+UTB_____.pfa -adobe-utopia-bold-r-normal--0-0-0-0-p-0-iso8859-1
+UTI_____.pfa -adobe-utopia-medium-i-normal--0-0-0-0-p-0-iso10646-1
+UTI_____.pfa -adobe-utopia-medium-i-normal--0-0-0-0-p-0-iso8859-1
+UTRG____.pfa -adobe-utopia-medium-r-normal--0-0-0-0-p-0-iso10646-1
+UTRG____.pfa -adobe-utopia-medium-r-normal--0-0-0-0-p-0-iso8859-1
+c0419bt_.pfb -bitstream-courier 10 pitch-medium-r-normal--0-0-0-0-m-0-iso10646-1
+c0419bt_.pfb -bitstream-courier 10 pitch-medium-r-normal--0-0-0-0-m-0-iso8859-1
+c0582bt_.pfb -bitstream-courier 10 pitch-medium-i-normal--0-0-0-0-m-0-iso10646-1
+c0582bt_.pfb -bitstream-courier 10 pitch-medium-i-normal--0-0-0-0-m-0-iso8859-1
+c0583bt_.pfb -bitstream-courier 10 pitch-bold-r-normal--0-0-0-0-m-0-iso10646-1
+c0583bt_.pfb -bitstream-courier 10 pitch-bold-r-normal--0-0-0-0-m-0-iso8859-1
+c0611bt_.pfb -bitstream-courier 10 pitch-bold-i-normal--0-0-0-0-m-0-iso10646-1
+c0611bt_.pfb -bitstream-courier 10 pitch-bold-i-normal--0-0-0-0-m-0-iso8859-1
+c0632bt_.pfb -bitstream-bitstream charter-bold-r-normal--0-0-0-0-p-0-iso10646-1
+c0632bt_.pfb -bitstream-bitstream charter-bold-r-normal--0-0-0-0-p-0-iso8859-1
+c0633bt_.pfb -bitstream-bitstream charter-bold-i-normal--0-0-0-0-p-0-iso10646-1
+c0633bt_.pfb -bitstream-bitstream charter-bold-i-normal--0-0-0-0-p-0-iso8859-1
+c0648bt_.pfb -bitstream-bitstream charter-medium-r-normal--0-0-0-0-p-0-iso10646-1
+c0648bt_.pfb -bitstream-bitstream charter-medium-r-normal--0-0-0-0-p-0-iso8859-1
+c0649bt_.pfb -bitstream-bitstream charter-medium-i-normal--0-0-0-0-p-0-iso10646-1
+c0649bt_.pfb -bitstream-bitstream charter-medium-i-normal--0-0-0-0-p-0-iso8859-1
+cursor.pfa -xfree86-cursor-medium-r-normal--0-0-0-0-p-0-adobe-fontspecific
diff --git a/e2e-tests/cypress/fonts/google-noto-emoji/NotoColorEmoji.ttf b/e2e-tests/cypress/fonts/google-noto-emoji/NotoColorEmoji.ttf
new file mode 100644
index 00000000..69cf21a1
Binary files /dev/null and b/e2e-tests/cypress/fonts/google-noto-emoji/NotoColorEmoji.ttf differ
diff --git a/e2e-tests/cypress/integration/app_features_spec.js b/e2e-tests/cypress/integration/app_features_spec.js
index cb5261a2..80f65f97 100644
--- a/e2e-tests/cypress/integration/app_features_spec.js
+++ b/e2e-tests/cypress/integration/app_features_spec.js
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import moment from 'moment';
-const dateFormatter = value => moment(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
+const dateFormatter = value => moment.utc(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
describe('App Features Tests', () => {
beforeEach(() => {
@@ -34,7 +34,7 @@ describe('App Features Tests', () => {
},
}).as('getSubjects');
cy.visit('/');
- cy.contains('My Projects')
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
cy.contains('New Software Version is Available').should('not.exist')
cy.get('[data-cy=projectCard]').last().contains('Manage').click()
cy.wait('@getSubjects')
@@ -55,7 +55,7 @@ describe('App Features Tests', () => {
},
}).as('getSubjects');
cy.visit('/');
- cy.contains('My Projects')
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
cy.get('[data-cy=projectCard]').last().contains('Manage').click()
cy.wait('@getSubjects')
diff --git a/e2e-tests/cypress/integration/badges_spec.js b/e2e-tests/cypress/integration/badges_spec.js
index aa9d2f25..f564e2d0 100644
--- a/e2e-tests/cypress/integration/badges_spec.js
+++ b/e2e-tests/cypress/integration/badges_spec.js
@@ -19,26 +19,526 @@ describe('Badges Tests', () => {
cy.request('POST', '/app/projects/proj1', {
projectId: 'proj1',
name: "proj1"
- })
+ }).as('createProject');
+
+ Cypress.Commands.add("gemStartNextMonth", () => {
+ cy.get('[data-cy="startDatePicker"] header .next').first().click()
+ });
+ Cypress.Commands.add("gemStartPrevMonth", () => {
+ cy.get('[data-cy="startDatePicker"] header .prev').first().click()
+ });
+ Cypress.Commands.add("gemEndNextMonth", () => {
+ cy.get('[data-cy="endDatePicker"] header .next').first().click()
+ });
+ Cypress.Commands.add("gemEndPrevMonth", () => {
+ cy.get('[data-cy="endDatePicker"] header .prev').first().click()
+ });
+ Cypress.Commands.add("gemStartSetDay", (dayNum) => {
+ cy.get('[data-cy="startDatePicker"] .day').contains(dayNum).click()
+ });
+ Cypress.Commands.add("gemEndSetDay", (dayNum) => {
+ cy.get('[data-cy="endDatePicker"] .day').contains(dayNum).click()
+ });
+
+ cy.server();
+ cy.route('POST', '/admin/projects/proj1/badgeNameExists').as('nameExistsCheck');
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
});
it('create badge with special chars', () => {
const expectedId = 'LotsofspecialPcharsBadge';
- const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}{P c'ha'rs";
- cy.server().route('POST', `/admin/projects/proj1/badges/${expectedId}`).as('postNewBadge');
+ const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}{P/ c'ha'rs";
+
+ cy.route('POST', `/admin/projects/proj1/badges/${expectedId}`).as('postNewBadge');
+ cy.route('POST', '/admin/projects/proj1/badgeNameExists').as('nameExistsCheck');
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
+ cy.get('@createProject').should((response) => {
+ expect(response.status).to.eql(200)
+ });
cy.visit('/projects/proj1/badges');
- cy.clickButton('Badge')
+ cy.wait('@loadBadges');
+ cy.clickButton('Badge');
- cy.get('#badgeName').type(providedName)
+ cy.get('#badgeName').type(providedName);
- cy.getIdField().should('have.value', expectedId)
+ cy.wait('@nameExistsCheck');
- cy.clickSave()
+ cy.getIdField().should('have.value', expectedId);
+
+ cy.clickSave();
cy.wait('@postNewBadge');
cy.contains('ID: Lotsofspecial')
});
+ it('create badge with enter key', () => {
+ const expectedId = 'LotsofspecialPcharsBadge';
+ const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}{P/ c'ha'rs";
+
+ cy.route('POST', `/admin/projects/proj1/badges/${expectedId}`).as('postNewBadge');
+ cy.route('POST', '/admin/projects/proj1/badgeNameExists').as('nameExistsCheck');
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
+ cy.get('@createProject').should((response) => {
+ expect(response.status).to.eql(200)
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.wait('@loadBadges');
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type(providedName);
+
+ cy.wait('@nameExistsCheck');
+
+ cy.getIdField().should('have.value', expectedId);
+
+ cy.get('#badgeName').type('{enter}');
+ cy.wait('@postNewBadge');
+
+ cy.contains('ID: Lotsofspecial')
+ });
+
+ if('Close badge dialog', () => {
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
+ cy.get('@createProject').should((response) => {
+ expect(response.status).to.eql(200)
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.wait('@loadBadges');
+ cy.clickButton('Badge');
+ cy.get('[data-cy=closeBadgeButton]').click();
+ cy.get('[data-cy=closeBadgeButton]').should('not.be.visible');
+ });
+
+ it('cannot publish badge with no skills', () => {
+ cy.route('POST', `/admin/projects/proj1/badges/anameBadge`).as('postNewBadge');
+ cy.route('POST', '/admin/projects/proj1/badgeNameExists').as('nameExistsCheck');
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
+ cy.get('@createProject').should((response) => {
+ expect(response.status).to.eql(200)
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.wait('@loadBadges');
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('a name');
+
+ cy.wait('@nameExistsCheck');
+
+ cy.getIdField().should('have.value', 'anameBadge');
+
+ cy.clickSave();
+ cy.wait('@postNewBadge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('This Badge has no assigned Skills. A Badge cannot be published without at least one assigned Skill.').should('be.visible');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ });
+
+ it('inactive badge displays warning', () => {
+ const expectedId = 'InactiveBadge';
+ const providedName = 'Inactive';
+ cy.server();
+ cy.route('GET', '/app/userInfo').as('getUserInfo');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('hasSupervisor');
+ cy.route('POST', `/admin/projects/proj1/badges/${expectedId}`).as('postNewBadge');
+ cy.route('POST', '/admin/projects/proj1/badgeNameExists').as('nameExistsCheck');
+ cy.route('GET', '/admin/projects/proj1/badges').as('loadBadges');
+
+ cy.get('@createProject').should((response) => {
+ expect(response.status).to.eql(200)
+ });
+
+ cy.visit('/projects/proj1/badges');
+
+ cy.wait('@loadBadges');
+ cy.wait('@getUserInfo');
+ cy.wait('@hasSupervisor');
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type(providedName);
+
+ cy.wait('@nameExistsCheck');
+
+ cy.clickSave();
+ cy.wait('@postNewBadge');
+
+ cy.get('div.card-body i.fa-exclamation-circle').should('be.visible');
+ });
+
+ it('name causes id to fail validation', () => {
+ cy.request('POST', '/admin/projects/proj1/badges/badgeExist', {
+ projectId: 'proj1',
+ name: "Badge Exist",
+ badgeId: 'badgeExist'
+ })
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+
+ // name causes id to be too long
+ const msg = 'Badge ID cannot exceed 50 characters';
+ const validNameButInvalidId = Array(46).fill('a').join('');
+ cy.get('[data-cy=badgeName]').click();
+ cy.get('[data-cy=badgeName]').invoke('val', validNameButInvalidId).trigger('input');
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+ cy.get('[data-cy=badgeName]').type('{backspace}');
+ cy.get('[data-cy=idError]').contains(msg).should('not.be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.enabled');
+ });
+
+ it('badge validation', () => {
+ // create existing badge
+ cy.request('POST', '/admin/projects/proj1/badges/badgeExist', {
+ projectId: 'proj1',
+ name: "Badge Exist",
+ badgeId: 'badgeExist'
+ })
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+
+ const overallFormValidationMsg = 'Form did NOT pass validation, please fix and try to Save again';
+
+ // name is too short
+ let msg = 'Badge Name cannot be less than 3 characters';
+ cy.get('#badgeName').type('Te');
+ cy.get('[data-cy=badgeNameError]').contains(msg).should('be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+ cy.get('#badgeName').type('Tes');
+ cy.get('[data-cy=badgeNameError]').should('not.be.visible');
+
+ // name too long
+ msg = 'Badge Name cannot exceed 50 characters';
+ cy.contains('Enable').click();
+ cy.getIdField().clear().type("badgeId");
+ const invalidName = Array(51).fill('a').join('');
+ cy.get('#badgeName').clear();
+ cy.get('#badgeName').invoke('val', invalidName).trigger('input');
+ cy.get('[data-cy=badgeNameError]').contains(msg).should('be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+ cy.get('#badgeName').type('{backspace}');
+ cy.get('[data-cy=badgeNameError]').should('not.be.visible');
+
+ // id too short
+ msg = 'Badge ID cannot be less than 3 characters';
+ cy.getIdField().clear().type("aa");
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+ cy.getIdField().type("a");
+ cy.get('[data-cy=idError]').should('not.be.visible');
+
+ // id too long
+ msg = 'Badge ID cannot exceed 50 characters';
+ const invalidId = Array(51).fill('a').join('');
+ cy.getIdField().clear()
+ cy.getIdField().click().type(invalidId);
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.getIdField().type('{backspace}');
+ cy.get('[data-cy=idError]').should('not.be.visible');
+
+ // id must not have special chars
+ msg = 'Badge ID may only contain alpha-numeric characters';
+ cy.getIdField().clear().type('With$Special');
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.getIdField().clear().type('GoodToGo');
+ cy.get('[data-cy=idError]').should('not.be.visible');
+
+ cy.getIdField().clear().type('SomeId');
+ // !L@o#t$s of %s^p&e*c(i)a_l++_|}{P/ c'ha'rs
+ let specialChars = [' ', '_', '!', '@', '#', '%', '^', '&', '*', '(', ')', '-', '+', '='];
+ specialChars.forEach((element) => {
+ cy.getIdField().type(element);
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.getIdField().type('{backspace}');
+ cy.contains(msg).should('not.be.visible');
+ })
+
+ // badge name must not be already taken
+ msg = 'The value for Badge Name is already taken';
+ cy.get('#badgeName').clear().type('Badge Exist');
+ cy.get('[data-cy=badgeNameError]').contains(msg).should('be.visible');
+ cy.get('#badgeName').type('1');
+ cy.get('[data-cy=badgeNameError]').should('not.be.visible');
+
+ // badge id must not already exist
+ msg = 'The value for Badge ID is already taken';
+ cy.getIdField().clear().type('badgeExist');
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.getIdField().type('1');
+ cy.get('[data-cy=idError]').should('not.be.visible');
+
+ // max description
+ msg='Badge Description cannot exceed 2000 characters';
+ const invalidDescription = Array(2000).fill('a').join('');
+ // it takes way too long using .type method
+ cy.get('#markdown-editor textarea').invoke('val', invalidDescription).trigger('change');
+ cy.get('#markdown-editor').type('a');
+ cy.get('[data-cy=badgeDescriptionError]').contains(msg).should('be.visible');
+ cy.get('#markdown-editor').type('{backspace}');
+ cy.get('[data-cy=badgeDescriptionError]').should('not.be.visible')
+
+ // finally let's save
+ cy.clickSave();
+ cy.wait('@loadBadges');
+ cy.contains('Badge Exist1');
+ });
+
+ it('gem start and end time validation', () => {
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+ cy.get('[data-cy="gemEditContainer"]').click()
+ cy.contains('Enable Gem Feature').click();
+ cy.contains('Start Date');
+
+ cy.get('#badgeName').type('Test Badge');
+
+ // dates should not overlap
+ let msg = 'Start Date must come before End Date';
+ cy.gemStartNextMonth();
+ cy.gemStartSetDay(1);
+ cy.gemEndNextMonth();
+ cy.gemEndSetDay(1);
+ cy.get('[data-cy=endDateError]').contains(msg).should('be.visible');
+ cy.gemEndSetDay(2);
+ cy.get('[data-cy=endDateError]').should('not.be.visible');
+
+ // start date should be before end date
+ msg = 'Start Date must come before End Date';
+ cy.gemStartSetDay(3);
+ cy.get('[data-cy=startDateError]').contains(msg).should('be.visible');
+ cy.gemEndSetDay(4);
+ cy.get('[data-cy=startDateError]').should('not.be.visible');
+
+ // dates should not be in the past
+ msg = 'End Date cannot be in the past';
+ cy.gemStartPrevMonth();
+ cy.gemStartPrevMonth();
+ cy.gemStartSetDay(1);
+ cy.gemEndPrevMonth();
+ cy.gemEndPrevMonth();
+ cy.gemEndSetDay(2);
+ cy.get('[data-cy=endDateError]').contains(msg).should('be.visible');
+
+ // should not save if there are validation errors
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+
+ // fix the errors and save
+ cy.gemStartNextMonth();
+ cy.gemStartNextMonth();
+ cy.gemEndNextMonth();
+ cy.gemEndNextMonth();
+ cy.gemStartSetDay(1);
+ cy.gemEndSetDay(2);
+ cy.get('[data-cy=endDateError]').should('not.be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.enabled');
+
+ cy.clickSave();
+ cy.wait('@loadBadges');
+ cy.contains('Test Badge');
+ });
+
+ it('Badge is disabled when created, can only be enabled once', () => {
+
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+ cy.get('#badgeName').type('Test Badge');
+ cy.clickSave();
+ cy.wait('@loadBadges');
+
+ cy.get('[data-cy=manageBadge]').click();
+ cy.get('#skills-selector').click();
+ cy.get('#skills-selector input[type=text]').type('{enter}');
+ cy.contains('.router-link-active', 'Badges').click();
+
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+
+ cy.wait('@loadBadges');
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=goLive]').should('not.exist');
+ });
+
+ it('Badge is disabled when created, canceling confirm dialog leaves badge disabled', () => {
+
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+ cy.get('#badgeName').type('Test Badge');
+ cy.clickSave();
+ cy.wait('@loadBadges');
+
+ cy.get('[data-cy=manageBadge]').click();
+ cy.get('#skills-selector').click();
+ cy.get('#skills-selector input[type=text]').type('{enter}');
+ cy.contains('.router-link-active', 'Badges').click();
+
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Cancel').click();
+
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('not.exist');
+ cy.get('[data-cy=goLive]').should('exist');
+ });
+
+ it('Can add Skill requirements to disabled badge', () => {
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+ cy.get('#badgeName').type('Test Badge');
+ cy.clickSave();
+ cy.wait('@loadBadges');
+ cy.get('[data-cy=manageBadge]').click();
+ cy.get('#skills-selector').click();
+ cy.get('#skills-selector input[type=text]').type('{enter}');
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+ cy.wait('@loadBadges');
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ });
+
+ it('removing last skill from enabled badge does not disable badge', () => {
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.visit('/projects/proj1/badges');
+ cy.clickButton('Badge');
+ cy.contains('New Badge');
+ cy.get('#badgeName').type('Test Badge');
+ cy.clickSave();
+ cy.wait('@loadBadges');
+ cy.get('[data-cy=manageBadge]').click();
+ cy.get('#skills-selector').click();
+ cy.get('#skills-selector input[type=text]').type('{enter}');
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+ cy.wait('@loadBadges');
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=manageBadge]').click();
+ cy.get('[data-cy=deleteSkill]').click();
+ cy.contains('YES, Delete It!').click();
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ });
+
+ it('badge user details does not break breadcrumb bar', () => {
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.request('POST', '/admin/projects/proj1/badges/badge1', {
+ projectId: 'proj1',
+ badgeId: 'badge1',
+ name: 'Badge 1',
+ "iconClass":"fas fa-ghost",
+ description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ });
+
+ cy.request('POST', '/admin/projects/proj1/badge/badge1/skills/skill1')
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: 'someuser', timestamp: new Date().getTime()})
+
+
+ cy.visit('/projects/proj1/badges/badge1');
+ cy.get('[data-cy=nav-Users]').click();
+ cy.contains('Details').click();
+ cy.get('[data-cy=breadcrumb-badge1]').should('be.visible');
+ cy.get('[data-cy=breadcrumb-Users]').should('be.visible');
+ })
-})
+});
diff --git a/e2e-tests/cypress/integration/client-display/client-display-features_spec.js b/e2e-tests/cypress/integration/client-display/client-display-features_spec.js
index 4f60e3b4..4c370094 100644
--- a/e2e-tests/cypress/integration/client-display/client-display-features_spec.js
+++ b/e2e-tests/cypress/integration/client-display/client-display-features_spec.js
@@ -14,17 +14,16 @@
* limitations under the License.
*/
import moment from 'moment';
-const dateFormatter = value => moment(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
+const dateFormatter = value => moment.utc(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
describe('Client Display Features Tests', () => {
-
- before(() => {
- cy.disableUILogin();
- });
-
- after(function () {
- cy.enableUILogin();
- });
+ const snapshotOptions = {
+ blackout: ['[data-cy=pointHistoryChart]'],
+ failureThreshold: 0.03, // threshold for entire image
+ failureThresholdType: 'percent', // percent of image or number of pixels
+ customDiffConfig: { threshold: 0.01 }, // threshold for each pixel
+ capture: 'fullPage', // When fullPage, the application under test is captured in its entirety from top to bottom.
+ };
beforeEach(() => {
Cypress.env('disabledUILoginProp', true);
@@ -39,6 +38,23 @@ describe('Client Display Features Tests', () => {
helpUrl: 'http://doHelpOnThisSubject.com',
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
});
+
+ Cypress.Commands.add("createSkill", (num) => {
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill${num}`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: `skill${num}`,
+ name: `This is ${num}`,
+ type: 'Skill',
+ pointIncrement: 50,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ version: 0,
+ helpUrl: 'http://doHelpOnThisSkill.com'
+ });
+ });
})
it('display new version banner when software is updated', () => {
@@ -77,6 +93,7 @@ describe('Client Display Features Tests', () => {
});
it('do not display new version banner if lib version in headers is older than lib version in local storage', () => {
+ const mockedLibVersion = dateFormatter(new Date() - 1000 * 60 * 60 * 24 * 5);
cy.server().route({
url: '/api/projects/proj1/subjects/subj1/summary',
status: 200,
@@ -96,7 +113,7 @@ describe('Client Display Features Tests', () => {
'helpUrl': 'http://doHelpOnThisSubject.com'
},
headers: {
- 'skills-client-lib-version': dateFormatter(new Date() - 1000 * 60 * 60 * 24)
+ 'skills-client-lib-version': mockedLibVersion
},
}).as('getSubjectSummary');
@@ -108,7 +125,7 @@ describe('Client Display Features Tests', () => {
'position': 1
},
headers: {
- 'skills-client-lib-version': dateFormatter(new Date() - 1000 * 60 * 60 * 24)
+ 'skills-client-lib-version': mockedLibVersion
},
}).as('getRank');
@@ -117,7 +134,7 @@ describe('Client Display Features Tests', () => {
status: 200,
response: { 'pointsHistory': [] },
headers: {
- 'skills-client-lib-version': dateFormatter(new Date() - 1000 * 60 * 60 * 24)
+ 'skills-client-lib-version': mockedLibVersion
},
}).as('getPointHistory');
@@ -133,4 +150,135 @@ describe('Client Display Features Tests', () => {
cy.contains('New Skills Software Version is Available').should('not.exist')
});
+ it('achieve level 5, then add new skill', () => {
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 50,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ version: 0,
+ helpUrl: 'http://doHelpOnThisSkill.com'
+ });
+
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime() - 1000*60*60*24})
+
+ cy.cdVisit('/');
+
+ cy.contains('Overall Points');
+
+ cy.get('[data-cy=subjectTile]').eq(0).contains('Subject 1')
+ cy.get('[data-cy=subjectTile]').eq(0).contains('Level 5')
+
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill2`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill2',
+ name: `This is 2`,
+ type: 'Skill',
+ pointIncrement: 50,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ version: 0,
+ helpUrl: 'http://doHelpOnThisSkill.com'
+ });
+
+ cy.cdVisit('/');
+
+ cy.contains('Overall Points');
+
+ cy.get('[data-cy=subjectTile]').eq(0).contains('Subject 1')
+ cy.get('[data-cy=subjectTile]').eq(0).contains('Level 5')
+ });
+
+ it('deps are added to partially achieved skill', () => {
+ cy.createSkill(1);
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.createSkill(2);
+ cy.createSkill(3);
+ cy.request('POST', `/admin/projects/proj1/skills/skill1/dependency/skill2`)
+ cy.request('POST', `/admin/projects/proj1/skills/skill2/dependency/skill3`)
+
+ cy.cdVisit('/');
+
+ cy.cdClickSubj(0, 'Subject 1');
+
+ cy.matchImageSnapshot(`Subject-WithLockedSkills-ThatWerePartiallyAchieved`, snapshotOptions);
+
+ cy.cdClickSkill(0);
+ cy.contains('This is 1');
+ const expectedMsg = 'You were able to earn partial points before the dependencies were added';
+ cy.contains(expectedMsg);
+ // should render dependencies section
+ cy.contains('Dependencies');
+
+ cy.matchImageSnapshot(`LockedSkill-ThatWasPartiallyAchieved`, snapshotOptions);
+
+ // make sure the other locked skill doesn't contain the same message
+ cy.cdBack('Subject 1');
+ cy.cdClickSkill(1);
+ cy.contains('This is 2');
+ cy.contains(expectedMsg).should('not.exist');
+
+ // make sure the skill without deps doesn't have the message
+ cy.cdBack('Subject 1');
+ cy.cdClickSkill(2);
+ cy.contains('This is 3');
+ cy.contains(expectedMsg).should('not.exist');
+ });
+
+ it('deps are added to fully achieved skill', () => {
+ cy.createSkill(1);
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {
+ userId: Cypress.env('proxyUser'),
+ timestamp: new Date().getTime()
+ })
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {
+ userId: Cypress.env('proxyUser'),
+ timestamp: new Date().getTime() - 1000 * 60 * 24
+ })
+ cy.createSkill(2);
+ cy.request('POST', `/admin/projects/proj1/skills/skill1/dependency/skill2`)
+
+ cy.cdVisit('/');
+ cy.cdClickSubj(0, 'Subject 1');
+
+ cy.matchImageSnapshot(`Subject-WithLockedSkills-ThatWereFullyAchieved`, snapshotOptions);
+
+ cy.cdClickSkill(0);
+ cy.contains('This is 1');
+ const msg = "Congrats! You completed this skill before the dependencies were added";
+ cy.contains(msg);
+
+ cy.matchImageSnapshot(`LockedSkill-ThatWasFullyAchieved`, snapshotOptions);
+
+ // other skill should not have the message
+ cy.cdBack('Subject 1');
+ cy.cdClickSkill(1);
+ cy.contains('This is 2');
+ cy.contains(msg).should('not.exist');
+
+ // now let's achieve the dependent skill
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {
+ userId: Cypress.env('proxyUser'),
+ timestamp: new Date().getTime()
+ })
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {
+ userId: Cypress.env('proxyUser'),
+ timestamp: new Date().getTime() - 1000 * 60 * 24
+ })
+ cy.cdBack('Subject 1');
+ cy.cdClickSkill(0);
+ cy.contains('This is 1');
+ cy.contains(msg).should('not.exist');
+ });
+
})
diff --git a/e2e-tests/cypress/integration/client-display/client-display-markdown_spec.js b/e2e-tests/cypress/integration/client-display/client-display-markdown_spec.js
new file mode 100644
index 00000000..a69cff69
--- /dev/null
+++ b/e2e-tests/cypress/integration/client-display/client-display-markdown_spec.js
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import moment from 'moment';
+const dateFormatter = value => moment.utc(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
+
+describe('Client Display Markdown Tests', () => {
+ const snapshotOptions = {
+ blackout: ['[data-cy=pointHistoryChart]'],
+ failureThreshold: 0.03, // threshold for entire image
+ failureThresholdType: 'percent', // percent of image or number of pixels
+ customDiffConfig: { threshold: 0.01 }, // threshold for each pixel
+ capture: 'fullPage', // When fullPage, the application under test is captured in its entirety from top to bottom.
+ };
+
+ beforeEach(() => {
+ Cypress.env('disabledUILoginProp', true);
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: 'proj1'
+ });
+ })
+
+ it('subject\'s markdown', () => {
+ const markdown = "# Title1\n## Title2\n### Title 3\n#### Title 4\n##### Title 5\nTitle 6\n\n" +
+ "---\n" +
+ "# Emphasis\n" +
+ "italics: *italicized* or _italicized_\n\n" +
+ "bold: **bolded** or __bolded__\n\n" +
+ "combination **_bolded & italicized_**\n\n" +
+ "strikethrough: ~~struck~~\n\n" +
+ "---\n" +
+ "# Inline\n" +
+ "Inline `code` has `back-ticks around` it\n\n" +
+ "---\n" +
+ "# Multiline\n" +
+ "\n" +
+ "\n" +
+ "```\n" +
+ "import { SkillsDirective } from '@skilltree/skills-client-vue';\n" +
+ "Vue.use(SkillsDirective);\n" +
+ "```\n" +
+ "# Lists\n" +
+ "Ordered Lists:\n" +
+ "1. Item one\n" +
+ "1. Item two\n" +
+ "1. Item three (actual number does not matter)\n\n" +
+ "If List item has multiple lines of text, subsequent lines must be idented four spaces, otherwise list item numbers will reset, e.g.,\n" +
+ "1. item one\n" +
+ " paragrah one\n" +
+ "1. item two\n" +
+ "1. item three\n" +
+ "\n" +
+ "Unordered Lists\n" +
+ "* Item\n" +
+ "* Item\n" +
+ "* Item\n" +
+ "___\n" +
+ "# Links\n" +
+ "[in line link](https://www.somewebsite.com)\n" +
+ "___\n" +
+ "# Blockquotes\n" +
+ "> Blockquotes are very handy to emulate reply text.\n" +
+ "> This line is part of the same quote.\n\n" +
+ "# Horizontal rule\n" +
+ "Use three or more dashes, asterisks, or underscores to generate a horizontal rule line\n" +
+ "\n" +
+ "Separate me\n\n" +
+ "___\n\n" +
+ "Separate me\n\n" +
+ "---\n\n" +
+ "Separate me\n\n" +
+ "***\n\n" +
+ "# Emojis\n" +
+ ":star: :star: :star: :star:\n" +
+ "";
+
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: 'Subject 1',
+ helpUrl: 'http://doHelpOnThisSubject.com',
+ description: markdown
+ });
+
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: `skill1`,
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 50,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ description: markdown,
+ version: 0,
+ helpUrl: 'http://doHelpOnThisSkill.com'
+ });
+
+ cy.request('POST', '/admin/projects/proj1/badges/badge1', {
+ projectId: 'proj1',
+ badgeId: 'badge1',
+ name: 'Badge 1',
+ "iconClass":"fas fa-ghost",
+ description: markdown,
+ });
+
+ cy.cdVisit('/');
+ cy.contains('Overall Points');
+
+ // check subject
+ cy.cdClickSubj(0, 'Subject 1');
+ cy.contains('Emphasis');
+ cy.matchImageSnapshot(`Markdown-subject`, snapshotOptions);
+
+ // check skill
+ cy.cdClickSkill(0);
+ cy.contains('This is 1');
+ cy.contains('Emphasis');
+ cy.matchImageSnapshot(`Markdown-skill`, snapshotOptions);
+
+ // check expanded skill
+ cy.cdBack('Subject 1');
+ cy.get('[data-cy=toggleSkillDetails]').click()
+ cy.contains('Overall Points Earned');
+ cy.matchImageSnapshot(`Markdown-Skill-Preview`, snapshotOptions);
+
+ cy.cdVisit('/');
+ cy.contains('Overall Points');
+
+ // check badge
+ cy.cdClickBadges();
+ cy.contains('Badges');
+ cy.contains('Emphasis');
+ cy.matchImageSnapshot(`Markdown-Badge`, snapshotOptions);
+ });
+});
diff --git a/e2e-tests/cypress/integration/client-display/client-display-theme_spec.js b/e2e-tests/cypress/integration/client-display/client-display-theme_spec.js
index c954aa3e..c0374551 100644
--- a/e2e-tests/cypress/integration/client-display/client-display-theme_spec.js
+++ b/e2e-tests/cypress/integration/client-display/client-display-theme_spec.js
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import moment from 'moment';
-const dateFormatter = value => moment(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
+const dateFormatter = value => moment.utc(value).format('YYYY-MM-DD[T]HH:mm:ss[Z]');
describe('Client Display Tests', () => {
const snapshotOptions = {
- blackout: ['[data-cy=pointHistoryChart]'],
+ blackout: ['[data-cy=pointHistoryChart]', '#dependent-skills-network', '[data-cy=achievementDate]'],
failureThreshold: 0.03, // threshold for entire image
failureThresholdType: 'percent', // percent of image or number of pixels
- customDiffConfig: { threshold: 0.1 }, // threshold for each pixel
- // capture: 'viewport', // capture viewport in screenshot
+ customDiffConfig: { threshold: 0.01 }, // threshold for each pixel
+ capture: 'fullPage', // When fullPage, the application under test is captured in its entirety from top to bottom.
};
const sizes = [
'iphone-6',
@@ -33,8 +33,6 @@ describe('Client Display Tests', () => {
];
before(() => {
- cy.disableUILogin();
-
Cypress.Commands.add("cdInitProjWithSkills", () => {
cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
projectId: 'proj1',
@@ -145,20 +143,16 @@ describe('Client Display Tests', () => {
cy.request('POST', '/admin/projects/proj1/badge/badge2/skills/skill3')
- cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: 'user0', timestamp: new Date().getTime()})
- cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: 'user0', timestamp: new Date().getTime() - 1000*60*60*24})
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime() - 1000*60*60*24})
- cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: 'user0', timestamp: new Date().getTime()})
- cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: 'user0', timestamp: new Date().getTime() - 1000*60*60*24})
+ cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime() - 1000*60*60*24})
});
});
- after(function () {
- cy.enableUILogin();
- });
-
beforeEach(() => {
Cypress.env('disabledUILoginProp', true);
cy.request('POST', '/app/projects/proj1', {
@@ -239,8 +233,6 @@ describe('Client Display Tests', () => {
cy.contains('This is 4');
cy.contains('Lorem ipsum dolor sit amet');
cy.contains('Achieved Dependencies');
- // wait for graph to finish animating
- cy.wait(4000);
cy.matchImageSnapshot(`Subject0-Skill3-Details_${size}`, snapshotOptions);
});
diff --git a/e2e-tests/cypress/integration/client-display/client-display_point_history_chart_spec.js b/e2e-tests/cypress/integration/client-display/client-display_point_history_chart_spec.js
new file mode 100644
index 00000000..b14eb6fc
--- /dev/null
+++ b/e2e-tests/cypress/integration/client-display/client-display_point_history_chart_spec.js
@@ -0,0 +1,736 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var moment = require('moment-timezone');
+
+describe('Client Display Tests', () => {
+
+ const waitForAnimation = 3000;
+
+ beforeEach(() => {
+ Cypress.env('disabledUILoginProp', true);
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: 'proj1'
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1', subjectId: 'subj1', name: 'Subject 1',
+ });
+ });
+
+ it('multiple achievements in the middle', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-13T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-14T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-15T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-16T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-17T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-18T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-19T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-20T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-21T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-22T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500,
+ 'name': 'Levels 1, 2, 3'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+
+ it('point history with data from server', () => {
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ version: 0,
+ helpUrl: 'http://doHelpOnThisSkill.com'
+ });
+
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill2`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill2',
+ name: `This is 2`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill3`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill3',
+ name: `This is 3`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill4`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill4',
+ name: `This is 4`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 2,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ const m = moment('2020-09-12 11', 'YYYY-MM-DD HH');
+ const orig = m.clone()
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(4, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(3, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(2, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(1, 'day').format('x')})
+
+ cy.server().route('/api/projects/proj1/pointHistory').as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ cy.contains('Levels 1, 2');
+
+ });
+
+
+ it('multiple achievements at the last date', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500,
+ 'name': 'Levels 1, 2, 3'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+ it('multiple achievements on first date', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-02T00:00:00.000+00:00',
+ 'points': 400,
+ 'name': 'Levels 1, 2, 3'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+ it('single achievements on the first date', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 500
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-02T00:00:00.000+00:00',
+ 'points': 400,
+ 'name': 'Level 1'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+ it('single achievement on the last date', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500,
+ 'name': 'Level 1'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+
+ it('achievements throughout time', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100,
+ 'name': 'Level 1'
+ },{
+ 'achievedOn': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300,
+ 'name': 'Level 2'
+ },{
+ 'achievedOn': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400,
+ 'name': 'Level 3'
+ },{
+ 'achievedOn': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400,
+ 'name': 'Level 4'
+ },{
+ 'achievedOn': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500,
+ 'name': 'Level 5'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+
+ it('levels achieved on subsequent days', () => {
+ const data = {
+ 'pointsHistory': [{
+ 'dayPerformed': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100
+ }, {
+ 'dayPerformed': '2020-09-03T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-04T00:00:00.000+00:00',
+ 'points': 200
+ }, {
+ 'dayPerformed': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-07T00:00:00.000+00:00',
+ 'points': 300
+ }, {
+ 'dayPerformed': '2020-09-08T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-09T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-10T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400
+ }, {
+ 'dayPerformed': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500
+ }],
+ 'achievements': [{
+ 'achievedOn': '2020-09-02T00:00:00.000+00:00',
+ 'points': 100,
+ 'name': 'Level 1'
+ },{
+ 'achievedOn': '2020-09-05T00:00:00.000+00:00',
+ 'points': 300,
+ 'name': 'Level 2'
+ },{
+ 'achievedOn': '2020-09-06T00:00:00.000+00:00',
+ 'points': 300,
+ 'name': 'Level 3'
+ },{
+ 'achievedOn': '2020-09-11T00:00:00.000+00:00',
+ 'points': 400,
+ 'name': 'Level 4'
+ },{
+ 'achievedOn': '2020-09-12T00:00:00.000+00:00',
+ 'points': 500,
+ 'name': 'Level 5'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+
+ function createTimeline(start, numDays, startScore, increaseBy, increaseEvery, stopIncreasingAfterDays = -1) {
+ const m = moment.utc(start, 'YYYY-MM-DD HH');
+ const pointHistory = [];
+ let score = startScore;
+ for( let i=0; i
i)) {
+ score += increaseBy;
+ }
+ pointHistory.push({
+ 'dayPerformed': m.clone().add(i, 'day').tz('UTC').format(),
+ 'points': score,
+ });
+ }
+ return pointHistory;
+ }
+
+ it('levels achieved on subsequent days with many days in the timeline', () => {
+ const pointHistory = createTimeline('2019-09-12', 120, 10, 10, 10);
+ pointHistory.forEach((value) => {
+ cy.log(value);
+ });
+ const data = {
+ 'pointsHistory': pointHistory,
+ 'achievements': [{
+ 'achievedOn': pointHistory[50].dayPerformed,
+ 'points': pointHistory[50].points,
+ 'name': 'Level 1'
+ },{
+ 'achievedOn': pointHistory[51].dayPerformed,
+ 'points': pointHistory[51].points,
+ 'name': 'Level 2'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+ it('rapid growth of points af start followed by no activity', () => {
+ const pointHistory = createTimeline('2019-09-12', 240, 10, 100, 7, 30);
+ cy.log(`Generated ${pointHistory.length} points`);
+ const data = {
+ 'pointsHistory': pointHistory,
+ 'achievements': [{
+ 'achievedOn': pointHistory[2].dayPerformed,
+ 'points': pointHistory[2].points,
+ 'name': 'Level 1'
+ },{
+ 'achievedOn': pointHistory[7].dayPerformed,
+ 'points': pointHistory[7].points,
+ 'name': 'Level 2'
+ },{
+ 'achievedOn': pointHistory[12].dayPerformed,
+ 'points': pointHistory[12].points,
+ 'name': 'Level 3'
+ },{
+ 'achievedOn': pointHistory[23].dayPerformed,
+ 'points': pointHistory[23].points,
+ 'name': 'Level 4'
+ },{
+ 'achievedOn': pointHistory[30].dayPerformed,
+ 'points': pointHistory[30].points,
+ 'name': 'Level 5'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+
+ cy.cdVisit('/');
+ cy.wait('@getPointHistory');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+
+ cy.contains('Reset Zoom').click();
+ cy.wait(waitForAnimation);
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot('PointHistoryChart-Reset');
+ });
+
+
+ it('subject: rapid growth of points af start followed by no activity', () => {
+ const pointHistory = createTimeline('2019-09-12', 240, 10, 100, 7, 30);
+ cy.log(`Generated ${pointHistory.length} points`);
+ const data = {
+ 'pointsHistory': pointHistory,
+ 'achievements': [{
+ 'achievedOn': pointHistory[2].dayPerformed,
+ 'points': pointHistory[2].points,
+ 'name': 'Level 1'
+ },{
+ 'achievedOn': pointHistory[7].dayPerformed,
+ 'points': pointHistory[7].points,
+ 'name': 'Level 2'
+ },{
+ 'achievedOn': pointHistory[12].dayPerformed,
+ 'points': pointHistory[12].points,
+ 'name': 'Level 3'
+ },{
+ 'achievedOn': pointHistory[23].dayPerformed,
+ 'points': pointHistory[23].points,
+ 'name': 'Level 4'
+ },{
+ 'achievedOn': pointHistory[30].dayPerformed,
+ 'points': pointHistory[30].points,
+ 'name': 'Level 5'
+ }]
+ }
+
+ cy.server().route({
+ url: '/api/projects/proj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistory');
+ cy.server().route({
+ url: '/api/projects/proj1/subjects/subj1/pointHistory',
+ status: 200,
+ response: data,
+ }).as('getPointHistorySubject');
+
+
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+ cy.wait('@getPointHistorySubject');
+
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+ it('empty point history', () => {
+ cy.cdVisit('/');
+ cy.server().route('/api/projects/proj1/pointHistory').as('getPointHistory');
+ cy.wait('@getPointHistory')
+ // let's wait for animation to complete
+ cy.wait(waitForAnimation);
+
+ cy.get('[data-cy=pointHistoryChart]').matchImageSnapshot();
+ });
+
+
+});
+
diff --git a/e2e-tests/cypress/integration/client-display/client-display_spec.js b/e2e-tests/cypress/integration/client-display/client-display_spec.js
index 6c5ced61..956feb9d 100644
--- a/e2e-tests/cypress/integration/client-display/client-display_spec.js
+++ b/e2e-tests/cypress/integration/client-display/client-display_spec.js
@@ -13,17 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import moment from 'moment';
+
describe('Client Display Tests', () => {
- const cssAttachedToNavigableCards = 'skills-navigable-item';
+ const snapshotOptions = {
+ blackout: ['[data-cy=pointHistoryChart]', '[data-cy=timePassed]'],
+ failureThreshold: 0.03, // threshold for entire image
+ failureThresholdType: 'percent', // percent of image or number of pixels
+ customDiffConfig: { threshold: 0.01 }, // threshold for each pixel
+ capture: 'fullPage', // When fullPage, the application under test is captured in its entirety from top to bottom.
+ };
- before(() => {
- cy.disableUILogin();
- });
-
- after(function () {
- cy.enableUILogin();
- });
+ const cssAttachedToNavigableCards = 'skills-navigable-item';
beforeEach(() => {
Cypress.env('disabledUILoginProp', true);
@@ -108,11 +110,11 @@ describe('Client Display Tests', () => {
});
cy.request('POST', `/admin/projects/proj1/skills/skill4/dependency/skill2`)
- cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: 'user0', timestamp: new Date().getTime()})
- cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: 'user0', timestamp: new Date().getTime() - 1000*60*60*24})
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.request('POST', `/api/projects/proj1/skills/skill1`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime() - 1000*60*60*24})
- cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: 'user0', timestamp: new Date().getTime()})
- cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: 'user0', timestamp: new Date().getTime() - 1000*60*60*24})
+ cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime()})
+ cy.request('POST', `/api/projects/proj1/skills/skill3`, {userId: Cypress.env('proxyUser'), timestamp: new Date().getTime() - 1000*60*60*24})
cy.request('POST', '/admin/projects/proj1/badges/badge1', {
projectId: 'proj1',
@@ -211,7 +213,7 @@ describe('Client Display Tests', () => {
});
cy.cdVisit('/?isSummaryOnly=true');
- cy.get('[data-cy=myRank]').contains("1")
+ // cy.get('[data-cy=myRank]').contains("1")
cy.get('[data-cy=myBadges]').contains("0 Badges")
// make sure click doesn't take us anywhere
@@ -229,5 +231,71 @@ describe('Client Display Tests', () => {
cy.get('[data-cy=subjectTile]').should('not.exist');
});
+ it('display achieved date on skill overview page', () => {
+ const m = moment('2020-09-12 11', 'YYYY-MM-DD HH');
+ const orig = m.clone()
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(4, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(3, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(2, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(1, 'day').format('x')})
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+ cy.cdClickSkill(1);
+
+ cy.get('[data-cy=achievementDate]').contains(`Achieved on ${orig.format("MMMM Do YYYY")}`);
+ cy.get('[data-cy=achievementDate]').contains(`${orig.fromNow()}`);
+
+ cy.matchImageSnapshot(`Skill-Overview-Achieved`, snapshotOptions);
+
+ cy.cdVisit('/?enableTheme=true');
+ cy.cdClickSubj(0);
+ cy.cdClickSkill(1);
+
+ cy.get('[data-cy=achievementDate]').contains(`Achieved on ${orig.format("MMMM Do YYYY")}`);
+ cy.get('[data-cy=achievementDate]').contains(`${orig.fromNow()}`);
+
+ cy.matchImageSnapshot(`Skill-Overview-Achieved-Themed`, snapshotOptions);
+
+ cy.setResolution('iphone-6');
+
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+ cy.cdClickSkill(1);
+
+ cy.get('[data-cy=achievementDate]').contains(`Achieved on ${orig.format("MMMM Do YYYY")}`);
+ cy.get('[data-cy=achievementDate]').contains(`${orig.fromNow()}`);
+
+ cy.matchImageSnapshot(`Skill-Overview-Achieved-iphone6`, snapshotOptions);
+
+ cy.setResolution('ipad-2');
+
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+ cy.cdClickSkill(1);
+
+ cy.get('[data-cy=achievementDate]').contains(`Achieved on ${orig.format("MMMM Do YYYY")}`);
+ cy.get('[data-cy=achievementDate]').contains(`${orig.fromNow()}`);
+
+ cy.matchImageSnapshot(`Skill-Overview-Achieved-ipad2`, snapshotOptions);
+
+ });
+
+ it('display achieved date on subject page when skill details are expanded', () => {
+ const m = moment('2020-09-12 11', 'YYYY-MM-DD HH');
+ const orig = m.clone()
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(4, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(3, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(2, 'day').format('x')})
+ cy.request('POST', `/api/projects/proj1/skills/skill2`, {userId: Cypress.env('proxyUser'), timestamp: m.subtract(1, 'day').format('x')})
+ cy.cdVisit('/');
+ cy.cdClickSubj(0);
+
+ cy.get('[data-cy=toggleSkillDetails]').click();
+ cy.get('[data-cy=skillProgress]:nth-child(2) [data-cy=achievementDate]').contains(`Achieved on ${orig.format("MMMM Do YYYY")}`);
+ cy.get('[data-cy=skillProgress]:nth-child(2) [data-cy=achievementDate]').contains(`${orig.fromNow()}`);
+ });
+
});
diff --git a/e2e-tests/cypress/integration/error_pages_spec.js b/e2e-tests/cypress/integration/error_pages_spec.js
new file mode 100644
index 00000000..1d0ece23
--- /dev/null
+++ b/e2e-tests/cypress/integration/error_pages_spec.js
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Error Pages Tests', () => {
+
+ beforeEach(() => {
+ cy.server();
+ });
+
+ it('Project Does Not Exist', () => {
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/fake'
+ }).as('loadProject');
+ cy.visit('/projects/fake');
+ cy.wait('@loadProject');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/fake/subjects/fake'
+ }).as('loadSubject');
+ cy.visit('/projects/fake/subjects/fake');
+ cy.wait('@loadSubject');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/fake/subjects/fake/skills/fake'
+ }).as('loadSkill');
+
+ cy.visit('/projects/fake/subjects/fake/skills/fake');
+ cy.wait('@loadSkill');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/fake/badges/fake'
+ }).as('loadBadge');
+
+ cy.visit('/projects/fake/badges/fake');
+ cy.wait('@loadBadge');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+ });
+
+ it( 'User Not Authorized For Project', () => {
+ cy.register('user1', 'password1', false);
+ cy.register('user2', 'password2', false);
+ cy.logout();
+ cy.login('user1', 'password1');
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5'
+ });
+
+ cy.request('POST', '/admin/projects/proj1/badges/badge1', {
+ enabled:false,
+ projectId:"proj1",
+ name:"Badge1",
+ badgeId:"badge1",
+ description:"",
+ iconClass:"fas fa-award"
+ });
+ cy.logout();
+ cy.login('user2', 'password2');
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1'
+ }).as('loadProject');
+ cy.visit('/projects/proj1');
+ cy.wait('@loadProject');
+
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/subjects/subj1'
+ }).as('loadSubject');
+ cy.visit('/projects/proj1/subjects/subj1');
+ cy.wait('@loadSubject');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/subjects/subj1/skills/skill1'
+ }).as('loadSkill');
+ cy.visit('/projects/proj1/subjects/subj1/skills/skill1');
+ cy.wait('@loadSkill');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/badges/badge1'
+ }).as('loadBadge');
+ cy.visit('/projects/proj1/badges/badge1');
+ cy.wait('@loadBadge');
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Project OR this Project does not exist');
+ });
+
+ it('Subject Not Found', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/subjects/fake'
+ }).as('loadSubject');
+
+ cy.visit('/projects/proj1/subjects/fake');
+ cy.wait('@loadSubject');
+
+ cy.get('[data-cy=notFoundExplanation]').should('be.visible');
+ cy.get('[data-cy=notFoundExplanation]').contains('Subject [fake] doesn\'t exist in project [proj1]');
+ })
+
+ it('Skill Not Found', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/subjects/subj1/skills/skill1'
+ }).as('loadSkill');
+ cy.visit('/projects/proj1/subjects/subj1/skills/skill1');
+ cy.wait('@loadSkill');
+
+ cy.get('[data-cy=notFoundExplanation]').should('be.visible');
+ cy.get('[data-cy=notFoundExplanation]').contains('Skill [skill1] doesn\'t exist.');
+ });
+
+ it('Badge Not Found', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.route({
+ method: 'GET',
+ url: '/admin/projects/proj1/badges/fake'
+ }).as('loadBadge');
+
+ cy.visit('/projects/proj1/badges/fake');
+ cy.wait('@loadBadge');
+
+ cy.get('[data-cy=notFoundExplanation]').should('be.visible');
+ cy.get('[data-cy=notFoundExplanation]').contains('Badge [fake] doesn\'t exist');
+ });
+
+ it('Global Badge Not Found', () => {
+ const supervisorUser = 'supervisor@skills.org';
+ cy.register(supervisorUser, 'password');
+ cy.login('root@skills.org', 'password');
+ cy.request('PUT', `/root/users/${supervisorUser}/roles/ROLE_SUPERVISOR`);
+ cy.logout();
+ cy.login(supervisorUser, 'password');
+
+ cy.route({
+ method: 'GET',
+ url: '/supervisor/badges/fake'
+ }).as('loadGlobalBadge');
+ cy.visit('/globalBadges/fake');
+ cy.wait('@loadGlobalBadge');
+
+ cy.get('[data-cy=notFoundExplanation]').should('be.visible');
+ cy.get('[data-cy=notFoundExplanation]').contains('GlobalBadge [fake] doesn\'t exist.');
+ });
+
+ it('Global Badge Not Authorized', () => {
+ const supervisorUser = 'supervisor@skills.org';
+ cy.register(supervisorUser, 'password');
+ cy.login('root@skills.org', 'password');
+ cy.request('PUT', `/root/users/${supervisorUser}/roles/ROLE_SUPERVISOR`);
+ cy.logout();
+ cy.login(supervisorUser, 'password');
+ cy.request('POST', '/supervisor/badges/globalBadge1', {
+ "enabled":false,
+ "originalBadgeId":"",
+ "name":"globalBadge1",
+ "badgeId":"globalBadge1",
+ "description":"",
+ "iconClass":"fas fa-award"
+ });
+ cy.register('user1', 'password1', false);
+ cy.logout();
+ cy.login('user1', 'password1');
+
+ cy.route('GET', '/supervisor/badges/globalBadge1').as('loadGlobalBadge');
+ cy.visit('/globalBadges/globalBadge1');
+ cy.wait('@loadGlobalBadge');
+
+ cy.get('[data-cy=notAuthorizedExplanation]').should('be.visible');
+ cy.get('[data-cy=notAuthorizedExplanation]').contains('You do not have permission to view/manage this Global Badge OR this Global Badge does not exist');
+ });
+
+
+});
diff --git a/e2e-tests/cypress/integration/global_badges_spec.js b/e2e-tests/cypress/integration/global_badges_spec.js
index f2301a7c..3409f221 100644
--- a/e2e-tests/cypress/integration/global_badges_spec.js
+++ b/e2e-tests/cypress/integration/global_badges_spec.js
@@ -16,6 +16,7 @@
describe('Global Badges Tests', () => {
beforeEach(() => {
+ cy.server();
cy.logout();
const supervisorUser = 'supervisor@skills.org';
cy.register(supervisorUser, 'password');
@@ -27,12 +28,14 @@ describe('Global Badges Tests', () => {
it('Create badge with special chars', () => {
- const expectedId = 'JustABadgeBadge';
- const providedName = "JustABadge";
+ const expectedId = 'LotsofspecialPcharsBadge';
+ const providedName = "!L@o#t$s of %s^p&e*c/?#(i)a_l++_|}{P c'ha'rs";
cy.server();
cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
- cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole')
cy.route('PUT', `/supervisor/badges/${expectedId}`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/${expectedId}/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole')
cy.visit('/globalBadges');
cy.wait('@getGlobalBadges');
@@ -41,13 +44,40 @@ describe('Global Badges Tests', () => {
cy.clickButton('Badge');
cy.get('#badgeName').type(providedName);
-
+ cy.wait('@nameExists');
cy.clickSave();
+ cy.wait('@idExists');
cy.wait('@postGlobalBadge');
cy.contains(`ID: ${expectedId}`);
});
+ it('name causes id to fail validation', () => {
+ cy.server();
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole');
+
+ cy.visit('/globalBadges');
+ cy.wait('@getGlobalBadges');
+ cy.wait('@checkSupervisorRole');
+
+ cy.clickButton('Badge');
+
+ // name causes id to be too long
+ const msg = 'Badge ID cannot exceed 50 characters';
+ const validNameButInvalidId = Array(46).fill('a').join('');
+ cy.get('[data-cy=badgeName]').click();
+ cy.get('[data-cy=badgeName]').invoke('val', validNameButInvalidId).trigger('input');
+ cy.get('[data-cy=idError]').contains(msg).should('be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.disabled');
+ cy.get('[data-cy=badgeName]').type('{backspace}');
+ cy.get('[data-cy=idError]').contains(msg).should('not.be.visible');
+ cy.get('[data-cy=saveBadgeButton]').should('be.enabled');
+ });
+
+
+
it('Delete badge', () => {
const expectedId = 'JustABadgeBadge';
const providedName = "JustABadge";
@@ -133,12 +163,13 @@ describe('Global Badges Tests', () => {
originalBadgeId: ''
});
- cy.contains('Badges').click();
+ cy.visit('/');
+ cy.clickNav('Badges');
cy.contains('Manage').click();
cy.get('.multiselect__tags').click();
cy.get('.multiselect__tags input').type('{enter}');
cy.get('div.table-responsive').should('be.visible');
- cy.get('li').contains('Levels').click();
+ cy.clickNav('Levels');
cy.get('.multiselect__tags').first().click();
cy.get('.multiselect__tags input').first().type('proj2{enter}');
@@ -154,28 +185,355 @@ describe('Global Badges Tests', () => {
cy.server();
cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
- cy.contains('Badges').click();
+ cy.visit('/');
+ cy.clickNav('Badges');
+ cy.wait('@getGlobalBadges');
+ });
+
+ it('Cannot publish Global Badge with no Skills and no Levels', () => {
+ const expectedId = 'TestBadgeBadge';
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('PUT', `/supervisor/badges/${expectedId}`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/${expectedId}/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole');
+ cy.route('GET', `/supervisor/badges/${expectedId}`).as('getExpectedBadge');
+
+ cy.visit('/globalBadges');
cy.wait('@getGlobalBadges');
+ cy.wait('@checkSupervisorRole');
+
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('Test Badge');
+ cy.wait('@nameExists');
+ cy.clickSave();
+ cy.wait('@postGlobalBadge');
+
+ cy.clickNav('Badges');
+
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('This Global Badge has no assigned Skills or Project Levels. A Global Badge cannot be published without at least one Skill or Project Level.').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
});
+ it('Global Badge is disabled when created, can only be enabled once', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ const expectedId = 'TestBadgeBadge';
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('PUT', `/supervisor/badges/${expectedId}`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/${expectedId}/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole');
+ cy.route('GET', `/supervisor/badges/${expectedId}`).as('getExpectedBadge');
+
+ cy.visit('/globalBadges');
+ cy.wait('@getGlobalBadges');
+ cy.wait('@checkSupervisorRole');
+
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('Test Badge');
+ cy.wait('@nameExists');
+ cy.clickSave();
+ cy.wait('@postGlobalBadge');
+
+ cy.clickNav('Badges');
+ cy.contains('Manage').click();
+ cy.get('.multiselect__tags').click();
+ cy.get('.multiselect__tags input').type('{enter}');
+ cy.get('div.table-responsive').should('be.visible');
+ cy.contains('GlobalBadges').click();
+
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+ cy.wait('@postGlobalBadge');
+ cy.wait('@getExpectedBadge');
+ cy.wait('@getGlobalBadges');
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=goLive]').should('not.exist');
+ });
+
+ it('Canceling go live dialog should leave global badge disabled', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ const expectedId = 'TestBadgeBadge';
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('PUT', `/supervisor/badges/${expectedId}`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/${expectedId}/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('checkSupervisorRole');
+
+ cy.visit('/globalBadges');
+ cy.wait('@getGlobalBadges');
+ cy.wait('@checkSupervisorRole');
+
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('Test Badge');
+ cy.wait('@nameExists');
+ cy.clickSave();
+ cy.wait('@postGlobalBadge');
+
+ cy.contains('Test Badge').should('exist');
+ cy.contains('Manage').click();
+ cy.get('.multiselect__tags').click();
+ cy.get('.multiselect__tags input').type('{enter}');
+ cy.get('div.table-responsive').should('be.visible');
+
+ cy.contains('GlobalBadges').click();
+ cy.wait('@getGlobalBadges');
+
+ cy.contains('Test Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Cancel').click();
+ cy.contains('Test Badge');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('not.exist');
+ cy.get('[data-cy=goLive]').should('exist');
+ });
+
+ it('Can add Skill and Level requirements to disabled Global Badge', () => {
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('PUT', `/supervisor/badges/ABadgeBadge`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/ABadgeBadge/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ //proj/subj/skill1
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+ //proj/subj/skill2
+ cy.request('POST', '/app/projects/proj2', {
+ projectId: 'proj2',
+ name: "proj2"
+ });
+ cy.request('POST', '/admin/projects/proj2/subjects/subj1', {
+ projectId: 'proj2',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj2/subjects/subj1/skills/skill1`, {
+ projectId: 'proj2',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ cy.visit('/');
+
+ cy.clickNav('Badges');
+ cy.wait('@getGlobalBadges');
+
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('A Badge');
+ cy.wait('@nameExists');
+ cy.clickSave();
+ cy.wait('@idExists');
+ cy.wait('@postGlobalBadge');
+
+ cy.contains('A Badge').should('exist');
+ cy.contains('Manage').click();
+ cy.get('.multiselect__tags').click();
+ cy.get('.multiselect__tags input').type('{enter}');
+ cy.get('div.table-responsive').should('be.visible');
+ cy.clickNav('Levels');
+
+ cy.get('.multiselect__tags').first().click();
+ cy.get('.multiselect__tags input').first().type('proj2{enter}');
+
+ cy.get('.multiselect__tags').last().click();
+ cy.get('.multiselect__tags input').last().type('5{enter}');
+
+ cy.contains('Add').click();
+ cy.get('#simple-levels-table').should('be.visible');
+
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.wait('@getGlobalBadges');
+
+ cy.contains('A Badge').should('exist');
+
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+ cy.wait('@getGlobalBadges');
+ cy.contains('A Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=goLive]').should('not.exist');
+ });
+
+ it('Removing all skills should not cause published Global Badge to become disabled', () => {
+ cy.route('GET', `/supervisor/badges`).as('getGlobalBadges');
+ cy.route('PUT', `/supervisor/badges/ABadgeBadge`).as('postGlobalBadge');
+ cy.route('GET', `/supervisor/badges/id/ABadgeBadge/exists`).as('idExists');
+ cy.route('POST', '/supervisor/badges/name/exists').as('nameExists');
+ //proj/subj/skill1
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ });
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj1/subjects/subj1/skills/skill1`, {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+ //proj/subj/skill2
+ cy.request('POST', '/app/projects/proj2', {
+ projectId: 'proj2',
+ name: "proj2"
+ });
+ cy.request('POST', '/admin/projects/proj2/subjects/subj1', {
+ projectId: 'proj2',
+ subjectId: 'subj1',
+ name: "Subject 1"
+ });
+ cy.request('POST', `/admin/projects/proj2/subjects/subj1/skills/skill1`, {
+ projectId: 'proj2',
+ subjectId: 'subj1',
+ skillId: 'skill1',
+ name: `This is 1`,
+ type: 'Skill',
+ pointIncrement: 100,
+ numPerformToCompletion: 5,
+ pointIncrementInterval: 0,
+ numMaxOccurrencesIncrementInterval: -1,
+ version: 0,
+ });
+
+ cy.visit('/');
+
+ cy.clickNav('Badges');
+ cy.wait('@getGlobalBadges');
+
+ cy.clickButton('Badge');
+
+ cy.get('#badgeName').type('A Badge');
+ cy.wait('@nameExists');
+ cy.clickSave();
+ cy.wait('@idExists');
+ cy.wait('@postGlobalBadge');
+
+ cy.contains('A Badge').should('exist');
+ cy.contains('Manage').click();
+ cy.get('.multiselect__tags').click();
+ cy.get('.multiselect__tags input').type('{enter}');
+ cy.get('div.table-responsive').should('be.visible');
+ cy.clickNav('Levels');
+
+ cy.get('.multiselect__tags').first().click();
+ cy.get('.multiselect__tags input').first().type('proj2{enter}');
+
+ cy.get('.multiselect__tags').last().click();
+ cy.get('.multiselect__tags input').last().type('5{enter}');
+
+ cy.contains('Add').click();
+ cy.get('#simple-levels-table').should('be.visible');
+
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.wait('@getGlobalBadges');
+
+ cy.contains('A Badge').should('exist');
+
+ cy.get('[data-cy=badgeStatus]').contains('Status: Disabled').should('exist');
+ cy.get('[data-cy=goLive]').click();
+ cy.contains('Please Confirm!').should('exist');
+ cy.contains('Yes, Go Live!').click();
+ cy.wait('@getGlobalBadges');
+ cy.contains('A Badge').should('exist');
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=goLive]').should('not.exist');
+
+ cy.contains('Manage').click();
+ cy.get('[data-cy=deleteSkill]').click();
+ cy.contains('YES, Delete It!').click();
+ cy.contains('.router-link-active', 'Badges').click();
+ cy.get('[data-cy=badgeStatus]').contains('Status: Live').should('exist');
+ cy.get('[data-cy=goLive]').should('not.exist');
+ });
- // THIS DOES NOT PASS: will be handled bia #449
- // it('create badge with special chars', () => {
- // const expectedId = 'LotsofspecialPcharsBadge';
- // const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}{P c'ha'rs";
- // cy.server().route('GET', `/supervisor/badges`).as('getGlobalBadges');
- //
- // cy.visit('/globalBadges');
- // cy.wait('@getGlobalBadges')
- // cy.clickButton('Badge')
- //
- // cy.get('#badgeName').type(providedName)
- // cy.getIdField().should('have.value', expectedId)
- //
- // // cy.clickSave()
- // // cy.wait('@postNewBadge');
- // //
- // // cy.contains('ID: Lotsofspecial')
- // });
-
-})
+});
diff --git a/e2e-tests/cypress/integration/login_spec.js b/e2e-tests/cypress/integration/login_spec.js
index fd863efd..46093519 100644
--- a/e2e-tests/cypress/integration/login_spec.js
+++ b/e2e-tests/cypress/integration/login_spec.js
@@ -22,6 +22,7 @@ describe('Login Tests', () => {
.route('GET', '/app/projects').as('getProjects')
.route('GET', '/api/icons/customIconCss').as('getProjectsCustomIcons')
.route('GET', '/app/userInfo').as('getUserInfo')
+ .route('GET', '/app/oAuthProviders').as('getOAuthProviders')
.route('POST', '/performLogin').as('postPerformLogin');
});
@@ -32,12 +33,11 @@ describe('Login Tests', () => {
cy.get('#inputPassword').type('password');
cy.contains('Login').click();
- cy.wait('@getProjects').its('status').should('be', 200)
- .wait('@getUserInfo').its('status').should('be', 200);
+ cy.wait('@getProjects').its('status').should('equal', 200)
+ .wait('@getUserInfo').its('status').should('equal', 200);
cy.contains('Project');
- cy.contains('My Projects');
- cy.contains('Inception');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
});
it('form: bad password', () => {
@@ -102,7 +102,7 @@ describe('Login Tests', () => {
cy.visit('/');
cy.contains('Login').should('be.disabled');
- const expectedText = 'Email cannot be less than 5 characters.';
+ const expectedText = 'Email Address cannot be less than 5 characters.';
cy.get('#username').type('v@s');
cy.get('#inputPassword').type('12345678');
@@ -115,33 +115,11 @@ describe('Login Tests', () => {
cy.contains(expectedText).should('not.exist');
})
- it('disabled login - email must not exceed 73 chars', () => {
- cy.visit('/');
- cy.contains('Login').should('be.disabled');
-
- // valid email must be less than 73 chars
- const invalidEmail = Array(74-9).fill('a').join('');
- const validEmail = Array(73-9).fill('a').join('');
-
- // will be taken care of by email validator
- const expectedText = 'The Email field must be a valid email';
-
- cy.get('#username').type(`${invalidEmail}@mail.org`);
- cy.get('#inputPassword').type('12345678');
- cy.contains('Login').should('be.disabled');
- cy.contains(expectedText);
-
- cy.get('#username').clear();
- cy.get('#username').type(`${validEmail}@mail.org`);
- cy.contains('Login').should('be.enabled');
- cy.contains(expectedText).should('not.exist');
- })
-
it('disabled login - valid email format', () => {
cy.visit('/');
cy.contains('Login').should('be.disabled');
- const expectedText = 'The Email field must be a valid email';
+ const expectedText = 'Email Address must be a valid email';
cy.get('#username').type('notvalid');
cy.get('#inputPassword').type('12345678');
@@ -158,4 +136,14 @@ describe('Login Tests', () => {
cy.contains('Login').should('be.enabled');
cy.contains(expectedText).should('not.exist');
})
+
+ if (!Cypress.env('oauthMode')) {
+ it('OAuth login is not enabled', () => {
+ cy.visit('/');
+ cy.contains('Login').should('be.disabled');
+
+ cy.wait('@getOAuthProviders').its('status').should('equal', 200)
+ cy.get('[data-cy=oAuthProviders]').should('not.exist');
+ })
+ }
});
diff --git a/e2e-tests/cypress/integration/markdown_spec.js b/e2e-tests/cypress/integration/markdown_spec.js
new file mode 100644
index 00000000..bf354b90
--- /dev/null
+++ b/e2e-tests/cypress/integration/markdown_spec.js
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Markdown Tests', () => {
+
+ const snapshotOptions = {
+ blackout: ['[data-cy=skillTableCellCreatedDate]'],
+ failureThreshold: 0.03, // threshold for entire image
+ failureThresholdType: 'percent', // percent of image or number of pixels
+ customDiffConfig: { threshold: 0.01 }, // threshold for each pixel
+ capture: 'fullPage', // When fullPage, the application under test is captured in its entirety from top to bottom.
+ };
+
+ beforeEach(() => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "proj1"
+ })
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1',
+ subjectId: 'subj1',
+ name: "Subject 1",
+ })
+ });
+
+ it('markdown features', () => {
+ cy.visit('/projects/proj1/');
+
+ const markdownInput = '[data-cy=markdownEditorInput]';
+ cy.get('[data-cy=cardSettingsButton]').click();
+ cy.contains('Edit').click();
+
+ const validateMarkdown = (markdown, snapshotName, expectedText = null, clickWrite = true) => {
+ if (clickWrite) {
+ cy.contains('Write').click();
+ }
+ cy.get(markdownInput).clear().type(markdown);
+ cy.contains('Preview').click();
+ // move focus away from Preview
+ cy.contains('Description').click();
+ if (expectedText) {
+ cy.contains(expectedText);
+ }
+ cy.matchImageSnapshot(snapshotName);
+ }
+ validateMarkdown('# Title1\n## Title2\n### Title 3\n#### Title 4\n##### Title 5\nTitle 6\n\n', 'Markdown-Titles', null,false);
+
+ const emphasisMarkdown = "italics: *italicized* or _italicized_\n\n" +
+ "bold: **bolded** or __bolded__\n\n" +
+ "combination **_bolded & italicized_**\n\n" +
+ "strikethrough: ~~struck~~\n\n";
+ validateMarkdown(emphasisMarkdown, 'Markdown-Emphasis');
+
+ validateMarkdown("Inline `code` has `back-ticks around` it\n\n", 'Markdown-Inline')
+
+ const multiLineCode = "Some text followed by code\n" +
+ "```\n" +
+ "const validateMarkdown = (markdown, snapshotName) => {\n" +
+ "}\n" +
+ "```";
+ validateMarkdown(multiLineCode, 'Markdown-MultiLineCode')
+
+ validateMarkdown('Some text:\n1. Item one\n1. Item two\n1. Item three (actual number does not matter)', 'Markdown-NumberedList')
+
+ validateMarkdown('List:\n* Item\n* Item\n* Item\n', 'Markdown-UnorderedList')
+
+ validateMarkdown('[in line link](https://www.somewebsite.com)', 'Markdown-Link')
+
+ const blockQuote = "# Blockquote:\n" +
+ "> Blockquotes are very handy to emulate reply text.\n" +
+ "> This line is part of the same quote.\n\n";
+ validateMarkdown(blockQuote, 'Markdown-blockquote');
+
+ validateMarkdown('Separate me\n\n___\n\nSeparate me\n\n---\n\nSeparate me\n\n***', 'Markdown-Separator')
+
+ validateMarkdown(':star: :star: :star: :star:', 'Markdown-emoji', '⭐ ⭐ ⭐ ⭐')
+ });
+
+ it('on skills pages', () => {
+ const markdown = "# Title1\n## Title2\n### Title 3\n#### Title 4\n##### Title 5\nTitle 6\n\n" +
+ "---\n" +
+ "# Emphasis\n" +
+ "italics: *italicized* or _italicized_\n\n" +
+ "bold: **bolded** or __bolded__\n\n" +
+ "combination **_bolded & italicized_**\n\n" +
+ "strikethrough: ~~struck~~\n\n" +
+ "---\n" +
+ "# Inline\n" +
+ "Inline `code` has `back-ticks around` it\n\n" +
+ "---\n" +
+ "# Multiline\n" +
+ "\n" +
+ "\n" +
+ "```\n" +
+ "import { SkillsDirective } from '@skilltree/skills-client-vue';\n" +
+ "Vue.use(SkillsDirective);\n" +
+ "```\n" +
+ "# Lists\n" +
+ "Ordered Lists:\n" +
+ "1. Item one\n" +
+ "1. Item two\n" +
+ "1. Item three (actual number does not matter)\n\n" +
+ "If List item has multiple lines of text, subsequent lines must be idented four spaces, otherwise list item numbers will reset, e.g.,\n" +
+ "1. item one\n" +
+ " paragrah one\n" +
+ "1. item two\n" +
+ "1. item three\n" +
+ "\n" +
+ "Unordered Lists\n" +
+ "* Item\n" +
+ "* Item\n" +
+ "* Item\n" +
+ "___\n" +
+ "# Links\n" +
+ "[in line link](https://www.somewebsite.com)\n" +
+ "___\n" +
+ "# Blockquotes\n" +
+ "> Blockquotes are very handy to emulate reply text.\n" +
+ "> This line is part of the same quote.\n\n" +
+ "# Horizontal rule\n" +
+ "Use three or more dashes, asterisks, or underscores to generate a horizontal rule line\n" +
+ "\n" +
+ "Separate me\n\n" +
+ "___\n\n" +
+ "Separate me\n\n" +
+ "---\n\n" +
+ "Separate me\n\n" +
+ "***\n\n" +
+ "# Emojis\n" +
+ ":star: :star: :star: :star:\n" +
+ "";
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1/skills/skill1', {
+ projectId: 'proj1',
+ subjectId: "subj1",
+ skillId: "skill1",
+ name: "Skill 1",
+ pointIncrement: '50',
+ numPerformToCompletion: '5',
+ description: markdown
+ });
+ cy.visit('/projects/proj1/subjects/subj1/skills/skill1');
+
+ cy.contains('Description');
+ cy.contains('Level 0');
+ cy.contains('Emojis')
+ cy.contains('⭐ ⭐ ⭐ ⭐');
+ cy.matchImageSnapshot('Markdown-SkillsPage-Overview', snapshotOptions);
+
+ cy.visit('/projects/proj1/subjects/subj1');
+ cy.contains('Level 0');
+ const selectorSkillsRowToggle = 'table .VueTables__child-row-toggler';
+ cy.get(selectorSkillsRowToggle).click();
+ cy.contains('Description');
+ cy.contains('Emojis')
+ cy.contains('⭐ ⭐ ⭐ ⭐');
+ cy.matchImageSnapshot('Markdown-SubjectPage-SkillPreview', snapshotOptions);
+ });
+
+})
diff --git a/e2e-tests/cypress/integration/metrics_spec.js b/e2e-tests/cypress/integration/metrics_spec.js
new file mode 100644
index 00000000..fe5033ee
--- /dev/null
+++ b/e2e-tests/cypress/integration/metrics_spec.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Metrics Specs', () => {
+ beforeEach(() => {
+ cy.server()
+ .route('GET', '/metrics/global').as('getMetrics')
+ .route('GET', '/app/userInfo').as('getUserInfo')
+ });
+
+ it('global metrics page loads', function () {
+ cy.visit('/metrics');
+ cy.contains('No Metrics Yet').should('be.visible');
+ });
+
+});
diff --git a/e2e-tests/cypress/integration/navigation_spec.js b/e2e-tests/cypress/integration/navigation_spec.js
new file mode 100644
index 00000000..193abf57
--- /dev/null
+++ b/e2e-tests/cypress/integration/navigation_spec.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Navigation Tests', () => {
+ beforeEach(() => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "My New test Project"
+ })
+ cy.request('POST', '/admin/projects/proj1/subjects/subj1', {
+ projectId: 'proj1', subjectId: 'subj1', name: 'Subject 1',
+ });
+ });
+
+ it('ability to expand and collapse navigation', function () {
+ cy.visit('/projects/proj1');
+ cy.contains('ID: subj1');
+
+ // validate nav i expanded by default
+ cy.get('[data-cy=nav-Subjects]').contains('Subjects');
+ cy.get('[data-cy=nav-Badges]').contains('Badges');
+
+ // collapse
+ cy.get('[data-cy=navCollapseOrExpand]').click();
+ cy.get('[data-cy=nav-Subjects]').contains('Projects').should('not.exist');
+ cy.get('[data-cy=nav-Badges]').contains('Metrics').should('not.exist');
+
+ // refresh and validate that nav is still collapsed
+ cy.visit('/projects/proj1');
+ cy.contains('ID: subj1');
+ cy.get('[data-cy=nav-Subjects]').contains('Subjects').should('not.exist');
+ cy.get('[data-cy=nav-Badges]').contains('Badges').should('not.exist');
+
+ // navigate through the collapsed nav
+ cy.get('[data-cy=nav-Badges]').click();
+ cy.contains('No Badges Yet');
+ cy.get('[data-cy=nav-Subjects]').click();
+ cy.contains('ID: subj1');
+ cy.get('[data-cy=nav-Subjects]').contains('Subjects').should('not.exist');
+ cy.get('[data-cy=nav-Badges]').contains('Badges').should('not.exist');
+
+ // drill down to subject and make sure nav is still collapsed
+ cy.clickManageSubject('subj1');
+ cy.contains('No Skills Yet');
+ cy.get('[data-cy=nav-Skills]').contains('Skills').should('not.exist');
+ cy.get('[data-cy=nav-Levels]').contains('Levels').should('not.exist');
+
+ // refresh and make sure that nav is still collapsed
+ cy.visit('/projects/proj1/subjects/subj1');
+ cy.contains('No Skills Yet');
+ cy.get('[data-cy=nav-Skills]').contains('Skills').should('not.exist');
+ cy.get('[data-cy=nav-Levels]').contains('Levels').should('not.exist');
+
+ // expand nav
+ cy.get('[data-cy=navCollapseOrExpand]').click();
+ cy.get('[data-cy=nav-Skills]').contains('Skills');
+ cy.get('[data-cy=nav-Levels]').contains('Levels');
+
+ // navigate back to the home page and make sure nav is expanded
+ cy.get('[data-cy=breadcrumb-proj1]').click();
+ cy.contains('ID: subj1');
+ cy.get('[data-cy=nav-Subjects]').contains('Subjects');
+ cy.get('[data-cy=nav-Badges]').contains('Badges');
+ });
+
+ it('selected menu item should be highlighted', function () {
+ cy.visit('/projects/proj1');
+ cy.contains('ID: subj1');
+ cy.get('[data-cy=nav-Subjects]').should('have.class', 'bg-primary');
+ cy.get('[data-cy=nav-Badges]').should('not.have.class', 'bg-primary');
+
+ cy.get('[data-cy=nav-Badges]').click();
+ cy.contains('No Badges Yet');
+ cy.get('[data-cy=nav-Subjects]').should('not.have.class', 'bg-primary');
+ cy.get('[data-cy=nav-Badges]').should('have.class', 'bg-primary');
+ });
+
+ it('navigation on a small screen', function () {
+ cy.viewport('iphone-6')
+ cy.visit('/projects/proj1');
+ cy.contains('ID: subj1');
+ cy.get('[data-cy=navCollapseOrExpand]').should('not.exist');
+ cy.get('[data-cy=nav-Subjects]').should('not.visible');
+ cy.get('[data-cy=nav-Badges]').should('not.visible');
+
+ // expand menu
+ cy.get('[data-cy=navSmallScreenExpandMenu]').click()
+ cy.get('[data-cy=nav-Subjects]').contains('Subjects');
+ cy.get('[data-cy=nav-Badges]').contains('Badges');
+
+ // navigate and make sure menu is collapsed again
+ cy.get('[data-cy=nav-Badges]').click();
+ cy.contains('No Badges Yet');
+ cy.get('[data-cy=nav-Subjects]').should('not.visible');
+ cy.get('[data-cy=nav-Badges]').should('not.visible');
+ });
+
+});
+
diff --git a/e2e-tests/cypress/integration/password_reset_spec.js b/e2e-tests/cypress/integration/password_reset_spec.js
new file mode 100644
index 00000000..ccc90a93
--- /dev/null
+++ b/e2e-tests/cypress/integration/password_reset_spec.js
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Password Reset Tests', () => {
+
+ beforeEach(() => {
+ cy.logout();
+ cy.resetEmail();
+
+ cy.fixture('vars.json').then((vars) => {
+ cy.register(vars.rootUser, vars.defaultPass, true);
+ });
+
+ cy.login('root@skills.org', 'password');
+
+ cy.request({
+ method: 'POST',
+ url: '/root/saveEmailSettings',
+ body: {
+ host: 'localhost',
+ port: 1026,
+ 'protocol': 'smtp'
+ },
+ });
+
+ cy.request({
+ method: 'POST',
+ url: '/root/saveSystemSettings',
+ body: {
+ publicUrl: 'http://localhost:8082/',
+ resetTokenExpiration: 'PT2H'
+ }
+ });
+
+ cy.logout();
+
+ cy.server();
+ cy.route({
+ method: 'POST',
+ url: '/performPasswordReset'
+ }).as('performReset');
+ cy.route('GET', '/app/projects').as('getProjects')
+ cy.route('GET', '/app/userInfo').as('getUserInfo')
+ });
+
+ it('reset password', () => {
+ cy.register("test@skills.org", "apassword", false);
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.get('[data-cy=forgotPasswordEmail]').should('exist');
+ cy.get('[data-cy=forgotPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPassword').click();
+ cy.get('[data-cy=resetRequestConfirmation').should('exist');
+ cy.wait(11*1000); //request rest page redirects to login after 30 seconds
+ cy.get('[data-cy=login]').should('exist');
+ cy.getResetLink().then((resetLink) => {
+ cy.visit(resetLink);
+ cy.get('[data-cy=resetPasswordSubmit]').should('exist');
+ cy.get('[data-cy=resetPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPasswordNewPassword]').type('password2')
+ cy.get('[data-cy=resetPasswordConfirm]').type('password2');
+ cy.get('[data-cy=resetPasswordSubmit]').click();
+
+ cy.wait('@performReset');
+ cy.get('[data-cy=resetConfirmation]').should('exist');
+ cy.wait(11*1000) //will redirect to login page after 30 seconds
+ cy.get('[data-cy=login]').should('exist');
+ cy.get('#username').type('test@skills.org');
+ cy.get('#inputPassword').type('password2');
+ cy.get('[data-cy=login]').click();
+ cy.wait('@getProjects');
+ cy.wait('@getUserInfo');
+
+ cy.contains('Project');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
+ });
+
+ });
+
+ it('reset password - wrong user', () => {
+ cy.register("test@skills.org", "apassword", false);
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.get('[data-cy=forgotPasswordEmail]').should('exist');
+ cy.get('[data-cy=forgotPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPassword').click();
+ cy.get('[data-cy=resetRequestConfirmation').should('exist');
+ cy.wait(11*1000); //request rest page redirects to login after 30 seconds
+ cy.get('[data-cy=login]').should('exist');
+ cy.getResetLink().then((resetLink) => {
+ cy.visit(resetLink);
+ cy.get('[data-cy=resetPasswordSubmit]').should('exist');
+ cy.get('[data-cy=resetPasswordEmail]').type('test2@skills.org');
+ cy.get('[data-cy=resetPasswordNewPassword]').type('password2')
+ cy.get('[data-cy=resetPasswordConfirm]').type('password2');
+ cy.get('[data-cy=resetPasswordSubmit]').click();
+
+ cy.wait('@performReset');
+ cy.get('[data-cy=resetError]').should('be.visible');
+ cy.get('[data-cy=resetPasswordSubmit]').should('be.disabled');
+ });
+ });
+
+ it('reset password - password confirmation mismatch', () => {
+ cy.register("test@skills.org", "apassword", false);
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.get('[data-cy=forgotPasswordEmail]').should('exist');
+ cy.get('[data-cy=forgotPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPassword').click();
+ cy.get('[data-cy=resetRequestConfirmation').should('exist');
+ cy.wait(11*1000); //request rest page redirects to login after 30 seconds
+ cy.get('[data-cy=login]').should('exist');
+ cy.getResetLink().then((resetLink) => {
+ cy.visit(resetLink);
+ cy.get('[data-cy=resetPasswordSubmit]').should('exist');
+ cy.get('[data-cy=resetPasswordEmail]').type('test2@skills.org');
+ cy.get('[data-cy=resetPasswordNewPassword]').type('password2')
+ cy.get('[data-cy=resetPasswordConfirm]').type('password');
+ cy.get('[data-cy=resetPasswordSubmit]').should('be.disabled');
+ });
+ });
+
+ it('reset password - user does not exist', () => {
+ cy.register("test@skills.org", "apassword", false);
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.get('[data-cy=forgotPasswordEmail]').should('exist');
+ cy.get('[data-cy=forgotPasswordEmail]').type('fake@skills.org');
+ cy.get('[data-cy=resetPassword').click();
+ cy.get('[data-cy=resetFailedError]').should('be.visible');
+ });
+
+ it('cannot use reset link twice', () => {
+ cy.register("test@skills.org", "apassword", false);
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.get('[data-cy=forgotPasswordEmail]').should('exist');
+ cy.get('[data-cy=forgotPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPassword').click();
+ cy.get('[data-cy=resetRequestConfirmation').should('exist');
+ cy.get('[data-cy=loginPage]').click();
+ cy.get('[data-cy=login]').should('exist');
+ cy.getResetLink().then((resetLink) => {
+ cy.visit(resetLink);
+ cy.get('[data-cy=resetPasswordSubmit]').should('exist');
+ cy.get('[data-cy=resetPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPasswordNewPassword]').type('password2')
+ cy.get('[data-cy=resetPasswordConfirm]').type('password2');
+ cy.get('[data-cy=resetPasswordSubmit]').click();
+
+ cy.wait('@performReset');
+ cy.get('[data-cy=resetConfirmation]').should('exist');
+ cy.get('[data-cy=loginPage]').click();
+ cy.get('[data-cy=login]').should('exist');
+
+ cy.visit(resetLink);
+ cy.get('[data-cy=resetPasswordSubmit]').should('exist');
+ cy.get('[data-cy=resetPasswordEmail]').type('test@skills.org');
+ cy.get('[data-cy=resetPasswordNewPassword]').type('password3')
+ cy.get('[data-cy=resetPasswordConfirm]').type('password3');
+ cy.get('[data-cy=resetPasswordSubmit]').click();
+
+ cy.wait('@performReset');
+ cy.get('[data-cy=resetError]').should('be.visible');
+ cy.get('[data-cy=resetPasswordSubmit]').should('be.disabled');
+ });
+ });
+
+ it('reset not enabled if required configurations not set', ()=>{
+ cy.login('root@skills.org', 'password');
+
+ cy.request({
+ method: 'POST',
+ url: '/root/saveSystemSettings',
+ body: {
+ publicUrl: '',
+ }
+ });
+ cy.logout();
+ cy.route('GET', '/public/isFeatureSupported?feature=passwordreset').as('isEnabled');
+ cy.visit('/');
+ cy.get('[data-cy=forgotPassword]').click();
+ cy.wait('@isEnabled');
+ cy.get('[data-cy=resetNotSupported]').should('be.visible');
+ cy.get('[data-cy=forgotPasswordEmail').should('have.length.lte', 0);
+ });
+});
diff --git a/e2e-tests/cypress/integration/projects_spec.js b/e2e-tests/cypress/integration/projects_spec.js
index 3a5156ba..dd7d181c 100644
--- a/e2e-tests/cypress/integration/projects_spec.js
+++ b/e2e-tests/cypress/integration/projects_spec.js
@@ -19,15 +19,21 @@ describe('Projects Tests', () => {
.route('GET', '/app/projects').as('getProjects')
.route('GET', '/api/icons/customIconCss').as('getProjectsCustomIcons')
.route('GET', '/app/userInfo').as('getUserInfo')
+ .route('/admin/projects/proj1/users/root@skills.org/roles').as('getRolesForRoot');
});
it('Create new projects', function () {
- cy.visit('/');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
cy.route('POST', '/app/projects/MyNewtestProject').as('postNewProject');
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
+
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type("My New test Project")
+ cy.get('[data-cy="projectName"]').type("My New test Project")
cy.clickSave();
cy.wait('@postNewProject');
@@ -36,19 +42,58 @@ describe('Projects Tests', () => {
cy.contains('ID: MyNewtestProject')
});
+
+ it('Create new project using enter key', function () {
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
+ cy.route('POST', '/app/projects/MyNewtestProject').as('postNewProject');
+
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
+
+ cy.clickButton('Project');
+ cy.get('[data-cy="projectName"]').type("My New test Project")
+ cy.get('[data-cy="projectName"]').type('{enter}')
+
+ cy.wait('@postNewProject');
+
+ cy.contains('My New test Project')
+ cy.contains('ID: MyNewtestProject')
+ });
+
+ it('Close new project dialog', () => {
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
+ cy.route('POST', '/app/projects/MyNewtestProject').as('postNewProject');
+
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
+
+ cy.clickButton('Project');
+ cy.get('[data-cy=closeProjectButton]').click();
+ cy.get('[data-cy="projectName"]').should('not.be.visible');
+ });
+
it('Duplicate project names are not allowed', () => {
cy.request('POST', '/app/projects/MyNewtestProject', {
projectId: 'MyNewtestProject',
name: "My New test Project"
})
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type("My New test Project")
-
- cy.contains('The value for the Project Name is already taken')
- cy.clickSave();
- cy.contains('***Form did NOT pass validation, please fix and try to Save again***')
+ cy.get('[data-cy="projectName"]').type("My New test Project")
+ cy.get('[data-cy=projectNameError]').contains('The value for the Project Name is already taken').should('be.visible')
+ cy.get('[data-cy=saveProjectButton]').should('be.disabled');
});
@@ -57,26 +102,36 @@ describe('Projects Tests', () => {
projectId: 'MyNewtestProject',
name: "My New test Project"
})
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type("Other Project Name")
+ cy.get('[data-cy="projectName"]').type("Other Project Name")
cy.contains('Enable').click();
cy.getIdField().clear().type("MyNewtestProject")
- cy.contains('The value for the Project ID is already taken')
- cy.clickSave();
- cy.contains('***Form did NOT pass validation, please fix and try to Save again***')
+ cy.get('[data-cy=idError]').contains('The value for the Project ID is already taken').should('be.visible');
+ cy.get('[data-cy=saveProjectButton]').should('be.disabled');
});
it('Project id autofill strips out special characters and spaces', () => {
const expectedId = 'LotsofspecialPchars';
- const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}{P c'ha'rs";
+ const providedName = "!L@o#t$s of %s^p&e*c(i)a_l++_|}/[]#?{P c'ha'rs";
cy.route('POST', `/app/projects/${expectedId}`).as('postNewProject');
+ cy.route('POST', '/app/projectExist').as('projectExists');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type(providedName)
+ cy.get('[data-cy="projectName"]').type(providedName);
+ cy.wait('@projectExists');
cy.getIdField().should('have.value', expectedId)
cy.clickSave();
@@ -92,69 +147,77 @@ describe('Projects Tests', () => {
cy.route('POST', `/app/projects/${expectedId}`)
.as('postNewProject');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type(providedName)
+ cy.get('[data-cy="projectName"]').type(providedName)
cy.getIdField().should('have.value', expectedId)
cy.clickSave();
cy.wait('@postNewProject');
cy.clickButton('Project');
- cy.get('[data-vv-name="projectName"]').type(providedName.toLowerCase())
+ cy.get('[data-cy="projectName"]').type(providedName.toLowerCase())
- cy.contains('The value for the Project Name is already taken')
+ cy.get('[data-cy=projectNameError').contains('The value for the Project Name is already taken').should('be.visible');
- cy.clickSave();
- cy.contains('***Form did NOT pass validation, please fix and try to Save again***')
+ cy.get('[data-cy=saveProjectButton]').should('be.disabled');
});
it('Once project id is enabled name-to-id autofill should be turned off', () => {
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');;
- cy.get('[data-vv-name="projectName"]').type('InitValue');
+ cy.get('[data-cy="projectName"]').type('InitValue');
cy.getIdField().should('have.value', 'InitValue');
cy.contains('Enable').click();
cy.contains('Enabled').not('a');
- cy.get('[data-vv-name="projectName"]').type('MoreValue');
+ cy.get('[data-cy="projectName"]').type('MoreValue');
cy.getIdField().should('have.value', 'InitValue');
- cy.get('[data-vv-name="projectName"]').clear();
+ cy.get('[data-cy="projectName"]').clear();
cy.getIdField().should('have.value', 'InitValue');
});
it('Project name is required', () => {
cy.server();
- cy.route({
- method: 'GET',
- url: '/app/projects'
- }).as('loadProjects');
- cy.visit('/')
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
cy.wait('@loadProjects');
- cy.clickButton('Project');;
+ cy.clickButton('Project');
cy.contains('Enable').click();
cy.getIdField().type('InitValue');
- cy.clickSave();
-
- cy.contains('The Project Name field is required')
- cy.contains('***Form did NOT pass validation, please fix and try to Save again***')
+ cy.get('[data-cy=saveProjectButton').should('be.disabled');
})
it('Project id is required', () => {
- cy.visit('/')
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');;
- cy.get('[data-vv-name="projectName"]').type('New Project');
+ cy.get('[data-cy="projectName"]').type('New Project');
cy.contains('Enable').click();
cy.getIdField().clear()
-
- cy.clickSave();
-
- cy.contains('The Project ID field is required')
- cy.contains('***Form did NOT pass validation, please fix and try to Save again***')
+ cy.get('[data-cy=idError]').contains('Project ID is required').should('be.visible');
+ cy.get('[data-cy=saveProjectButton').should('be.disabled');
})
@@ -163,24 +226,29 @@ describe('Projects Tests', () => {
const maxLenMsg = 'Project Name cannot exceed 50 characters';
const projId = 'ProjectId'
cy.route('POST', `/app/projects/${projId}`).as('postNewProject');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
- cy.visit('/')
cy.clickButton('Project');;
cy.contains('Enable').click();
cy.getIdField().type('ProjectId')
- cy.get('[data-vv-name="projectName"]').type('12');
+ cy.get('[data-cy="projectName"]').type('12');
cy.contains(minLenMsg)
- cy.get('[data-vv-name="projectName"]').type('3');
+ cy.get('[data-cy="projectName"]').type('3');
cy.contains(minLenMsg).should('not.exist')
const longInvalid = Array(51).fill('a').join('');
const longValid = Array(50).fill('a').join('');
- cy.get('[data-vv-name="projectName"]').clear().type(longInvalid);
+ cy.get('[data-cy="projectName"]').clear().type(longInvalid);
cy.contains(maxLenMsg)
- cy.get('[data-vv-name="projectName"]').clear().type(longValid);
+ cy.get('[data-cy="projectName"]').clear().type(longValid);
cy.contains(maxLenMsg).should('not.exist')
cy.clickSave();
@@ -198,21 +266,26 @@ describe('Projects Tests', () => {
const longInvalid = Array(51).fill('a').join('');
const longValid = Array(50).fill('a').join('');
cy.route('POST', `/app/projects/${longValid}`).as('postNewProject');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
- cy.visit('/')
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
cy.clickButton('Project');;
cy.contains('Enable').click();
cy.getIdField().type('12')
- cy.get('[data-vv-name="projectName"]').type(projName);
+ cy.get('[data-cy="projectName"]').type(projName);
cy.contains(minLenMsg)
cy.getIdField().type('3');
cy.contains(minLenMsg).should('not.exist')
- cy.getIdField().clear().type(longInvalid);
+ cy.getIdField().clear().click()
+ cy.getIdField().invoke('val', longInvalid).trigger('input');
cy.contains(maxLenMsg)
- cy.getIdField().clear().type(longValid);
+ cy.getIdField().clear().click().invoke('val', longValid).trigger('input');
cy.contains(maxLenMsg).should('not.exist')
cy.clickSave();
@@ -229,7 +302,7 @@ describe('Projects Tests', () => {
cy.route({
method: 'PUT',
- url: '/admin/projects/proj1/users/foo/roles/ROLE_PROJECT_ADMIN',
+ url: '/admin/projects/proj1/users/bar/roles/ROLE_PROJECT_ADMIN',
status: 400,
response: {errorCode: 'UserNotFound', explanation: 'User was not found'}
}).as('addAdmin');
@@ -238,12 +311,16 @@ describe('Projects Tests', () => {
method: 'POST',
url: '/app/users/suggest*',
status: 200,
- response: [{userId:'foo', userIdForDisplay: 'foo', first: 'foo', last: 'foo', dn: 'foo'}]
+ response: [{userId:'bar', userIdForDisplay: 'bar', first: 'bar', last: 'bar', dn: 'bar'}]
}).as('suggest');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/admin/projects/proj1').as('loadProject');
cy.visit('/projects/proj1/access');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProject');
- cy.contains('Enter user id').type('foo');
+ cy.contains('Enter user id').type('bar');
cy.wait('@suggest');
cy.get('.multiselect__input').type('{enter}');
cy.clickButton('Add');
@@ -259,7 +336,7 @@ describe('Projects Tests', () => {
cy.route({
method: 'PUT',
- url: '/admin/projects/proj1/users/foo/roles/ROLE_PROJECT_ADMIN',
+ url: '/admin/projects/proj1/users/bar/roles/ROLE_PROJECT_ADMIN',
status: 400,
response: {errorCode: 'InternalError', explanation: 'Some Error Occurred'}
}).as('addAdmin');
@@ -268,12 +345,16 @@ describe('Projects Tests', () => {
method: 'POST',
url: '/app/users/suggest*',
status: 200,
- response: [{userId:'foo', userIdForDisplay: 'foo', first: 'foo', last: 'foo', dn: 'foo'}]
+ response: [{userId:'bar', userIdForDisplay: 'bar', first: 'bar', last: 'bar', dn: 'bar'}]
}).as('suggest');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/admin/projects/proj1').as('loadProject');
cy.visit('/projects/proj1/access');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProject');
- cy.contains('Enter user id').type('foo');
+ cy.contains('Enter user id').type('bar');
cy.wait('@suggest');
cy.get('.multiselect__input').type('{enter}');
cy.clickButton('Add');
@@ -296,14 +377,19 @@ describe('Projects Tests', () => {
method: 'POST',
url: '/app/users/suggestDashboardUsers*',
}).as('suggest');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/admin/projects/proj1').as('loadProject');
cy.visit('/projects/proj1/access');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProject');
cy.contains('Enter user id').type('{enter}');
cy.wait('@suggest');
cy.contains('root@skills.org').click();
cy.clickButton('Add');
cy.wait('@addAdmin');
+ cy.wait('@getRolesForRoot');
cy.contains('Firstname LastName (root@skills.org)').should('exist');
});
@@ -322,15 +408,20 @@ describe('Projects Tests', () => {
method: 'POST',
url: '/app/users/suggestDashboardUsers*',
}).as('suggest');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/admin/projects/proj1').as('loadProject');
cy.visit('/projects/proj1/access');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProject');
cy.contains('Enter user id').type('root{enter}');
cy.wait('@suggest');
cy.contains('root@skills.org').click();
cy.clickButton('Add');
cy.wait('@addAdmin');
- cy.contains('Firstname LastName (root@skills.org)').should('be.visible');
+ cy.wait('@getRolesForRoot');
+ cy.contains('Firstname LastName (root@skills.org)')
});
it('Add Admin - forward slash character does not cause error', () => {
@@ -348,12 +439,16 @@ describe('Projects Tests', () => {
method: 'POST',
url: '/app/users/suggestDashboardUsers*',
}).as('suggest');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/admin/projects/proj1').as('loadProject');
cy.visit('/projects/proj1/access');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProject');
- cy.contains('Enter user id').type('root/foo{enter}');
+ cy.contains('Enter user id').type('root/bar{enter}');
cy.wait('@suggest');
});
-
});
+
diff --git a/e2e-tests/cypress/integration/register_root_user_spec.js b/e2e-tests/cypress/integration/register_root_user_spec.js
new file mode 100644
index 00000000..97422a91
--- /dev/null
+++ b/e2e-tests/cypress/integration/register_root_user_spec.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Register Root Users', () => {
+
+ beforeEach(() => {
+ cy.logout();
+ cy.clearDb();
+ });
+
+ afterEach(() => {
+ cy.logout();
+ cy.clearDb();
+
+ cy.fixture('vars.json').then((vars) => {
+ cy.register(vars.rootUser, vars.defaultPass, true);
+ cy.register(vars.defaultUser, vars.defaultPass);
+ })
+ });
+
+ it('register root user', () => {
+ cy.visit('/');
+ cy.contains('New Root Account')
+ cy.get('#firstName').type("Robert")
+ cy.get('#lastName').type("Smith")
+ cy.get('#email').type("rob.smith@madeup.org")
+ cy.get('#password').type("password")
+ cy.get('#password_confirmation').type("password")
+ cy.contains('Create Account').click()
+ });
+});
diff --git a/e2e-tests/cypress/integration/register_user_spec.js b/e2e-tests/cypress/integration/register_user_spec.js
new file mode 100644
index 00000000..f2be2aa4
--- /dev/null
+++ b/e2e-tests/cypress/integration/register_user_spec.js
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Register Dashboard Users', () => {
+
+ beforeEach(() => {
+ cy.logout();
+ });
+
+ it('navigate between login and sign up page', () => {
+ cy.visit('/');
+ cy.contains('Don\'t have a SkillTree account')
+ cy.contains('Sign up').click()
+ cy.contains('New Account')
+ cy.contains('Sign in').click()
+ cy.contains('Don\'t have a SkillTree account')
+ });
+
+ it('register dashboard user', () => {
+ cy.visit('/request-account');
+ cy.contains('New Account')
+ cy.get('#firstName').type("Robert")
+ cy.get('#lastName').type("Smith")
+ cy.get('#email').type("rob.smith@madeup.org")
+ cy.get('#password').type("password")
+ cy.get('#password_confirmation').type("password")
+ cy.contains('Create Account').click()
+
+ cy.contains('No Projects Yet')
+ });
+
+ it('register dashboard validation', () => {
+ cy.visit('/request-account');
+ cy.contains('New Account')
+ cy.get('#firstName').type("Robert")
+ cy.get('#lastName').type("Smith")
+ cy.get('#email').type("rob.smith@madeup.org")
+ cy.get('#password').type("password")
+ cy.get('#password_confirmation').type("password")
+
+
+ // password mismatch via confirmation pass
+ cy.get('#password_confirmation').clear().type("password1")
+ cy.contains('Create Account').should('be.disabled');
+ cy.contains('Password confirmation does not match')
+ cy.get('#password_confirmation').clear().type("password")
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('Password confirmation does not match').should('not.exist')
+
+ // password mismatch via main pass
+ cy.get('#password').clear().type("password1")
+ cy.contains('Create Account').should('be.disabled');
+ cy.contains('Password confirmation does not match')
+ cy.get('#password').clear().type("password")
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('Password confirmation does not match').should('not.exist')
+
+ // password must be at least 8 chars
+ cy.get('#password').clear().type("passwor")
+ cy.contains('Create Account').should('be.disabled');
+ cy.contains('Password cannot be less than 8 characters')
+ cy.get('#password').clear().type("password")
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('Password cannot be less than 8 characters').should('not.exist')
+
+ // password must not exceed 40 characters
+ const invalidPassword = Array(41).fill('a').join('');
+ const validPassword = Array(40).fill('a').join('');
+ cy.get('#password').clear().type(invalidPassword)
+ cy.get('#password_confirmation').clear().type(invalidPassword)
+ cy.contains('Password cannot exceed 40 characters')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#password').type('{backspace}')
+ cy.get('#password_confirmation').type('{backspace}')
+ cy.contains('Password cannot exceed 40 characters').should('not.exist')
+ cy.contains('Create Account').should('be.enabled');
+
+ // email must be at least 5 chars
+ cy.get('#email').clear().type("1234")
+ cy.contains('Email must be a valid email')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#email').clear().type("rob.smith@madeup.org")
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('Email must be a valid email').should('not.exist')
+
+ /*
+ emails over 73 characters are considered valid in vee-validate 3+
+ // email must not exceed 73 chars
+ const invalidEmail = Array(74-9).fill('a').join('');
+ const validEmail = Array(73-9).fill('a').join('');
+ cy.get('#email').clear().type(`${invalidEmail}@mail.org`)
+ cy.contains('The Email field must be a valid email').should('be.visible')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#email').clear().type(`${validEmail}@mail.org`)
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('The Email field must be a valid email').should('not.exist')
+ */
+
+ // email already taken
+ cy.get('#email').clear().type('skills@skills.org')
+ cy.contains('The email address is already used for another account')
+ cy.contains('Create Account').should('be.disabled');
+
+ cy.get('#email').clear().type('skills1@skills.org')
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('The email address is already used for another account').should('not.exist')
+
+ // valid email
+ cy.get('#email').clear().type("rob.smithmadeup.org")
+ cy.contains('Email must be a valid email')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#email').clear().type("rob.smith@madeup.org")
+ cy.contains('Email must be a valid email').should('not.exist')
+ cy.contains('Create Account').should('be.enabled');
+
+ // first name must not be empty
+ cy.get('#firstName').clear()
+ cy.contains('First Name is required')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#firstName').type('Robert')
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('First Name is required').should('not.exist')
+
+ // first name must not exceed 30 characters
+ const thirtyOneChars = Array(31).fill('a').join('');
+ cy.get('#firstName').clear().type(thirtyOneChars)
+ cy.contains('First Name cannot exceed 30 characters')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#firstName').type('{backspace}')
+ cy.contains('First Name exceed 30 characters').should('not.exist')
+ cy.contains('Create Account').should('be.enabled');
+
+ // last name must not be empty
+ cy.get('#lastName').clear()
+ cy.contains('Last Name is required')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#lastName').type('Smith')
+ cy.contains('Create Account').should('be.enabled');
+ cy.contains('Last Name is required').should('not.exist')
+
+ // last name must not exceed 30 characters
+ cy.get('#lastName').clear().type(thirtyOneChars)
+ cy.contains('Last Name cannot exceed 30 characters')
+ cy.contains('Create Account').should('be.disabled');
+ cy.get('#lastName').type('{backspace}')
+ cy.contains('Last Name cannot exceed 30 characters').should('not.exist')
+ cy.contains('Create Account').should('be.enabled');
+ });
+
+});
diff --git a/e2e-tests/cypress/integration/rootUser_pinUnpin_spec.js b/e2e-tests/cypress/integration/rootUser_pinUnpin_spec.js
new file mode 100644
index 00000000..637a2866
--- /dev/null
+++ b/e2e-tests/cypress/integration/rootUser_pinUnpin_spec.js
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe('Root Pin and Unpin Tests', () => {
+ beforeEach(() => {
+ cy.server()
+ .route('GET', '/app/projects').as('getProjects')
+ .route('GET', '/api/icons/customIconCss').as('getProjectsCustomIcons')
+ .route('GET', '/app/userInfo').as('getUserInfo')
+ .route('/admin/projects/proj1/users/root@skills.org/roles').as('getRolesForRoot');
+ });
+
+ it('Pin and Unpin projects', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "one"
+ });
+
+ cy.request('POST', '/app/projects/proj2', {
+ projectId: 'proj2',
+ name: "two"
+ });
+
+ cy.request('POST', '/app/projects/proj3', {
+ projectId: 'proj3',
+ name: "three"
+ });
+
+ cy.request('POST', '/app/projects/proj4', {
+ projectId: 'proj4',
+ name: "four"
+ });
+ cy.logout();
+ cy.fixture('vars.json').then((vars) => {
+ cy.login(vars.rootUser, vars.defaultPass);
+ cy.route('GET', '/app/projects').as('default');
+ cy.route('GET', '/app/projects?search=one').as('searchOne');
+ cy.route('POST', '/root/pin/proj1').as('pinOne');
+ cy.route('DELETE', '/root/pin/proj1').as('unpinOne');
+ cy.route('GET', '/admin/projects/proj1/subjects').as('loadSubjects');
+
+ cy.visit('/');
+ //confirm that default project loading returns no projects for root user
+ cy.wait('@default');
+ cy.contains('No Projects Yet...').should('be.visible');
+
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.contains('Pin Projects');
+ cy.contains('Search Project Catalog');
+
+ cy.get('[data-cy=pinProjectsSearchInput]').type('t');
+ cy.get('[data-cy=pinProjectsSearchResultsNumRows]').contains('Rows: 3');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('Inception');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('two');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('three');
+
+ cy.get('[data-cy=pinProjectsSearchInput]').type('wo');
+ cy.get('[data-cy=pinProjectsSearchResultsNumRows]').contains('Rows: 1');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('Inception').should('not.exist');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('two');
+
+ cy.get('[data-cy=pinProjectsSearchInput]').type('1');
+ cy.get('[data-cy=pinProjects]').contains('No Results');
+
+ cy.get('[data-cy=pinProjectsClearSearch]').click();
+ cy.get('[data-cy=pinProjects]').contains('Search Project Catalog');
+
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get('[data-cy=pinProjectsSearchResultsNumRows]').contains('Rows: 5');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('Inception');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('two');
+ cy.get('[data-cy=pinProjectsSearchResults]').contains('three');
+
+ // pin 1 project
+ const rowSelector = '[data-cy=pinProjectsSearchResults] tbody tr'
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+ cy.get('@cyRows').eq(0).find('td').as('row1');
+ cy.get('@row1').eq(0).contains('four');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('not.exist');
+ cy.get('@row1').eq(0).find('[data-cy=pinButton]').click();
+ cy.get('@row1').eq(0).find('[data-cy=pinButton]').should('not.exist');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('exist');
+ cy.get('[data-cy=modalDoneButton]').click();
+
+ const projectsSelector = '[data-cy=projectCard]';
+ cy.get(projectsSelector).should('have.length', 1).as('projects');
+ cy.get('@projects').eq(0).contains('four');
+
+ // make sure the pinned project is still pinned
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+ cy.get('@cyRows').eq(0).find('td').as('row1');
+ cy.get('@row1').eq(0).find('[data-cy=pinButton]').should('not.exist');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('exist');
+ cy.get('[data-cy=modalDoneButton]').click();
+
+ // unpin that project
+ cy.get('@projects').eq(0).contains('Unpin').click();
+ cy.contains('No Projects Yet');
+ });
+ });
+
+ it('Pin all projects then unpin 1', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "one"
+ });
+
+ cy.request('POST', '/app/projects/proj2', {
+ projectId: 'proj2',
+ name: "two"
+ });
+
+ cy.request('POST', '/app/projects/proj3', {
+ projectId: 'proj3',
+ name: "three"
+ });
+
+ cy.request('POST', '/app/projects/proj4', {
+ projectId: 'proj4',
+ name: "four"
+ });
+ cy.logout();
+ cy.fixture('vars.json').then((vars) => {
+ cy.login(vars.rootUser, vars.defaultPass);
+ cy.route('GET', '/app/projects').as('default');
+ cy.route('GET', '/app/projects?search=one').as('searchOne');
+ cy.route('POST', '/root/pin/proj1').as('pinOne');
+ cy.route('DELETE', '/root/pin/proj1').as('unpinOne');
+ cy.route('GET', '/admin/projects/proj1/subjects').as('loadSubjects');
+
+ cy.visit('/');
+ //confirm that default project loading returns no projects for root user
+ cy.wait('@default');
+ cy.contains('No Projects Yet...').should('be.visible');
+
+ const rowSelector = '[data-cy=pinProjectsSearchResults] tbody tr'
+ const projectsSelector = '[data-cy=projectCard]';
+
+ // pin all projects
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.contains('Search Project Catalog');
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows')
+ .eq(i)
+ .find('td')
+ .as('row1');
+ cy.get('@row1')
+ .eq(0)
+ .find('[data-cy=pinButton]')
+ .click();
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('exist');
+ }
+ cy.get('[data-cy=modalDoneButton]').click();
+
+ cy.get(projectsSelector).should('have.length', 5).as('projects');
+ cy.contains('ID: Inception');
+ cy.contains('one');
+ cy.contains('two');
+ cy.contains('three');
+ cy.contains('four');
+
+ // unpin from the component
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.contains('Pin Projects');
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get('[data-cy=pinProjectsSearchResultsNumRows]').contains('Rows: 5');
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+ cy.get('@cyRows').eq(2).find('td').as('row1');
+ cy.get('@row1').eq(0).find('[data-cy=pinButton]').should('not.exist');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('exist');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').click();
+ cy.get('@row1').eq(0).find('[data-cy=pinButton]').should('exist');
+ cy.get('@row1').eq(0).find('[data-cy=unpinButton]').should('not.exist');
+
+ cy.get('[data-cy=modalDoneButton]').click();
+
+ cy.get(projectsSelector).should('have.length', 4).as('projects');
+ cy.contains('Inception');
+ cy.contains('two');
+ cy.contains('one').should('not.exist');
+ });
+ });
+
+ it('Browse projects catalog - many projects', () => {
+
+ for (let i = 0; i < 12; i += 1) {
+ cy.request('POST', `/app/projects/proj${i}`, {
+ projectId: `proj${i}`,
+ name: `Good project ${i}`
+ });
+ }
+
+ cy.logout();
+ cy.fixture('vars.json').then((vars) => {
+ cy.login(vars.rootUser, vars.defaultPass);
+ cy.route('GET', '/app/projects').as('default');
+ cy.route('GET', '/app/projects?search=one').as('searchOne');
+ cy.route('POST', '/root/pin/proj1').as('pinOne');
+ cy.route('DELETE', '/root/pin/proj1').as('unpinOne');
+ cy.route('GET', '/admin/projects/proj1/subjects').as('loadSubjects');
+
+ cy.visit('/');
+ //confirm that default project loading returns no projects for root user
+ cy.wait('@default');
+ cy.contains('No Projects Yet...').should('be.visible');
+
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.contains('Pin Projects');
+ cy.contains('Search Project Catalog');
+
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get('[data-cy=pinProjectsSearchResultsNumRows]').contains('Rows: 13');
+
+ const rowSelector = '[data-cy=pinProjectsSearchResults] tbody tr'
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows').eq(i).find('td').as('row1');
+ cy.get('@row1').eq(0).contains(`Good project ${i}`);
+ }
+
+ cy.get('[data-cy=pinedResultsPaging]').contains('2').click();
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows').eq(i).find('td').as('row1');
+ cy.get('@row1').eq(0).contains(`Good project ${i+5}`);
+ }
+
+ cy.get('[data-cy=pinedResultsPaging]').contains('3').click();
+ cy.get(rowSelector).should('have.length', 3).as('cyRows');
+ for (let i = 0; i < 2; i += 1) {
+ cy.get('@cyRows').eq(i).find('td').as('row1');
+ cy.get('@row1').eq(0).contains(`Good project ${i+10}`);
+ }
+
+ cy.get('@cyRows').eq(2).find('td').as('row1');
+ cy.get('@row1').eq(0).contains('Inception');
+ });
+ });
+
+ it('Close Pin Projects modal using escape and then reopen', () => {
+ cy.logout();
+ cy.fixture('vars.json').then((vars) => {
+ cy.login(vars.rootUser, vars.defaultPass);
+ cy.route('GET', '/app/projects').as('default');
+ cy.route('GET', '/app/projects?search=one').as('searchOne');
+ cy.route('POST', '/root/pin/proj1').as('pinOne');
+ cy.route('DELETE', '/root/pin/proj1').as('unpinOne');
+ cy.route('GET', '/admin/projects/proj1/subjects').as('loadSubjects');
+
+ cy.visit('/');
+ //confirm that default project loading returns no projects for root user
+ cy.wait('@default');
+ cy.contains('No Projects Yet...').should('be.visible');
+
+ // open the pin projects modal
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.get('[data-cy=pinProjects').should('exist') // dialog exists
+ cy.contains('Pin Projects');
+ cy.contains('Search Project Catalog');
+
+ // close with escape
+ cy.get('[data-cy=pinProjectsSearchInput]').type('{esc}', {force: true});
+ cy.get('[data-cy=pinProjects').should('not.exist') // dialog does not exists
+
+ // can re-open the pin modal
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.get('[data-cy=pinProjects').should('exist') // dialog exists
+ cy.contains('Pin Projects');
+ cy.contains('Search Project Catalog');
+
+ // close with escape
+ cy.get('[data-cy=pinProjectsSearchInput]').type('{esc}', {force: true});
+ cy.get('[data-cy=pinProjects').should('not.exist') // dialog does not exists
+
+ // open the new project modal
+ cy.clickButton('Project');
+ cy.contains('New Project'); // new project dialog does exist
+ cy.get('[data-cy=pinProjects').should('not.exist') // pin project dialog does not exists
+ });
+
+ });
+
+ it('Sort and then un-sort projects', () => {
+ cy.request('POST', '/app/projects/proj1', {
+ projectId: 'proj1',
+ name: "000"
+ });
+
+ cy.request('POST', '/app/projects/proj2', {
+ projectId: 'proj2',
+ name: "100"
+ });
+
+ cy.request('POST', '/app/projects/proj3', {
+ projectId: 'proj3',
+ name: "200"
+ });
+
+ cy.request('POST', '/app/projects/proj4', {
+ projectId: 'proj4',
+ name: "300"
+ });
+ cy.logout();
+ cy.fixture('vars.json').then((vars) => {
+ cy.login(vars.rootUser, vars.defaultPass);
+ cy.route('GET', '/app/projects').as('default');
+ cy.route('GET', '/app/projects?search=one').as('searchOne');
+ cy.route('POST', '/root/pin/proj1').as('pinOne');
+ cy.route('DELETE', '/root/pin/proj1').as('unpinOne');
+ cy.route('GET', '/admin/projects/proj1/subjects').as('loadSubjects');
+
+ cy.visit('/');
+ //confirm that default project loading returns no projects for root user
+ cy.wait('@default');
+ cy.contains('No Projects Yet...').should('be.visible');
+
+ const rowSelector = '[data-cy=pinProjectsSearchResults] tbody tr'
+ const headerSelector = '[data-cy=pinProjectsSearchResults] thead tr th'
+
+ // load all projects in default (ASC) order
+ cy.get('[data-cy=subPageHeaderControls]').contains('Pin').click();
+ cy.contains('Search Project Catalog');
+ cy.get('[data-cy=pinProjectsLoadAllButton]').click();
+ cy.get(rowSelector).should('have.length', 5).as('cyRows');
+
+ // verify rows are in ASC order based on project name
+ const rowNamesAsc = ['000', '100', '200', '300', 'Inception']
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows')
+ .eq(i)
+ .find('td')
+ .as('row-i');
+ cy.get('@row-i').contains(rowNamesAsc[i])
+ }
+
+ // now click the 'Name' header to sort in DESC order
+ cy.get(headerSelector).contains('Name').click()
+
+ // verify rows are in DESC order based on project name
+ const rowNameDesc = rowNamesAsc.reverse()
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows')
+ .eq(i)
+ .find('td')
+ .as('row-i');
+ cy.get('@row-i').contains(rowNameDesc[i])
+ }
+
+ // finally click a non-sortable column and sort order is reset and 'Name' column should still be visible
+ cy.get(headerSelector).contains('Subjects').click()
+ cy.get(headerSelector).contains('Name').should('be.visible')
+
+ // verify the order did not change
+ for (let i = 0; i < 5; i += 1) {
+ cy.get('@cyRows')
+ .eq(i)
+ .find('td')
+ .as('row-i');
+ cy.get('@row-i').contains(rowNameDesc[i])
+ }
+
+ cy.get('[data-cy=modalDoneButton]').click();
+
+ });
+ });
+
+
+
+});
+
diff --git a/e2e-tests/cypress/integration/settings_spec.js b/e2e-tests/cypress/integration/settings_spec.js
index 5b8249a2..5bd455e2 100644
--- a/e2e-tests/cypress/integration/settings_spec.js
+++ b/e2e-tests/cypress/integration/settings_spec.js
@@ -33,7 +33,7 @@ describe('Settings Tests', () => {
cy.route({method: 'GET', url: '/root/isRoot'}).as('checkRoot');
cy.visit('/');
- cy.contains('My Projects');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
cy.get('button.dropdown-toggle').first().click({force: true});
cy.contains('Settings').click();
cy.wait('@checkRoot');
@@ -50,6 +50,10 @@ describe('Settings Tests', () => {
cy.server();
cy.route('POST', '/root/users/without/role/ROLE_SUPER_DUPER_USER?userSuggestOption=ONE').as('getEligibleForRoot');
cy.route('PUT', '/root/users/skills@skills.org/roles/ROLE_SUPER_DUPER_USER').as('addRoot');
+ cy.route('GET', '/app/projects').as('loadProjects');
+ cy.route('GET', '/app/userInfo/hasRole/ROLE_SUPERVISOR').as('isSupervisor');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/public/config').as('loadConfig');
cy.route({
method: 'GET',
url: '/app/projects'
@@ -57,7 +61,11 @@ describe('Settings Tests', () => {
cy.route({method: 'GET', url: '/root/isRoot'}).as('checkRoot');
cy.visit('/');
- cy.contains('My Projects');
+ cy.wait('@loadConfig');
+ cy.wait('@loadUserInfo');
+ cy.wait('@loadProjects');
+ cy.wait('@isSupervisor');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
cy.get('button.dropdown-toggle').first().click({force: true});
cy.contains('Settings').click();
cy.wait('@checkRoot');
@@ -66,6 +74,7 @@ describe('Settings Tests', () => {
cy.wait('@getEligibleForRoot');
});
+
it('Add Root User With No Query', () => {
cy.server();
cy.route('POST', '/root/users/without/role/ROLE_SUPER_DUPER_USER?userSuggestOption=ONE').as('getEligibleForRoot');
@@ -100,7 +109,7 @@ describe('Settings Tests', () => {
cy.route({method: 'GET', url: '/root/isRoot'}).as('checkRoot');
cy.visit('/');
- cy.contains('My Projects');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
cy.get('li').contains('Badges').should('not.exist');
cy.vuex().its('state.access.isSupervisor').should('equal', false);
@@ -119,6 +128,33 @@ describe('Settings Tests', () => {
cy.get('[data-cy=navigationmenu]').contains('Badges', {timeout: 5000}).should('be.visible');
});
+ it('Add Supervisor User Not Found', () => {
+ cy.server();
+ // cy.route('PUT', '/root/users/root@skills.org/roles/ROLE_SUPERVISOR').as('addSupervisor');
+ cy.route('POST', 'root/users/without/role/ROLE_SUPERVISOR?userSuggestOption=ONE', [{"userId":"blah@skills.org","userIdForDisplay":"blah@skills.org","first":"Firstname","last":"LastName","nickname":"Firstname LastName","dn":null}]).as('getEligibleForSupervisor');
+ cy.route({
+ method: 'GET',
+ url: '/app/projects'
+ }).as('loadProjects');
+ cy.route({method: 'GET', url: '/root/isRoot'}).as('checkRoot');
+
+ cy.visit('/');
+ cy.get('[data-cy=subPageHeader]').contains('Projects');
+
+ cy.get('li').contains('Badges').should('not.exist');
+ cy.vuex().its('state.access.isSupervisor').should('equal', false);
+ cy.get('button.dropdown-toggle').first().click({force: true});
+ cy.contains('Settings').click();
+ cy.wait('@checkRoot');
+ cy.contains('Security').click();
+ cy.get('[data-cy=supervisorrm] div.multiselect__tags').type('root');
+ cy.wait('@getEligibleForSupervisor');
+ cy.get('[data-cy=supervisorrm]').contains('blah@skills.org').click();
+ cy.get('[data-cy=supervisorrm]').contains('Add').click();
+ cy.get('[data-cy=error-msg]').contains('Error! Request could not be completed! User [blah@skills.org] does not exist')
+ cy.vuex().its('state.access.isSupervisor').should('equal', false);
+ });
+
it('Add Supervisor User No Query', () => {
cy.server();
@@ -142,4 +178,290 @@ describe('Settings Tests', () => {
cy.get('[data-cy=supervisorrm] div.multiselect__tags').type('foo/bar{enter}');
cy.wait('@getEligibleForSupervisor');
});
+
+ it('Email Server Settings', () => {
+ cy.server();
+ cy.route('GET', '/root/getEmailSettings').as('loadEmailSettings');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.get('.userName').parent().click();
+ cy.contains('Settings').click();
+ cy.contains('Email').click();
+ cy.wait('@loadEmailSettings');
+ cy.get$('[data-cy=hostInput]').clear();
+ cy.get$('[data-cy=hostError]').contains('Host is required');
+ cy.get$('[data-cy=emailSettingsSave]').should('be.disabled');
+ cy.get$('[data-cy=hostInput]').type('{selectall}localhost');
+ cy.get$('[data-cy=portInput]').clear();
+ cy.get$('[data-cy=portError]').contains('Port is required');
+ cy.get$('[data-cy=emailSettingsSave]').should('be.disabled');
+ cy.get$('[data-cy=portInput]').type('{selectall}-55');
+ cy.get$('[data-cy=portError]').contains('Port must be 1 or greater');
+ cy.get$('[data-cy=portInput]').type('{selectall}65536');
+ cy.get$('[data-cy=portError]').contains('Port must be 65535 or less');
+ cy.get$('[data-cy=portInput]').type('{selectall}1026');
+ cy.get$('[data-cy=protocolInput]').clear();
+ cy.get$('[data-cy=protocolError').contains('Protocol is required');
+ cy.get$('[data-cy=emailSettingsSave]').should('be.disabled');
+ cy.get$('[data-cy=protocolInput]').type('{selectall}smtp');
+
+
+ cy.get$('[data-cy=tlsSwitch]').next('.custom-control-label').click();
+ cy.get$('[data-cy=authSwitch]').next('.custom-control-label').click();
+ cy.get('[data-cy=emailUsername]').should('be.visible');
+ cy.get('[data-cy=emailPassword]').should('be.visible');
+ cy.get$('[data-cy=emailUsername]').type('username');
+ cy.get$('[data-cy=emailPassword]').type('password');
+ cy.get$('[data-cy=emailSettingsTest]').click();
+ cy.get$('[data-cy=emailSettingsSave]').click();
+ //verify that appropriate saved data is loaded when form is loaded again
+ cy.contains('System').click();
+ cy.visit('/settings/email');
+ cy.wait('@loadEmailSettings');
+ cy.get('[data-cy=hostInput]').should('have.value', 'localhost');
+ cy.get('[data-cy=portInput]').should('have.value', '1026');
+ cy.get('[data-cy=protocolInput]').should('have.value', 'smtp');
+ cy.get('[data-cy=tlsSwitch]').should('have.value', 'true');
+ cy.get('[data-cy=authSwitch]').should('have.value', 'true');
+ cy.get('[data-cy=emailUsername]').should('have.value', 'username');
+ cy.get('[data-cy=emailPassword]').should('have.value', 'password');
+ });
+
+ it('Email Settings reasonable timeout', () => {
+ cy.server();
+ cy.route('GET', '/root/getEmailSettings').as('loadEmailSettings');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.get('.userName').parent().click();
+ cy.contains('Settings').click();
+ cy.contains('Email').click();
+ cy.wait('@loadEmailSettings');
+ cy.get$('[data-cy=hostInput]').type('{selectall}localhost');
+ //this needs to be an open port that is NOT an smtp server for the purposes of this test
+ cy.get$('[data-cy=portInput]').type('{selectall}8080');
+ cy.get$('[data-cy=protocolInput]').type('{selectall}smtp');
+
+ cy.get$('[data-cy=emailSettingsSave]').click();
+ cy.wait(12*1000);
+ cy.get('[data-cy=connectionError]').should('be.visible');
+ });
+
+ it('System Settings', () => {
+ cy.server();
+ cy.route('GET', '/root/getSystemSettings').as('loadSystemSettings');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/public/config').as('loadConfig');
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.get('.userName').parent().click();
+ cy.contains('Settings').click();
+ cy.contains('System').click();
+
+ cy.wait('@loadSystemSettings');
+ cy.get('[data-cy=resetTokenExpiration]').should('have.value', '2H');
+ cy.get$('[data-cy=publicUrl]').type('{selectall}http://localhost:8082');
+ cy.get$('[data-cy=resetTokenExpiration]').type('{selectall}2H25M22S');
+ cy.get$('[data-cy=fromEmail]').type('{selectall}foo@skilltree.madeup');
+ cy.get$('[data-cy=customHeader').type('{selectall}');
+ cy.get$('[data-cy=customFooter').type('{selectall}');
+ cy.get$('[data-cy=saveSystemSettings]').click();
+ cy.wait('@loadConfig');
+ cy.get('#customHeader').contains('HEADER');
+ cy.get('#customFooter').contains('FOOTER');
+ cy.visit('/settings/system');
+ cy.wait('@loadSystemSettings');
+ cy.get('[data-cy=publicUrl]').should('have.value', 'http://localhost:8082');
+ cy.get('[data-cy=resetTokenExpiration]').should('have.value', '2H25M22S');
+ cy.get('[data-cy=fromEmail]').should('have.value', 'foo@skilltree.madeup');
+ cy.get('[data-cy=customHeader').should('have.value','');
+ cy.get('[data-cy=customFooter').should('have.value','');
+
+ //confirm that header/footer persist after logging out
+ cy.logout();
+ cy.visit('/');
+ cy.wait('@loadConfig');
+ cy.get('#customHeader').contains('HEADER');
+ cy.get('#customFooter').contains('FOOTER');
+ });
+
+ it('System Settings - script tags not allowed in footer/header', () => {
+ cy.server();
+ cy.route('GET', '/root/getSystemSettings').as('loadSystemSettings');
+ cy.route('GET', '/app/userInfo').as('loadUserInfo');
+ cy.route('GET', '/public/config').as('loadConfig');
+ cy.visit('/');
+ cy.wait('@loadUserInfo');
+ cy.get('.userName').parent().click();
+ cy.contains('Settings').click();
+ cy.contains('System').click();
+
+ cy.wait('@loadSystemSettings');
+ cy.get('[data-cy=resetTokenExpiration]').should('have.value', '2H');
+ cy.get$('[data-cy=publicUrl]').type('{selectall}http://localhost:8082');
+ cy.get$('[data-cy=resetTokenExpiration]').type('{selectall}2H25M22S');
+ cy.get$('[data-cy=fromEmail]').type('{selectall}foo@skilltree.madeup');
+ cy.get$('[data-cy=customHeader]').type('{selectall}');
+ cy.get$('[data-cy=customFooter]').type('{selectall}');
+ cy.get('[data-cy=customHeaderError]').should('be.visible');
+ cy.get('[data-cy=customHeaderError]').contains('
-
-
diff --git a/frontend/src/components/access/RequestAccess.vue b/frontend/src/components/access/RequestAccess.vue
deleted file mode 100644
index c1ebe176..00000000
--- a/frontend/src/components/access/RequestAccess.vue
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
-
Create Skills Dashboard Account
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/header/HelpButton.vue b/frontend/src/components/header/HelpButton.vue
deleted file mode 100644
index 0835274e..00000000
--- a/frontend/src/components/header/HelpButton.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
- Dashboard Guide
-
-
- Integration Guide
-
-
-
-
-
-
-
diff --git a/frontend/src/components/levels/NewLevel.vue b/frontend/src/components/levels/NewLevel.vue
deleted file mode 100644
index 8b3040cc..00000000
--- a/frontend/src/components/levels/NewLevel.vue
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
-
- Cancel Icon Selection
-
-
-
-
-
-
- Save
-
-
- Cancel
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/settings/EmailServerSettings.vue b/frontend/src/components/settings/EmailServerSettings.vue
deleted file mode 100644
index 93e3887e..00000000
--- a/frontend/src/components/settings/EmailServerSettings.vue
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
-
- {{ emailInfo.tlsEnabled ? 'TLS Enabled' : 'TLS Disabled' }}
-
-
-
-
- {{ emailInfo.authEnabled ? 'Authentication Enabled' : 'Authentication Disabled' }}
-
-
-
-
-
-
- Test
-
-
-
- Save
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/skills/EditSkill.vue b/frontend/src/components/skills/EditSkill.vue
deleted file mode 100644
index 5866faf9..00000000
--- a/frontend/src/components/skills/EditSkill.vue
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
-
-
- Skill Name
-
-
- {{ errors[0] }}
-
-
-
-
-
-
-
-
- Version
-
-
-
-
- {{ errors[0]}}
-
-
-
-
-
-
-
-
-
-
- Point Increment
-
-
- {{ errors[0]}}
-
-
-
-
-
- Occurrences to Completion
-
-
- {{ errors[0]}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Window's Max Occurrences
-
-
-
-
- {{ errors[0]}}
-
-
-
-
-
-
-
-
-
-
Description
-
-
-
- {{ errors[0] }}
-
-
-
-
-
- Help URL/Path
-
-
-
- {{ errors.first('helpUrl')}}
-
-
- ***{{ overallErrMsg }}***
-
-
-
-
-
-
- Save
-
-
- Cancel
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/utils/MarkdownEditor.vue b/frontend/src/components/utils/MarkdownEditor.vue
deleted file mode 100644
index 5790d6f3..00000000
--- a/frontend/src/components/utils/MarkdownEditor.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
- Write
-
-
-
-
-
-
-
- Preview
-
-
-
-
-
-
-
Markdown is supported
-
-
-
-
-
-
diff --git a/frontend/src/components/utils/Navigation.vue b/frontend/src/components/utils/Navigation.vue
deleted file mode 100644
index ccde9f05..00000000
--- a/frontend/src/components/utils/Navigation.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
Navigation
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/utils/UserDnInput.vue b/frontend/src/components/utils/UserDnInput.vue
deleted file mode 100644
index c4fd9324..00000000
--- a/frontend/src/components/utils/UserDnInput.vue
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
diff --git a/frontend/src/components/utils/pages/PageHeader.vue b/frontend/src/components/utils/pages/PageHeader.vue
deleted file mode 100644
index 0148eeba..00000000
--- a/frontend/src/components/utils/pages/PageHeader.vue
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
-Copyright 2020 SkillTree
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-
-
-
-
-
-
{{ options.title }}
- {{ options.subTitle }}
-
-
-
-
-
-
{{stat.label}}
-
{{ stat.count | number}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/validators/RegisterValidators.js b/frontend/src/validators/RegisterValidators.js
deleted file mode 100644
index 73549c42..00000000
--- a/frontend/src/validators/RegisterValidators.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2020 SkillTree
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { Validator } from 'vee-validate';
-import './OptionalNumericValidator';
-import './CustomDescriptionValidator';
-import './CustomNameValidator';
-import ValidatorFactory from './ValidatorFactory';
-import store from '../store/store';
-
-export default {
- init() {
- Validator.extend('maxDescriptionLength', ValidatorFactory.newCharLengthValidator(store.getters.config.descriptionMaxLength));
- Validator.extend('maxFirstNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxFirstNameLength));
- Validator.extend('maxLastNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxLastNameLength));
- Validator.extend('maxNicknameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxNicknameLength));
-
- Validator.extend('minUsernameLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minUsernameLength));
-
- Validator.extend('minPasswordLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minPasswordLength));
- Validator.extend('maxPasswordLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxPasswordLength));
-
- Validator.extend('minIdLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minIdLength));
- Validator.extend('maxIdLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxIdLength));
-
- Validator.extend('minNameLength', ValidatorFactory.newCharMinLengthValidator(store.getters.config.minNameLength));
-
- Validator.extend('maxBadgeNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxBadgeNameLength));
- Validator.extend('maxProjectNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxProjectNameLength));
- Validator.extend('maxSkillNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxSkillNameLength));
- Validator.extend('maxSubjectNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxSubjectNameLength));
- Validator.extend('maxLevelNameLength', ValidatorFactory.newCharLengthValidator(store.getters.config.maxLevelNameLength));
-
- Validator.extend('maxSkillVersion', ValidatorFactory.newMaxNumValidator(store.getters.config.maxSkillVersion));
- Validator.extend('maxPointIncrement', ValidatorFactory.newMaxNumValidator(store.getters.config.maxPointIncrement));
- Validator.extend('maxNumPerformToCompletion', ValidatorFactory.newMaxNumValidator(store.getters.config.maxNumPerformToCompletion));
- Validator.extend('maxNumPointIncrementMaxOccurrences', ValidatorFactory.newMaxNumValidator(store.getters.config.maxNumPointIncrementMaxOccurrences));
-
- Validator.extend('userNoSpaceInUserIdInNonPkiMode', ValidatorFactory.newUserObjNoSpacesValidatorInNonPkiMode(store.getters.isPkiAuthenticated));
- },
-};
diff --git a/license-add/license-add-config.json b/license-add/license-add-config.json
index 5abe5488..fdddad9c 100644
--- a/license-add/license-add-config.json
+++ b/license-add/license-add-config.json
@@ -1,7 +1,7 @@
{
"ignore": [ ".git", "coverage", ".*/**", ".*", "*.iml", "babel.config.js", "rollup.config.js", "app/assets/**", "target/**",
"pom.xml", "build/**", "**/robots.txt", "**.svg", "**.config.js", "**/**/*.conf.js", "**/**/.*",
- "cypress/videos/**", "**/*.jar", "**.xml", "cypress/fonts/**", "cypress/fixtures/**"
+ "cypress/videos/**", "**/*.jar", "**.xml", "cypress/fonts/**", "cypress/fixtures/**", "logs/*"
],
"license": "../license-add/LICENSE-HEADER.txt",
"licenseFormats": {
diff --git a/pom.xml b/pom.xml
index 60174165..2fd475da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,15 +4,15 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- skills
- skills-service
+ skill-tree
+ skills-service-parent
pom
- 1.1.4-SNAPSHOT
+ 1.3.0-SNAPSHOT
- frontend
+ call-stack-profiler
+ dashboard
client-display
- skills-bootstrap
- backend
+ service
@@ -21,34 +21,37 @@
1.11
- 2.5.9
- 3.0.0-01
- 2.5.1-02
+ 3.0.5
+ 3.6.0-03
+ 3.0.5-01
- 1.3-groovy-2.5
- 1.8
+ 2.0-M3-groovy-3.0
+ 11
- 5.2.1
+ 6.5.5
- 3.7
- 3.2.2
- 2.6
+ 3.11
+ 4.4
+ 2.7
- 1.7.6
- v12.16.1
+ 1.10.3
+ v14.15.0
- 4.5.6
- 28.1-jre
+ 4.5.12
+ 29.0-jre
- 2.1.13.RELEASE
+ 2.3.3.RELEASE
+
+ 1.6.0
+ 2.27.2
org.springframework.boot
spring-boot-starter-parent
- 2.1.13.RELEASE
+ 2.3.3.RELEASE
diff --git a/backend/pom.xml b/service/pom.xml
similarity index 83%
rename from backend/pom.xml
rename to service/pom.xml
index c116f691..c78128ac 100644
--- a/backend/pom.xml
+++ b/service/pom.xml
@@ -3,14 +3,14 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- skills-service
- skills
- 1.1.4-SNAPSHOT
+ skills-service-parent
+ skill-tree
+ 1.3.0-SNAPSHOT
4.0.0
- backend
+ skills-service
javax.xml.bind
jaxb-api
- 2.3.0
+ 2.3.1
com.sun.xml.bind
@@ -56,7 +56,7 @@
com.sun.xml.bind
jaxb-impl
- 2.3.0
+ 2.3.3
@@ -70,7 +70,7 @@
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
- 2.1.12.RELEASE
+ 2.3.3.RELEASE
@@ -78,6 +78,10 @@
org.springframework.boot
spring-boot-starter-mail
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
org.codehaus.groovy
@@ -85,10 +89,6 @@
${groovy.version}
pom
-
- org.codehaus.groovy
- groovy-test-junit5
-
org.codehaus.groovy
groovy-test
@@ -143,6 +143,14 @@
org.springframework.boot
spring-boot-starter-reactor-netty
+
+ org.springframework.session
+ spring-session-data-redis
+
+
+ io.lettuce
+ lettuce-core
+
ch.qos.logback
@@ -162,7 +170,7 @@
mysql
mysql-connector-java
- 8.0.16
+ 8.0.21
org.postgresql
@@ -179,8 +187,8 @@
${commons.lang.version}
- commons-collections
- commons-collections
+ org.apache.commons
+ commons-collections4
${commons.collections.version}
@@ -193,19 +201,20 @@
org.spockframework
spock-core
${spock.myVersion}
-
-
- org.codehaus.groovy
- groovy-all
-
-
test
- profile
+ org.codehaus.groovy
+ groovy
+ ${groovy.version}
+ test
+
+
+
+ skill-tree
call-stack-profiler
- 1.0.2
+ ${project.version}
org.slf4j
@@ -237,6 +246,32 @@
org.springframework.boot
spring-boot-starter-test
test
+
+
+ junit
+ junit
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ 5.7.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.7.0
+ test
org.spockframework
@@ -245,6 +280,29 @@
test
+
+ com.icegreen
+ greenmail
+ ${greenmail.version}
+ test
+
+
+ junit
+ junit
+
+
+
+
+ com.github.tomakehurst
+ wiremock-jre8
+ ${wiremock.version}
+ test
+
+
+ org.codehaus.groovy
+ groovy
+
+
@@ -280,6 +338,12 @@
groovy-all
${groovy.version}
pom
+
+
+ org.codehaus.groovy
+ groovy-testng
+
+
org.springframework.boot
@@ -305,6 +369,16 @@
+
+ maven-failsafe-plugin
+ 2.22.2
+
+
+ **/*DBIT
+
+
+
+
maven-clean-plugin
@@ -321,7 +395,7 @@
maven-resources-plugin
- copy Vue.js frontend content
+ copy Vue.js dashboard content
generate-resources
copy-resources
@@ -331,7 +405,7 @@
true
- ${project.parent.basedir}/frontend/dist
+ ${project.parent.basedir}/dashboard/dist
@@ -355,25 +429,6 @@
-
- copy Vue.js boostrap content
- generate-resources
-
- copy-resources
-
-
- ${basedir}/src/main/resources/public/bootstrap
- true
-
-
- ${project.parent.basedir}/skills-bootstrap/dist/
-
- /*/
-
-
-
-
-
@@ -435,6 +490,8 @@
The MIT License
The GNU General Public License (GPL) Version 2 with the Classpath Exception
Eclipse Public License - Version 1.0
+ Eclipse Public License - Version 2.0
+ Eclipse Distribution License - Version 1.0
Dual license: Common Development and Distribution License 1.1 (CDDL-1.1) and The GNU General Public License (GPL) Version 2
Common Development and Distribution License 1.1 (CDDL-1.1) + The GNU General Public License (GPL) Version 2
Common Development and Distribution License 1.1 (CDDL-1.1) + The GNU General Public License (GPL) Version 2 with the Classpath Exception
@@ -479,6 +536,8 @@
Common Development and Distribution License (CDDL) + The GNU General Public License (GPL) Version 2 with the Classpath Exception|CDDL + GPLv2 with classpath exception
The GNU General Public License (GPL) Version 2 with FOSS exception|The GNU General Public License, v2 with FOSS exception
Mozilla Public License, Version 2.0 or Eclipse Public License - Version 1.0|MPL 2.0 or EPL 1.0
+ Eclipse Public License - Version 2.0|Eclipse Public License v. 2.0|Eclipse Public License v2.0
+ Eclipse Distribution License - Version 1.0|EDL 1.0|Eclipse Distribution License v. 1.0|Eclipse Distribution License - v 1.0
@@ -497,7 +556,9 @@
**/*.xml
**/*.jks
**/*.ftl
+ src/test/resources/certs/*.*
src/main/resources/public/**
+ src/main/resources/templates/**
**/license/*.properties
diff --git a/backend/src/license/override-THIRD-PARTY.properties b/service/src/license/override-THIRD-PARTY.properties
similarity index 100%
rename from backend/src/license/override-THIRD-PARTY.properties
rename to service/src/license/override-THIRD-PARTY.properties
diff --git a/backend/src/main/java/skills/CachingConfiguration.groovy b/service/src/main/java/skills/CachingConfiguration.groovy
similarity index 100%
rename from backend/src/main/java/skills/CachingConfiguration.groovy
rename to service/src/main/java/skills/CachingConfiguration.groovy
diff --git a/service/src/main/java/skills/EmailTemplatingConfig.groovy b/service/src/main/java/skills/EmailTemplatingConfig.groovy
new file mode 100644
index 00000000..9cd0bf88
--- /dev/null
+++ b/service/src/main/java/skills/EmailTemplatingConfig.groovy
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.thymeleaf.spring5.SpringTemplateEngine
+import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver
+import org.thymeleaf.templatemode.TemplateMode
+
+@Configuration
+class EmailTemplatingConfig {
+
+ @Bean
+ public SpringResourceTemplateResolver thymeleafTemplateResolver() {
+ SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
+ templateResolver.setPrefix("classpath:/templates/");
+ templateResolver.setSuffix(".html");
+ templateResolver.setCacheable(false)
+ templateResolver.setTemplateMode(TemplateMode.HTML);
+ templateResolver.setCharacterEncoding("UTF-8");
+ return templateResolver;
+ }
+
+ @Bean
+ public SpringTemplateEngine thymeleafTemplateEngine() {
+ SpringTemplateEngine templateEngine = new SpringTemplateEngine();
+ templateEngine.setTemplateResolver(thymeleafTemplateResolver());
+ templateEngine.setEnableSpringELCompiler(true);
+ return templateEngine;
+ }
+}
diff --git a/backend/src/main/java/skills/HealthChecker.groovy b/service/src/main/java/skills/HealthChecker.groovy
similarity index 89%
rename from backend/src/main/java/skills/HealthChecker.groovy
rename to service/src/main/java/skills/HealthChecker.groovy
index 43dc6908..d3cdf64a 100644
--- a/backend/src/main/java/skills/HealthChecker.groovy
+++ b/service/src/main/java/skills/HealthChecker.groovy
@@ -18,6 +18,7 @@ package skills
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Component
import skills.auth.AuthMode
import skills.auth.pki.PkiUserLookup
@@ -27,6 +28,10 @@ import javax.annotation.PostConstruct
@Slf4j
@Component
+@ConditionalOnProperty(
+ name = "skills.db.startup",
+ havingValue = "true",
+ matchIfMissing = true)
class HealthChecker {
@Value('#{securityConfig.authMode}}')
diff --git a/backend/src/main/java/skills/PublicProps.groovy b/service/src/main/java/skills/PublicProps.groovy
similarity index 100%
rename from backend/src/main/java/skills/PublicProps.groovy
rename to service/src/main/java/skills/PublicProps.groovy
diff --git a/backend/src/main/java/skills/SpringBootApp.java b/service/src/main/java/skills/SpringBootApp.java
similarity index 56%
rename from backend/src/main/java/skills/SpringBootApp.java
rename to service/src/main/java/skills/SpringBootApp.java
index 17163b34..91332fd8 100644
--- a/backend/src/main/java/skills/SpringBootApp.java
+++ b/service/src/main/java/skills/SpringBootApp.java
@@ -15,24 +15,32 @@
*/
package skills;
-import groovy.util.logging.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.AutoConfigurationImportSelector;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.ImportResource;
+import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.env.Environment;
+import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import skills.utils.SecretsUtil;
-import javax.annotation.PostConstruct;
import javax.net.ssl.HttpsURLConnection;
+import java.util.Set;
import java.util.TimeZone;
+@EnableAsync
@EnableScheduling
@EnableWebSecurity
-@SpringBootApplication
+@Import(SpringBootApp.SkillsAutoConfigurationImportSelector.class)
+@SpringBootApplication(exclude = { RedisRepositoriesAutoConfiguration.class })
@EnableJpaRepositories(basePackages = {"skills.storage.repos"})
public class SpringBootApp {
@@ -56,4 +64,23 @@ public static void main(String[] args) {
SpringApplication.run(SpringBootApp.class, args);
}
+
+ static final class SkillsAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
+ static final String REDIS = "redis";
+ static final String SESSION_STORE_PROP = "spring.session.store-type";
+ public static final String REDIS_REDIS_AUTO_CONFIGURATION = "org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration";
+
+ @Override
+ protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
+ Set exclusions = super.getExclusions(metadata, attributes);
+ Environment environment = getEnvironment();
+ // disable spring boot auto-config for Redis unless 'spring.session.store-type=redis' is configured
+ if (!StringUtils.equalsIgnoreCase(environment.getProperty(SESSION_STORE_PROP), REDIS)) {
+ exclusions.add(REDIS_REDIS_AUTO_CONFIGURATION);
+ } else {
+ log.info("Enabling Spring Boot RedisAutoConfiguration");
+ }
+ return exclusions;
+ }
+ }
}
diff --git a/backend/src/main/java/skills/TomcatConfig.groovy b/service/src/main/java/skills/TomcatConfig.groovy
similarity index 86%
rename from backend/src/main/java/skills/TomcatConfig.groovy
rename to service/src/main/java/skills/TomcatConfig.groovy
index 1f1c2bbc..dfc9bb14 100644
--- a/backend/src/main/java/skills/TomcatConfig.groovy
+++ b/service/src/main/java/skills/TomcatConfig.groovy
@@ -34,7 +34,12 @@ class TomcatConfig implements WebServerFactoryCustomizer ui = [:]
+ Map client = [:]
+
+ @PostConstruct
+ void init() {
+ if(client['loggingEnabled']) {
+ log.info("Client Logging Enabled: ${client}")
+ }
+ }
}
diff --git a/backend/src/main/java/skills/auth/AuthMode.groovy b/service/src/main/java/skills/auth/AuthMode.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/AuthMode.groovy
rename to service/src/main/java/skills/auth/AuthMode.groovy
diff --git a/backend/src/main/java/skills/auth/AuthUtils.groovy b/service/src/main/java/skills/auth/AuthUtils.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/AuthUtils.groovy
rename to service/src/main/java/skills/auth/AuthUtils.groovy
diff --git a/service/src/main/java/skills/auth/PortalWebSecurityHelper.groovy b/service/src/main/java/skills/auth/PortalWebSecurityHelper.groovy
new file mode 100644
index 00000000..18a70224
--- /dev/null
+++ b/service/src/main/java/skills/auth/PortalWebSecurityHelper.groovy
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.stereotype.Component
+import skills.storage.model.auth.RoleName
+
+@Component
+class PortalWebSecurityHelper {
+ HttpSecurity configureHttpSecurity(HttpSecurity http) {
+ http
+ .csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/", "/favicon.ico",
+ "/icons/**", "/static/**",
+ "/skilltree.ico",
+ "/error", "/oauth/**",
+ "/app/oAuthProviders", "/login*", "/login/**",
+ "/performLogin", "/createAccount",
+ "/createRootAccount", '/grantFirstRoot',
+ '/userExists/**', "/app/userInfo",
+ "/app/users/validExistingDashboardUserId/*", "/app/oAuthProviders",
+ "index.html", "/public/**",
+ "/skills-websocket/**", "/requestPasswordReset",
+ "/resetPassword/**", "/performPasswordReset").permitAll()
+ .antMatchers('/admin/**').hasAnyAuthority(RoleName.ROLE_PROJECT_ADMIN.name(), RoleName.ROLE_SUPER_DUPER_USER.name())
+ .antMatchers('/supervisor/**').hasAnyAuthority(RoleName.ROLE_SUPERVISOR.name(), RoleName.ROLE_SUPER_DUPER_USER.name())
+ .antMatchers('/root/isRoot').hasAnyAuthority(RoleName.values().collect {it.name()}.toArray(new String[0]))
+ .antMatchers('/root/**').hasRole('SUPER_DUPER_USER')
+ .anyRequest().authenticated()
+ http.headers().frameOptions().disable()
+
+ return http
+ }
+}
diff --git a/backend/src/main/java/skills/auth/SecurityConfiguration.groovy b/service/src/main/java/skills/auth/SecurityConfiguration.groovy
similarity index 76%
rename from backend/src/main/java/skills/auth/SecurityConfiguration.groovy
rename to service/src/main/java/skills/auth/SecurityConfiguration.groovy
index 3085a565..38d3e583 100644
--- a/backend/src/main/java/skills/auth/SecurityConfiguration.groovy
+++ b/service/src/main/java/skills/auth/SecurityConfiguration.groovy
@@ -15,23 +15,20 @@
*/
package skills.auth
+import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
-import org.springframework.context.annotation.Condition
-import org.springframework.context.annotation.ConditionContext
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
-import org.springframework.core.type.AnnotatedTypeMetadata
+import org.springframework.http.MediaType
import org.springframework.security.access.AccessDeniedException
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.AuthenticationException
-import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
@@ -41,7 +38,8 @@ import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextListener
-import skills.storage.model.auth.RoleName
+import skills.auth.util.AccessDeniedExplanation
+import skills.auth.util.AccessDeniedExplanationGenerator
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
@@ -56,6 +54,9 @@ class SecurityConfiguration {
@Value('${skills.authorization.authMode:#{T(skills.auth.AuthMode).DEFAULT_AUTH_MODE}}')
AuthMode authMode
+ @Autowired
+ ObjectMapper objectMapper
+
@Component
@Configuration
@Order(99)
@@ -79,6 +80,8 @@ class SecurityConfiguration {
@Autowired
UserDetailsService userDetailsService
+ AccessDeniedExplanationGenerator explanationGenerator = new AccessDeniedExplanationGenerator()
+
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").cors()
@@ -120,14 +123,21 @@ class SecurityConfiguration {
@Bean
AccessDeniedHandler accessDeniedHandler() {
- return new CustomHandler()
- }
-
- static class CustomHandler extends AccessDeniedHandlerImpl {
- @Override
- void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
- log.warn("Received AccessDeniedException for [${request.getRequestURI()}]", accessDeniedException)
- super.handle(request, response, accessDeniedException)
+ return new AccessDeniedHandlerImpl() {
+ @Override
+ void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
+ log.warn("Received AccessDeniedException for [${request.getRequestURI()}]", accessDeniedException)
+ super.handle(request, response, accessDeniedException)
+ AccessDeniedExplanation explanation = new AccessDeniedExplanationGenerator().generateExplanation(request.getServerName())
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN)
+ if(explanation) {
+ String asJson = objectMapper.writeValueAsString(explanation)
+ response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
+ response.setContentLength(asJson.bytes.length)
+ response.getWriter().print(asJson)
+ response.getWriter().flush()
+ }
+ }
}
}
}
diff --git a/backend/src/main/java/skills/auth/SecurityMode.groovy b/service/src/main/java/skills/auth/SecurityMode.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/SecurityMode.groovy
rename to service/src/main/java/skills/auth/SecurityMode.groovy
diff --git a/backend/src/main/java/skills/auth/SkillsAuthorizationException.groovy b/service/src/main/java/skills/auth/SkillsAuthorizationException.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/SkillsAuthorizationException.groovy
rename to service/src/main/java/skills/auth/SkillsAuthorizationException.groovy
diff --git a/backend/src/main/java/skills/auth/UserAuthService.groovy b/service/src/main/java/skills/auth/UserAuthService.groovy
similarity index 95%
rename from backend/src/main/java/skills/auth/UserAuthService.groovy
rename to service/src/main/java/skills/auth/UserAuthService.groovy
index 90f43644..88ae7114 100644
--- a/backend/src/main/java/skills/auth/UserAuthService.groovy
+++ b/service/src/main/java/skills/auth/UserAuthService.groovy
@@ -17,8 +17,9 @@ package skills.auth
import callStack.profiler.Profile
import groovy.util.logging.Slf4j
-import org.apache.commons.collections.CollectionUtils
+import org.apache.commons.collections4.CollectionUtils
import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Lazy
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.GrantedAuthority
@@ -55,6 +56,7 @@ class UserAuthService {
AccessSettingsStorageService accessSettingsStorageService
@Autowired
+ @Lazy
private AuthenticationManager authenticationManager
@Autowired
@@ -95,14 +97,8 @@ class UserAuthService {
}
@Transactional
- UserInfo createUser(UserInfo userInfo, boolean isSuperUser = false) {
+ UserInfo createUser(UserInfo userInfo) {
accessSettingsStorageService.createAppUser(userInfo, false)
-
- if (isSuperUser) {
- // super user gets assigned to Inception project
- inceptionProjectService.createInceptionAndAssignUser(userInfo)
- }
-
return loadByUserId(userInfo.username)
}
diff --git a/backend/src/main/java/skills/auth/UserInfo.groovy b/service/src/main/java/skills/auth/UserInfo.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/UserInfo.groovy
rename to service/src/main/java/skills/auth/UserInfo.groovy
diff --git a/backend/src/main/java/skills/auth/UserInfoService.groovy b/service/src/main/java/skills/auth/UserInfoService.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/UserInfoService.groovy
rename to service/src/main/java/skills/auth/UserInfoService.groovy
diff --git a/backend/src/main/java/skills/auth/UserSkillsGrantedAuthority.groovy b/service/src/main/java/skills/auth/UserSkillsGrantedAuthority.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/UserSkillsGrantedAuthority.groovy
rename to service/src/main/java/skills/auth/UserSkillsGrantedAuthority.groovy
diff --git a/backend/src/main/java/skills/auth/aop/AdminUsersOnlyWhenUserIdSupplied.java b/service/src/main/java/skills/auth/aop/AdminUsersOnlyWhenUserIdSupplied.java
similarity index 100%
rename from backend/src/main/java/skills/auth/aop/AdminUsersOnlyWhenUserIdSupplied.java
rename to service/src/main/java/skills/auth/aop/AdminUsersOnlyWhenUserIdSupplied.java
diff --git a/backend/src/main/java/skills/auth/aop/AuthorizationAspect.java b/service/src/main/java/skills/auth/aop/AuthorizationAspect.java
similarity index 100%
rename from backend/src/main/java/skills/auth/aop/AuthorizationAspect.java
rename to service/src/main/java/skills/auth/aop/AuthorizationAspect.java
diff --git a/backend/src/main/java/skills/auth/form/CreateAccountController.groovy b/service/src/main/java/skills/auth/form/CreateAccountController.groovy
similarity index 58%
rename from backend/src/main/java/skills/auth/form/CreateAccountController.groovy
rename to service/src/main/java/skills/auth/form/CreateAccountController.groovy
index c21a228c..0d35f635 100644
--- a/backend/src/main/java/skills/auth/form/CreateAccountController.groovy
+++ b/service/src/main/java/skills/auth/form/CreateAccountController.groovy
@@ -17,10 +17,13 @@ package skills.auth.form
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.autoconfigure.condition.ConditionOutcome
+import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition
import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.context.annotation.ConditionContext
import org.springframework.context.annotation.Conditional
import org.springframework.context.annotation.Configuration
-import org.springframework.http.MediaType
+import org.springframework.core.type.AnnotatedTypeMetadata
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
@@ -28,9 +31,11 @@ import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.*
import skills.PublicProps
import skills.auth.SecurityMode
+import skills.auth.UserAuthService
+import skills.auth.UserInfo
import skills.controller.PublicPropsBasedValidator
-import skills.storage.model.auth.RoleName
-import skills.storage.model.auth.UserRole
+import skills.controller.exceptions.SkillsValidator
+import skills.controller.result.model.OAuth2Provider
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@@ -42,7 +47,7 @@ import javax.servlet.http.HttpServletResponse
class CreateAccountController {
@Autowired
- skills.auth.UserAuthService userAuthService
+ UserAuthService userAuthService
@Autowired
PasswordEncoder passwordEncoder
@@ -58,27 +63,23 @@ class CreateAccountController {
@Conditional(SecurityMode.FormAuth)
@PutMapping("createAccount")
- void createAppUser(@RequestBody skills.auth.UserInfo userInfo, HttpServletResponse response) {
+ void createAppUser(@RequestBody UserInfo userInfo, HttpServletResponse response) {
String password = userInfo.password
- propsBasedValidator.validateMinStrLength(PublicProps.UiProp.minPasswordLength, "password", password)
- propsBasedValidator.validateMaxStrLength(PublicProps.UiProp.maxPasswordLength, "password", password)
-
- userInfo.password = passwordEncoder.encode(password)
- if (!userInfo.username) {
- userInfo.username = userInfo.email
- }
- if (!userInfo.nickname) {
- userInfo.nickname = "${userInfo.firstName} ${userInfo.lastName}"
- }
- userInfo.usernameForDisplay = userInfo.username
- userInfo = userAuthService.createUser(userInfo)
+ userInfo = createUser(userInfo)
userAuthService.autologin(userInfo, password)
}
@Conditional(SecurityMode.FormAuth)
@PutMapping("createRootAccount")
- void createRootUser(@RequestBody skills.auth.UserInfo userInfo, HttpServletResponse response) {
- skills.controller.exceptions.SkillsValidator.isTrue(!userAuthService.rootExists(), 'A root user already exists! Granting additional root privileges requires a root user to grant them!')
+ void createRootUser(@RequestBody UserInfo userInfo, HttpServletResponse response) {
+ SkillsValidator.isTrue(!userAuthService.rootExists(), 'A root user already exists! Granting additional root privileges requires a root user to grant them!')
+ String password = userInfo.password
+ userInfo = createUser(userInfo)
+ userAuthService.grantRoot(userInfo.username)
+ userAuthService.autologin(userInfo, password)
+ }
+
+ private UserInfo createUser(UserInfo userInfo) {
String password = userInfo.password
propsBasedValidator.validateMinStrLength(PublicProps.UiProp.minPasswordLength, "password", password)
propsBasedValidator.validateMaxStrLength(PublicProps.UiProp.maxPasswordLength, "password", password)
@@ -93,17 +94,14 @@ class CreateAccountController {
if (!userInfo.usernameForDisplay) {
userInfo.usernameForDisplay = userInfo.username
}
- userInfo.authorities = [new skills.auth.UserSkillsGrantedAuthority(new UserRole(
- roleName: RoleName.ROLE_SUPER_DUPER_USER
- ))]
- userAuthService.createUser(userInfo, true)
+ return userAuthService.createUser(userInfo)
}
@Conditional(SecurityMode.PkiAuth)
@RequestMapping(value = "/grantFirstRoot", method = [RequestMethod.POST, RequestMethod.PUT])
void grantFirstRoot(HttpServletRequest request) {
- skills.controller.exceptions.SkillsValidator.isTrue(!userAuthService.rootExists(), 'A root user already exists! Granting additional root privileges requires a root user to grant them!')
- skills.controller.exceptions.SkillsValidator.isNotNull(request.getUserPrincipal(), 'Granting the first root user is only available in PKI mode, but it looks like the request was not made by an authenticated account!')
+ SkillsValidator.isTrue(!userAuthService.rootExists(), 'A root user already exists! Granting additional root privileges requires a root user to grant them!')
+ SkillsValidator.isNotNull(request.getUserPrincipal(), 'Granting the first root user is only available in PKI mode, but it looks like the request was not made by an authenticated account!')
userAuthService.grantRoot(request.getUserPrincipal().name)
}
@@ -115,11 +113,11 @@ class CreateAccountController {
@Conditional(SecurityMode.FormAuth)
@GetMapping("/app/oAuthProviders")
- List getOAuthProviders() {
- List providers = []
- if (clientRegistrationRepository && oAuth2ProviderProperties) {
+ List getOAuthProviders() {
+ List providers = []
+ if (clientRegistrationRepository && oAuth2ProviderProperties?.registration) {
clientRegistrationRepository.iterator().each { ClientRegistration clientRegistration ->
- skills.controller.result.model.OAuth2Provider oAuth2Provider = oAuth2ProviderProperties.registration.get(clientRegistration.registrationId)
+ OAuth2Provider oAuth2Provider = oAuth2ProviderProperties.registration.get(clientRegistration.registrationId)
oAuth2Provider.registrationId = oAuth2Provider.registrationId ?: clientRegistration.registrationId
oAuth2Provider.clientName = oAuth2Provider.clientName ?: clientRegistration.clientName
providers.add(oAuth2Provider)
@@ -133,6 +131,33 @@ class CreateAccountController {
@ConfigurationProperties(prefix = "spring.security.oauth2.client")
@Conditional(SecurityMode.FormAuth)
static class OAuth2ProviderProperties {
- final Map registration = new HashMap<>()
+ final Map registration = new HashMap<>()
+ }
+
+ @Component
+ @Configuration
+ @Conditional(NoClientsConfiguredCondition)
+ /*
+ EmptyOauth2ClientRegistrationRepository will only be created if there are not OAuth2 clients configured (i.e.
+ when there are no security.oauth2.client.registration.XXX properties configured)
+ */
+ static class EmptyOauth2ClientRegistrationRepository implements ClientRegistrationRepository, Iterable {
+
+ @Override
+ Iterator iterator() {
+ return Collections.emptyIterator()
+ }
+
+ @Override
+ ClientRegistration findByRegistrationId(String registrationId) {
+ return null
+ }
+ }
+
+ static class NoClientsConfiguredCondition extends ClientsConfiguredCondition {
+ @Override
+ ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
+ return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata))
+ }
}
}
diff --git a/backend/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy b/service/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy
similarity index 70%
rename from backend/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy
rename to service/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy
index bff45c64..d7b6935c 100644
--- a/backend/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy
+++ b/service/src/main/java/skills/auth/form/FormSecurityConfiguration.groovy
@@ -15,29 +15,29 @@
*/
package skills.auth.form
+
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.context.annotation.Bean
-import org.springframework.context.annotation.Conditional
-import org.springframework.context.annotation.Configuration
-import org.springframework.context.annotation.Primary
+import org.springframework.context.annotation.*
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
-import org.springframework.security.access.AccessDeniedException
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.Authentication
-import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.password.PasswordEncoder
-import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
+import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler
+import org.springframework.session.web.http.CookieSerializer
+import org.springframework.session.web.http.DefaultCookieSerializer
import org.springframework.stereotype.Component
import skills.auth.PortalWebSecurityHelper
import skills.auth.SecurityConfiguration
@@ -54,6 +54,8 @@ import javax.servlet.http.HttpServletResponse
@Slf4j
class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
+ public static final String SKILLS_REDIRECT_URI = 'skillsRedirectUri'
+
@Autowired
private PortalWebSecurityHelper portalWebSecurityHelper
@@ -69,6 +71,12 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationSuccessHandler restAuthenticationSuccessHandler
+ @Autowired
+ private SkillsClientOAuth2AuthenticationSuccessHandler oauthAuthenticationSuccessHandler
+
+ @Autowired
+ private SkillsClientOAuth2AuthorizationRequestRepository skillsClientOAuth2AuthorizationRequestRepository
+
@Autowired
private RestLogoutSuccessHandler restLogoutSuccessHandler
@@ -101,18 +109,22 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
.loginProcessingUrl("/performLogin")
.successHandler(restAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
+ .and()
+ .logout()
+ .logoutSuccessHandler(restLogoutSuccessHandler)
.and()
.oauth2Login()
.loginPage("/skills-login")
+ .successHandler(oauthAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
- .and()
- .logout()
- .logoutSuccessHandler(restLogoutSuccessHandler)
+ .authorizationEndpoint()
+ .authorizationRequestRepository(skillsClientOAuth2AuthorizationRequestRepository)
}
@Override
@Bean(name = 'defaultAuthManager')
@Primary
+ @Lazy
AuthenticationManager authenticationManagerBean() throws Exception {
// provides the default AuthenticationManager as a Bean
return super.authenticationManagerBean()
@@ -133,6 +145,9 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
return [
google: new OAuth2UserConverterService.GoogleUserConverter(),
github: new OAuth2UserConverterService.GitHubUserConverter(),
+ gitlab: new OAuth2UserConverterService.GitLabUserConverter(),
+ auth0: new OAuth2UserConverterService.Auth0UserConverter(),
+ hydra: new OAuth2UserConverterService.HydraUserConverter(),
]
}
@@ -141,14 +156,9 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
return new SimpleUrlAuthenticationFailureHandler()
}
- @Component
- static class RestAccessDeniedHandler implements AccessDeniedHandler {
- @Override
- void handle(final HttpServletRequest request, final HttpServletResponse response, final AccessDeniedException ex) throws IOException, ServletException {
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- log.warn("Access Denied User [${authentication}], reqested resource [${request.getServletPath()}]")
- response.setStatus(HttpServletResponse.SC_FORBIDDEN)
- }
+ @Bean
+ CookieSerializer cookieSerializer() {
+ return new DefaultCookieSerializer(sameSite: 'None')
}
@Component
@@ -160,6 +170,19 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
}
}
+ @Component
+ static final class SkillsClientOAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+ @Override
+ protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
+ String targetUrl = request.getSession(false).getAttribute(SKILLS_REDIRECT_URI)
+ if (targetUrl) {
+ return targetUrl
+ } else {
+ return super.determineTargetUrl(request, response)
+ }
+ }
+ }
+
@Component
static final class RestLogoutSuccessHandler extends HttpStatusReturningLogoutSuccessHandler {
RestLogoutSuccessHandler() {
@@ -167,12 +190,27 @@ class FormSecurityConfiguration extends WebSecurityConfigurerAdapter {
}
@Override
- void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+ void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
writeNullJson(response)
super.onLogoutSuccess(request, response, authentication)
}
}
+ @Component
+ static final class SkillsClientOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository {
+ @Delegate
+ HttpSessionOAuth2AuthorizationRequestRepository httpSessionOAuth2AuthorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository()
+
+ @Override
+ void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
+ httpSessionOAuth2AuthorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response)
+ String skillsRedirectUri = request.getParameter(SKILLS_REDIRECT_URI)
+ if (skillsRedirectUri) {
+ request.getSession(false).setAttribute(SKILLS_REDIRECT_URI, skillsRedirectUri)
+ }
+ }
+ }
+
static final String NULL_JSON = 'null'
static writeNullJson(HttpServletResponse response) {
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
diff --git a/backend/src/main/java/skills/auth/form/LocalUserDetailsService.groovy b/service/src/main/java/skills/auth/form/LocalUserDetailsService.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/LocalUserDetailsService.groovy
rename to service/src/main/java/skills/auth/form/LocalUserDetailsService.groovy
diff --git a/service/src/main/java/skills/auth/form/PasswordReset.groovy b/service/src/main/java/skills/auth/form/PasswordReset.groovy
new file mode 100644
index 00000000..719da2a3
--- /dev/null
+++ b/service/src/main/java/skills/auth/form/PasswordReset.groovy
@@ -0,0 +1,22 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.form
+
+class PasswordReset {
+ String userId
+ String password
+ String resetToken
+}
diff --git a/service/src/main/java/skills/auth/form/PasswordResetController.groovy b/service/src/main/java/skills/auth/form/PasswordResetController.groovy
new file mode 100644
index 00000000..da139df1
--- /dev/null
+++ b/service/src/main/java/skills/auth/form/PasswordResetController.groovy
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.form
+
+import groovy.util.logging.Slf4j
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Conditional
+import org.springframework.context.annotation.Lazy
+import org.springframework.security.crypto.password.PasswordEncoder
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestPart
+import org.springframework.web.bind.annotation.RestController
+import skills.PublicProps
+import skills.auth.SecurityMode
+import skills.auth.UserAuthService
+import skills.auth.UserInfo
+import skills.controller.PublicPropsBasedValidator
+import skills.controller.exceptions.SkillException
+import skills.controller.result.model.RequestResult
+import skills.services.PasswordResetService
+import skills.storage.model.auth.PasswordResetToken
+import skills.storage.model.auth.User
+
+import javax.annotation.PostConstruct
+
+@Conditional(SecurityMode.FormAuth)
+@Slf4j
+@RestController()
+@RequestMapping("/")
+class PasswordResetController {
+
+ @Autowired
+ PasswordResetService resetService
+
+ @Autowired
+ UserAuthService userAuthService
+
+ @Autowired
+ PublicPropsBasedValidator propsBasedValidator
+
+ @Autowired
+ PasswordEncoder passwordEncoder
+
+ @PostMapping("resetPassword")
+ RequestResult requestPasswordReset(@RequestPart("userId") String userId) {
+ log.info("requesting password reset for [${userId}]")
+ User user = userAuthService.getUserRepository().findByUserId(userId)
+ if (!user) {
+ log.error("no user found for requested password reset")
+ throw new SkillException("No user found for id [${userId}]")
+ }
+ resetService.createTokenAndNotifyUser(user)
+ return RequestResult.success()
+ }
+
+ @PostMapping("performPasswordReset")
+ RequestResult resetPassword(@RequestBody PasswordReset reset) {
+ PasswordResetToken token = resetService.loadToken(reset.resetToken)
+ if (token?.getUser()?.getUserId() != reset.userId) {
+ throw new SkillException("Supplied reset token is not for the specified user")
+ }
+
+ if (!token.isValid()) {
+ throw new SkillException("Reset token has expired")
+ }
+
+ log.info("reseting password for user [${reset.userId}]")
+ propsBasedValidator.validateMinStrLength(PublicProps.UiProp.minPasswordLength, "new_password", reset.password)
+ propsBasedValidator.validateMaxStrLength(PublicProps.UiProp.maxPasswordLength, "new_password", reset.password)
+
+ UserInfo userInfo = userAuthService.loadByUserId(token.user.userId)
+ userInfo.password = passwordEncoder.encode(reset.password)
+ userAuthService.createOrUpdateUser(userInfo)
+ resetService.deleteToken(token.token)
+
+ return RequestResult.success()
+ }
+}
diff --git a/service/src/main/java/skills/auth/form/RestAccessDeniedHandler.groovy b/service/src/main/java/skills/auth/form/RestAccessDeniedHandler.groovy
new file mode 100644
index 00000000..4ac800f8
--- /dev/null
+++ b/service/src/main/java/skills/auth/form/RestAccessDeniedHandler.groovy
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.form
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import groovy.util.logging.Slf4j
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Conditional
+import org.springframework.http.MediaType
+import org.springframework.security.access.AccessDeniedException
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.web.access.AccessDeniedHandler
+import org.springframework.stereotype.Component
+import skills.auth.SecurityMode
+import skills.auth.util.AccessDeniedExplanation
+import skills.auth.util.AccessDeniedExplanationGenerator
+
+import javax.servlet.ServletException
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+@Conditional(SecurityMode.FormAuth)
+@Slf4j
+@Component
+class RestAccessDeniedHandler implements AccessDeniedHandler {
+
+ @Autowired
+ ObjectMapper om
+
+ @Override
+ void handle(final HttpServletRequest request, final HttpServletResponse response, final AccessDeniedException ex) throws IOException, ServletException {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ log.warn("Access Denied User [${authentication}], reqested resource [${request.getServletPath()}]")
+ AccessDeniedExplanation explanation = new AccessDeniedExplanationGenerator().generateExplanation(request.getServletPath())
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN)
+ if(explanation) {
+ String asJson = om.writeValueAsString(explanation)
+ response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
+ response.setContentLength(asJson.bytes.length)
+ response.getWriter().print(asJson)
+ response.getWriter().flush()
+ }
+ }
+}
diff --git a/backend/src/main/java/skills/auth/form/SkillsHttpSessionSecurityContextRepository.groovy b/service/src/main/java/skills/auth/form/SkillsHttpSessionSecurityContextRepository.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/SkillsHttpSessionSecurityContextRepository.groovy
rename to service/src/main/java/skills/auth/form/SkillsHttpSessionSecurityContextRepository.groovy
diff --git a/backend/src/main/java/skills/auth/form/jwt/JwtAuthenticationFilter.groovy b/service/src/main/java/skills/auth/form/jwt/JwtAuthenticationFilter.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/jwt/JwtAuthenticationFilter.groovy
rename to service/src/main/java/skills/auth/form/jwt/JwtAuthenticationFilter.groovy
diff --git a/backend/src/main/java/skills/auth/form/jwt/JwtAuthorizationFilter.groovy b/service/src/main/java/skills/auth/form/jwt/JwtAuthorizationFilter.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/jwt/JwtAuthorizationFilter.groovy
rename to service/src/main/java/skills/auth/form/jwt/JwtAuthorizationFilter.groovy
diff --git a/backend/src/main/java/skills/auth/form/jwt/JwtHelper.groovy b/service/src/main/java/skills/auth/form/jwt/JwtHelper.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/jwt/JwtHelper.groovy
rename to service/src/main/java/skills/auth/form/jwt/JwtHelper.groovy
diff --git a/backend/src/main/java/skills/auth/form/oauth2/AuthorizationServerConfig.groovy b/service/src/main/java/skills/auth/form/oauth2/AuthorizationServerConfig.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/oauth2/AuthorizationServerConfig.groovy
rename to service/src/main/java/skills/auth/form/oauth2/AuthorizationServerConfig.groovy
diff --git a/service/src/main/java/skills/auth/form/oauth2/OAuth2UserConverterService.groovy b/service/src/main/java/skills/auth/form/oauth2/OAuth2UserConverterService.groovy
new file mode 100644
index 00000000..39b9fad2
--- /dev/null
+++ b/service/src/main/java/skills/auth/form/oauth2/OAuth2UserConverterService.groovy
@@ -0,0 +1,209 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.form.oauth2
+
+import org.apache.commons.lang3.StringUtils
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Conditional
+import org.springframework.security.oauth2.core.user.OAuth2User
+import org.springframework.stereotype.Component
+import skills.auth.SecurityMode
+import skills.auth.SkillsAuthorizationException
+import skills.auth.UserAuthService
+import skills.auth.UserInfo
+
+import javax.annotation.Resource
+
+@Component
+@Conditional(SecurityMode.FormAuth)
+class OAuth2UserConverterService {
+
+ @Autowired
+ UserAuthService userAuthService
+
+ @Resource(name='oauth2UserConverters')
+ Map lookup = [:]
+
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ UserInfo userInfo
+ OAuth2UserConverter converter = lookup.get(providerId.toLowerCase())
+ if (converter) {
+ userInfo = converter.convert(providerId, oAuth2User)
+ if (!userInfo.usernameForDisplay) {
+ userInfo.usernameForDisplay = StringUtils.substringBeforeLast(userInfo.username, '-')
+ }
+ } else {
+ throw new SkillsAuthorizationException("No OAuth2UserConverter configured for providerId [${providerId}]")
+ }
+ return userInfo
+ }
+
+ static interface OAuth2UserConverter {
+ String getProviderId()
+ UserInfo convert(String providerId, OAuth2User oAuth2User)
+ }
+
+ static class GitHubUserConverter implements OAuth2UserConverter {
+ static final String NAME = 'name'
+ static final String EMAIL = 'email'
+ static final String LOGIN = 'login'
+
+ String providerId = 'github'
+
+ @Override
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ String username = oAuth2User.getName()
+ assert username, "Error getting name attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ String email = oAuth2User.attributes.get(EMAIL)
+ if (!email) {
+ throw new SkillsAuthorizationException("Email must be available in your public Github profile")
+ }
+ String name = oAuth2User.attributes.get(NAME)
+ if (!name) {
+ throw new SkillsAuthorizationException("Name must be available in your public Github profile")
+ }
+ String firstName = name?.tokenize()?.first()
+ List tokens = name?.tokenize()
+ tokens?.pop()
+ String lastName = tokens?.join(' ')
+ String login = oAuth2User.attributes.get(LOGIN)
+ return new UserInfo(
+ username: "${username}-${providerId}",
+ usernameForDisplay: login,
+ email:email,
+ firstName: firstName,
+ lastName: lastName,
+ )
+ }
+ }
+
+ static class GoogleUserConverter implements OAuth2UserConverter {
+ static final String FIRST_NAME = 'given_name'
+ static final String LAST_NAME = 'family_name'
+ static final String EMAIL = 'email'
+
+ String providerId = 'google'
+
+ @Override
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ String username = oAuth2User.getName()
+ assert username, "Error getting name attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ String firstName = oAuth2User.attributes.get(FIRST_NAME)
+ String lastName = oAuth2User.attributes.get(LAST_NAME)
+ String email = oAuth2User.attributes.get(EMAIL)
+ String usernameForDisplay = StringUtils.substringBeforeLast(email, '@')
+ assert firstName && lastName && email, "First Name [$firstName], Last Name [$lastName], and email [$email] must be available in your public Google profile"
+
+ return new UserInfo(
+ username: "${username}-${providerId}",
+ usernameForDisplay: usernameForDisplay,
+ email:email,
+ firstName: firstName,
+ lastName: lastName,
+ )
+ }
+ }
+
+ static class GitLabUserConverter implements OAuth2UserConverter {
+ static final String NAME = 'name'
+ static final String EMAIL = 'email'
+
+ String providerId = 'gitlab'
+
+ @Override
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ String username = oAuth2User.getName()
+ assert username, "Error getting name attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ String email = oAuth2User.attributes.get(EMAIL)
+ if (!email) {
+ throw new SkillsAuthorizationException("Email must be available in your public GitLab profile")
+ }
+ String name = oAuth2User.attributes.get(NAME)
+ if (!name) {
+ throw new SkillsAuthorizationException("Name must be available in your public GitLab profile")
+ }
+ String firstName = name?.tokenize()?.first()
+ List tokens = name?.tokenize()
+ tokens?.pop()
+ tokens?.remove(username)
+ String lastName = tokens?.join(' ')
+
+ return new UserInfo(
+ username: "${username}-${providerId}",
+ email:email,
+ firstName: firstName,
+ lastName: lastName,
+ )
+ }
+ }
+
+ static class Auth0UserConverter implements OAuth2UserConverter {
+ static final String USERNAME = 'nickname'
+ static final String NAME = 'name'
+ static final String EMAIL = 'email'
+
+ String providerId = 'auth0'
+
+ @Override
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ String username = oAuth2User.attributes.get(USERNAME)
+ assert username, "Error getting name attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ String email = oAuth2User.attributes.get(EMAIL)
+ if (!email) {
+ throw new SkillsAuthorizationException("Email must be available in your public GitLab profile")
+ }
+ String name = oAuth2User.attributes.get(NAME)
+ if (!name) {
+ throw new SkillsAuthorizationException("Name must be available in your public GitLab profile")
+ }
+ String firstName = name?.tokenize()?.first()
+ List tokens = name?.tokenize()
+ tokens?.pop()
+ tokens?.remove(username)
+ String lastName = tokens?.join(' ')
+
+ return new UserInfo(
+ username: "${username}-${providerId}",
+ email:email,
+ firstName: firstName,
+ lastName: lastName,
+ )
+ }
+ }
+
+ static class HydraUserConverter implements OAuth2UserConverter {
+ String providerId = 'hydra'
+ @Override
+ UserInfo convert(String providerId, OAuth2User oAuth2User) {
+ String email = oAuth2User.getName()
+ List tokens = email.tokenize('@')
+ String username = tokens.first()
+ String firstName = username;
+ String lastName = StringUtils.substringBeforeLast(tokens.last(), '.')
+ assert email, "Error getting email attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ assert username, "Error getting username attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ assert firstName, "Error getting firstName attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+ assert lastName, "Error getting lastName attribute of oAuth2User [${oAuth2User}] from providerId [$providerId]"
+
+ return new UserInfo(
+ username: "${username}-${providerId}",
+ email:email,
+ firstName: firstName,
+ lastName: lastName,
+ )
+ }
+ }
+}
diff --git a/backend/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy b/service/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy
similarity index 93%
rename from backend/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy
rename to service/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy
index 65a62610..401ff636 100644
--- a/backend/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy
+++ b/service/src/main/java/skills/auth/form/oauth2/OAuthUtils.groovy
@@ -33,8 +33,6 @@ import skills.auth.UserInfo
import javax.servlet.http.HttpServletRequest
import javax.transaction.Transactional
-import static AuthorizationServerConfig.SKILLS_PROXY_USER
-
@Component
@Conditional(SecurityMode.FormAuth)
@Slf4j
@@ -56,8 +54,8 @@ class OAuthUtils {
Authentication skillsAuth
OAuth2AuthenticationDetails oauthDetails = (OAuth2AuthenticationDetails) auth.getDetails()
Map claims = oauthDetails.getDecodedDetails()
- if (claims && claims.get(SKILLS_PROXY_USER)) {
- String proxyUserId = claims.get(SKILLS_PROXY_USER)
+ if (claims && claims.get(AuthorizationServerConfig.SKILLS_PROXY_USER)) {
+ String proxyUserId = claims.get(AuthorizationServerConfig.SKILLS_PROXY_USER)
log.info("Loading proxyUser [${proxyUserId}]")
UserInfo currentUser = new UserInfo(
username: proxyUserId,
@@ -67,7 +65,7 @@ class OAuthUtils {
// Create new Authentication using UserInfo
skillsAuth = new UsernamePasswordAuthenticationToken(currentUser, null, currentUser.authorities)
} else {
- throw new InvalidTokenException("client_credentials grant_type must specify $SKILLS_PROXY_USER field")
+ throw new InvalidTokenException("client_credentials grant_type must specify $AuthorizationServerConfig.SKILLS_PROXY_USER field")
}
return skillsAuth
}
@@ -85,7 +83,7 @@ class OAuthUtils {
currentUser = userAuthService.createOrUpdateUser(currentUser)
// Create new Authentication using UserInfo
- skillsAuth = new UsernamePasswordAuthenticationToken(currentUser, null, currentUser.authorities)
+ skillsAuth = new UsernamePasswordAuthenticationToken(currentUser, auth, currentUser.authorities)
}
return skillsAuth
}
diff --git a/backend/src/main/java/skills/auth/form/oauth2/ResourceServerConfig.groovy b/service/src/main/java/skills/auth/form/oauth2/ResourceServerConfig.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/form/oauth2/ResourceServerConfig.groovy
rename to service/src/main/java/skills/auth/form/oauth2/ResourceServerConfig.groovy
diff --git a/backend/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy b/service/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy
similarity index 91%
rename from backend/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy
rename to service/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy
index 9590f814..d69301ad 100644
--- a/backend/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy
+++ b/service/src/main/java/skills/auth/form/oauth2/SkillsOAuth2AuthenticationManager.groovy
@@ -27,11 +27,9 @@ import org.springframework.security.oauth2.provider.token.DefaultTokenServices
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
-import skills.WebSocketConfig
import skills.auth.SecurityMode
import skills.auth.UserInfo
-import javax.annotation.PostConstruct
import javax.servlet.http.HttpServletRequest
@Component('skillsOAuth2AuthManager')
@@ -42,19 +40,10 @@ class SkillsOAuth2AuthenticationManager extends OAuth2AuthenticationManager {
@Autowired
OAuthUtils oAuthUtils
- @Autowired
- WebSocketConfig webSocketConfig
-
SkillsOAuth2AuthenticationManager(DefaultTokenServices tokenServices) {
setTokenServices(tokenServices)
}
- @PostConstruct
- void postConstruct() {
- // inject into WebSocketConfig (@Autowired caused a circular reference)
- webSocketConfig.oAuth2AuthenticationManager = this
- }
-
@Override
Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication auth = super.authenticate(authentication)
diff --git a/backend/src/main/java/skills/auth/pki/HttpClientRestTemplateConfig.groovy b/service/src/main/java/skills/auth/pki/HttpClientRestTemplateConfig.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/pki/HttpClientRestTemplateConfig.groovy
rename to service/src/main/java/skills/auth/pki/HttpClientRestTemplateConfig.groovy
diff --git a/backend/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy b/service/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy
similarity index 99%
rename from backend/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy
rename to service/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy
index 4d766bd9..ad53e586 100644
--- a/backend/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy
+++ b/service/src/main/java/skills/auth/pki/PkiSecurityConfiguration.groovy
@@ -70,6 +70,7 @@ class PkiSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
@Bean(name = 'defaultAuthManager')
@Primary
+ @Lazy
AuthenticationManager authenticationManagerBean() throws Exception {
// provides the default AuthenticationManager as a Bean
return super.authenticationManagerBean()
diff --git a/backend/src/main/java/skills/auth/pki/PkiUserDetailsService.groovy b/service/src/main/java/skills/auth/pki/PkiUserDetailsService.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/pki/PkiUserDetailsService.groovy
rename to service/src/main/java/skills/auth/pki/PkiUserDetailsService.groovy
diff --git a/backend/src/main/java/skills/auth/pki/PkiUserLookup.groovy b/service/src/main/java/skills/auth/pki/PkiUserLookup.groovy
similarity index 100%
rename from backend/src/main/java/skills/auth/pki/PkiUserLookup.groovy
rename to service/src/main/java/skills/auth/pki/PkiUserLookup.groovy
diff --git a/service/src/main/java/skills/auth/util/AccessDeniedExplanation.groovy b/service/src/main/java/skills/auth/util/AccessDeniedExplanation.groovy
new file mode 100644
index 00000000..62fb3b59
--- /dev/null
+++ b/service/src/main/java/skills/auth/util/AccessDeniedExplanation.groovy
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.util
+
+class AccessDeniedExplanation {
+ String explanation;
+}
diff --git a/service/src/main/java/skills/auth/util/AccessDeniedExplanationGenerator.groovy b/service/src/main/java/skills/auth/util/AccessDeniedExplanationGenerator.groovy
new file mode 100644
index 00000000..87777a2e
--- /dev/null
+++ b/service/src/main/java/skills/auth/util/AccessDeniedExplanationGenerator.groovy
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.auth.util
+
+class AccessDeniedExplanationGenerator {
+
+ AccessDeniedExplanation generateExplanation(String requestPath) {
+ if (requestPath?.startsWith("/admin/projects")) {
+ return new AccessDeniedExplanation(explanation: "You do not have permission to view/manage this Project OR this Project does not exist")
+ } else if (requestPath?.startsWith("/supervisor/badges")) {
+ return new AccessDeniedExplanation(explanation: "You do not have permission to view/manage this Global Badge OR this Global Badge does not exist")
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/backend/src/main/java/skills/controller/AccessSettingsController.groovy b/service/src/main/java/skills/controller/AccessSettingsController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/AccessSettingsController.groovy
rename to service/src/main/java/skills/controller/AccessSettingsController.groovy
diff --git a/backend/src/main/java/skills/controller/AdminController.groovy b/service/src/main/java/skills/controller/AdminController.groovy
similarity index 95%
rename from backend/src/main/java/skills/controller/AdminController.groovy
rename to service/src/main/java/skills/controller/AdminController.groovy
index 7d12bff4..4acdc2b0 100644
--- a/backend/src/main/java/skills/controller/AdminController.groovy
+++ b/service/src/main/java/skills/controller/AdminController.groovy
@@ -109,8 +109,8 @@ class AdminController {
projectRequest.name = InputSanitizer.sanitize(projectRequest.name)
projectRequest.projectId = InputSanitizer.sanitize(projectRequest.projectId)
- projAdminService.saveProject(projectId, projectRequest)
- return new skills.controller.result.model.RequestResult(success: true)
+ projAdminService.saveProject(InputSanitizer.sanitize(projectId), projectRequest)
+ return new RequestResult(success: true)
}
@RequestMapping(value = "/projects/{id}", method = RequestMethod.DELETE)
@@ -168,34 +168,37 @@ class AdminController {
subjectRequest.iconClass = InputSanitizer.sanitize(subjectRequest.iconClass)
subjectRequest.helpUrl = InputSanitizer.sanitize(subjectRequest.helpUrl)
- subjAdminService.saveSubject(projectId, subjectId, subjectRequest)
+ subjAdminService.saveSubject(InputSanitizer.sanitize(projectId), subjectId, subjectRequest)
return new RequestResult(success: true)
}
- @RequestMapping(value = "/projects/{projectId}/subjectNameExists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/projects/{projectId}/subjectNameExists", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
boolean doesSubjectNameExist(@PathVariable("projectId") String projectId,
- @RequestParam(value = "subjectName", required = false) String subjectName) {
+ @RequestBody NameExistsRequest existsRequest) {
+ String subjectName = existsRequest.name
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(subjectName, "Subject Name")
- return subjAdminService.existsBySubjectName(projectId, subjectName)
+ return subjAdminService.existsBySubjectName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(subjectName))
}
- @RequestMapping(value = "/projects/{projectId}/badgeNameExists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/projects/{projectId}/badgeNameExists", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
boolean doesBadgeExist(@PathVariable("projectId") String projectId,
- @RequestParam(value = "badgeName", required = false) String badgeName) {
+ @RequestBody NameExistsRequest nameExistsRequest) {
+ String badgeName = nameExistsRequest.name
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(badgeName, "Badge Name")
- return badgeAdminService.existsByBadgeName(projectId, badgeName)
+ return badgeAdminService.existsByBadgeName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(badgeName))
}
- @RequestMapping(value = "/projects/{projectId}/skillNameExists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/projects/{projectId}/skillNameExists", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
boolean doesSkillNameExist(@PathVariable("projectId") String projectId,
- @RequestParam(value = "skillName", required = false) String skillName) {
+ @RequestBody NameExistsRequest existsRequest) {
+ String skillName = existsRequest.name
SkillsValidator.isNotBlank(projectId, "Project Id")
- SkillsValidator.isNotBlank(projectId, "Skill Name")
- return skillsAdminService.existsBySkillName(projectId, skillName)
+ SkillsValidator.isNotBlank(skillName, "Skill Name")
+ return skillsAdminService.existsBySkillName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(skillName))
}
/**
@@ -211,7 +214,7 @@ class AdminController {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(id, "Entity Id")
- return skillsAdminService.existsBySkillId(projectId, id)
+ return skillsAdminService.existsBySkillId(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(id))
}
@RequestMapping(value = "/projects/{projectId}/subjects/{subjectId}", method = RequestMethod.DELETE)
@@ -643,7 +646,7 @@ class AdminController {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(userId, "User Id", projectId)
- PageRequest pageRequest = new PageRequest(page - 1, limit, ascending ? ASC : DESC, orderBy)
+ PageRequest pageRequest = PageRequest.of(page - 1, limit, ascending ? ASC : DESC, orderBy)
return userAdminService.loadUserPerformedSkillsPage(projectId, userId?.toLowerCase(), query, pageRequest)
}
@@ -669,7 +672,7 @@ class AdminController {
@RequestParam Boolean ascending) {
SkillsValidator.isNotBlank(projectId, "Project Id")
- PageRequest pageRequest = new PageRequest(page - 1, limit, ascending ? ASC : DESC, orderBy)
+ PageRequest pageRequest = PageRequest.of(page - 1, limit, ascending ? ASC : DESC, orderBy)
return adminUsersService.loadUsersPage(projectId, query, pageRequest)
}
@@ -692,7 +695,7 @@ class AdminController {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(subjectId, "Subject Id", projectId)
- PageRequest pageRequest = new PageRequest(page - 1, limit, ascending ? ASC : DESC, orderBy)
+ PageRequest pageRequest = PageRequest.of(page - 1, limit, ascending ? ASC : DESC, orderBy)
List subjectSkills = getSkills(projectId, subjectId)
List skillIds = subjectSkills.collect { it.skillId }
return adminUsersService.loadUsersPage(projectId, skillIds, query, pageRequest)
@@ -710,7 +713,7 @@ class AdminController {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(skillId, "Skill Id", projectId)
- PageRequest pageRequest = new PageRequest(page - 1, limit, ascending ? ASC : DESC, orderBy)
+ PageRequest pageRequest = PageRequest.of(page - 1, limit, ascending ? ASC : DESC, orderBy)
return adminUsersService.loadUsersPage(projectId, Collections.singletonList(skillId), query, pageRequest)
}
@@ -726,7 +729,7 @@ class AdminController {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(badgeId, "Badge Id", projectId)
- PageRequest pageRequest = new PageRequest(page - 1, limit, ascending ? ASC : DESC, orderBy)
+ PageRequest pageRequest = PageRequest.of(page - 1, limit, ascending ? ASC : DESC, orderBy)
List badgeSkills = getBadgeSkills(projectId, badgeId)
List skillIds = badgeSkills.collect { it.skillId }
if (!skillIds) {
@@ -856,7 +859,7 @@ class AdminController {
@PathVariable("skillId") String skillId) {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(skillId, "Skill Id")
- return globalBadgesService.isSkillUsedInGlobalBadge(projectId, skillId)
+ return globalBadgesService.isSkillUsedInGlobalBadge(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(skillId))
}
@RequestMapping(value = "/projects/{projectId}/subjects/{subjectId}/globalBadge/exists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@@ -865,14 +868,14 @@ class AdminController {
@PathVariable("subjectId") String subjectId) {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(subjectId, "Subject Id")
- return globalBadgesService.isSubjectUsedInGlobalBadge(projectId, subjectId)
+ return globalBadgesService.isSubjectUsedInGlobalBadge(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(subjectId))
}
@RequestMapping(value = "/projects/{projectId}/globalBadge/exists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
boolean isProjectReferencedByGlobalBadge(@PathVariable("projectId") String projectId) {
SkillsValidator.isNotBlank(projectId, "Project Id")
- return globalBadgesService.isProjectUsedInGlobalBadge(projectId)
+ return globalBadgesService.isProjectUsedInGlobalBadge(InputSanitizer.sanitize(projectId))
}
@RequestMapping(value = "/projects/{projectId}/levels/{level}/globalBadge/exists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@@ -881,6 +884,6 @@ class AdminController {
@PathVariable("level") Integer level) {
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotNull(level, "Level")
- return globalBadgesService.isProjectLevelUsedInGlobalBadge(projectId, level)
+ return globalBadgesService.isProjectLevelUsedInGlobalBadge(InputSanitizer.sanitize(projectId), level)
}
}
diff --git a/backend/src/main/java/skills/controller/AdminMetricsController.groovy b/service/src/main/java/skills/controller/AdminMetricsController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/AdminMetricsController.groovy
rename to service/src/main/java/skills/controller/AdminMetricsController.groovy
diff --git a/backend/src/main/java/skills/controller/AuthenticatedSettingsController.groovy b/service/src/main/java/skills/controller/AuthenticatedSettingsController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/AuthenticatedSettingsController.groovy
rename to service/src/main/java/skills/controller/AuthenticatedSettingsController.groovy
diff --git a/service/src/main/java/skills/controller/ClientLoggingController.groovy b/service/src/main/java/skills/controller/ClientLoggingController.groovy
new file mode 100644
index 00000000..bd55fee7
--- /dev/null
+++ b/service/src/main/java/skills/controller/ClientLoggingController.groovy
@@ -0,0 +1,75 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller
+
+import groovy.transform.ToString
+import groovy.util.logging.Slf4j
+import org.springframework.web.bind.annotation.*
+import skills.profile.EnableCallStackProf
+
+@RestController
+@RequestMapping("/public")
+@Slf4j
+@EnableCallStackProf
+class ClientLoggingController {
+ // Predefined logging levels.
+// Logger.TRACE = defineLogLevel(1, 'TRACE');
+// Logger.DEBUG = defineLogLevel(2, 'DEBUG');
+// Logger.INFO = defineLogLevel(3, 'INFO');
+// Logger.TIME = defineLogLevel(4, 'TIME');
+// Logger.WARN = defineLogLevel(5, 'WARN');
+// Logger.ERROR = defineLogLevel(8, 'ERROR');
+// Logger.OFF = defineLogLevel(99, 'OFF');
+
+ @CrossOrigin
+ @RequestMapping(value = "/log", method = [RequestMethod.PUT, RequestMethod.POST])
+ @ResponseBody
+ boolean writeLog(@RequestBody LogMessage logMessage) {
+ switch (logMessage.level.value) {
+ case 1:
+ log.trace(logMessage.message)
+ break
+ case 2:
+ log.debug(logMessage.message)
+ break
+ case 3:
+ log.info(logMessage.message)
+ break
+ case 5:
+ log.warn(logMessage.message)
+ break
+ case 8:
+ log.error(logMessage.message)
+ break
+ default:
+ throw new IllegalArgumentException("Unexpected log level [${logMessage}]")
+ }
+ return true
+ }
+
+ @ToString
+ static class LogMessage {
+ // {"message":"We are initialized!","level":{"value":3,"name":"INFO"}}
+ String message
+ LogLevel level
+ }
+
+ @ToString
+ static class LogLevel {
+ String name
+ Integer value
+ }
+}
diff --git a/backend/src/main/java/skills/controller/CustomIconAdminController.groovy b/service/src/main/java/skills/controller/CustomIconAdminController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/CustomIconAdminController.groovy
rename to service/src/main/java/skills/controller/CustomIconAdminController.groovy
diff --git a/backend/src/main/java/skills/controller/CustomValidationController.groovy b/service/src/main/java/skills/controller/CustomValidationController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/CustomValidationController.groovy
rename to service/src/main/java/skills/controller/CustomValidationController.groovy
diff --git a/backend/src/main/java/skills/controller/BootstrapController.groovy b/service/src/main/java/skills/controller/FeatureVerificationController.groovy
similarity index 55%
rename from backend/src/main/java/skills/controller/BootstrapController.groovy
rename to service/src/main/java/skills/controller/FeatureVerificationController.groovy
index 94bb111c..438882fe 100644
--- a/backend/src/main/java/skills/controller/BootstrapController.groovy
+++ b/service/src/main/java/skills/controller/FeatureVerificationController.groovy
@@ -17,30 +17,30 @@ package skills.controller
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.ui.ModelMap
import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
-import org.springframework.web.servlet.ModelAndView
-import skills.services.AccessSettingsStorageService
+import skills.profile.EnableCallStackProf
+import skills.services.FeatureService
@RestController
+@RequestMapping("/public")
@Slf4j
-@skills.profile.EnableCallStackProf
-class BootstrapController {
+@EnableCallStackProf
+class FeatureVerificationController {
@Autowired
- AccessSettingsStorageService accessSettingsStorageService
+ FeatureService featureService
- @GetMapping('/')
- ModelAndView handleBootstrap(ModelMap model) {
- if (!accessSettingsStorageService.rootAdminExists()) {
- return new ModelAndView('redirect:/bootstrap/index.html', model)
+ @GetMapping("/isFeatureSupported")
+ public boolean isFeatureSupported(@RequestParam("feature") String feature) {
+ if ("passwordreset" == feature?.toLowerCase()) {
+ return featureService.isPasswordResetFeatureEnabled()
+ } else if (feature) {
+ log.warn("Unrecognized feature requested [${feature}]")
}
- return new ModelAndView('/index.html', model)
- }
- @GetMapping('/bootstrap')
- ModelAndView handleBootstrapPart2(ModelMap modelMap) {
- return new ModelAndView('redirect:/bootstrap/index.html', modelMap)
+ return false
}
}
diff --git a/backend/src/main/java/skills/controller/GlobalMetricsController.groovy b/service/src/main/java/skills/controller/GlobalMetricsController.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/GlobalMetricsController.groovy
rename to service/src/main/java/skills/controller/GlobalMetricsController.groovy
diff --git a/backend/src/main/java/skills/controller/ProjectController.groovy b/service/src/main/java/skills/controller/ProjectController.groovy
similarity index 85%
rename from backend/src/main/java/skills/controller/ProjectController.groovy
rename to service/src/main/java/skills/controller/ProjectController.groovy
index 311c3382..b1ffd816 100644
--- a/backend/src/main/java/skills/controller/ProjectController.groovy
+++ b/service/src/main/java/skills/controller/ProjectController.groovy
@@ -24,6 +24,7 @@ import skills.auth.UserInfoService
import skills.controller.exceptions.ErrorCode
import skills.controller.exceptions.SkillException
import skills.controller.exceptions.SkillsValidator
+import skills.controller.request.model.ProjectExistsRequest
import skills.controller.request.model.ProjectRequest
import skills.controller.result.model.CustomIconResult
import skills.controller.result.model.ProjectResult
@@ -34,6 +35,7 @@ import skills.services.IdFormatValidator
import skills.services.admin.ProjAdminService
import skills.services.admin.ShareSkillsService
import skills.services.admin.SkillsAdminService
+import skills.utils.InputSanitizer
import java.nio.charset.StandardCharsets
@@ -99,22 +101,34 @@ class ProjectController {
throw new SkillException("Project name was not provided.", projectId, null, ErrorCode.BadParam)
}
+ projectRequest.projectId = InputSanitizer.sanitize(projectRequest.projectId)
+ projectRequest.name = InputSanitizer.sanitize(projectRequest.name)
+
projAdminService.saveProject(null, projectRequest)
return new RequestResult(success: true)
}
- @RequestMapping(value = "/projectExist", method = RequestMethod.GET, produces = "application/json")
+ //need pin/unpin controller. Only permit if:
+ /*
+ boolean isRoot = userInfo.authorities?.find() {
+ it instanceof UserSkillsGrantedAuthority && RoleName.ROLE_SUPER_DUPER_USER == it.role?.roleName
+ }
+ */
+
+ @RequestMapping(value = "/projectExist", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
- boolean doesProjectExist(@RequestParam(value = "projectId", required = false) String projectId,
- @RequestParam(value = "projectName", required = false) String projectName) {
+ boolean doesProjectExist(@RequestBody ProjectExistsRequest existsRequest) {
+ String projectId = existsRequest.projectId
+ String projectName = existsRequest.name
+
SkillsValidator.isTrue((projectId || projectName), "One of Project Id or Project Name must be provided.")
SkillsValidator.isTrue(!(projectId && projectName), "Only Project Id or Project Name may be provided, not both.")
if (projectId) {
- return projAdminService.existsByProjectId(projectId)
+ return projAdminService.existsByProjectId(InputSanitizer.sanitize(projectId))
}
- return projAdminService.existsByProjectName(projectName)
+ return projAdminService.existsByProjectName(InputSanitizer.sanitize(projectName))
}
@RequestMapping(value = "/projects/{id}/customIcons", method = RequestMethod.GET, produces = "application/json")
diff --git a/service/src/main/java/skills/controller/PublicConfigController.groovy b/service/src/main/java/skills/controller/PublicConfigController.groovy
new file mode 100644
index 00000000..333b0265
--- /dev/null
+++ b/service/src/main/java/skills/controller/PublicConfigController.groovy
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller
+
+import groovy.util.logging.Slf4j
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
+import org.springframework.web.bind.annotation.*
+import skills.HealthChecker
+import skills.UIConfigProperties
+import skills.auth.AuthMode
+import skills.controller.result.model.SettingsResult
+import skills.profile.EnableCallStackProf
+import skills.services.AccessSettingsStorageService
+import skills.services.SystemSettingsService
+import skills.services.settings.Settings
+import skills.services.settings.SettingsService
+
+@RestController
+@RequestMapping("/public")
+@Slf4j
+@EnableCallStackProf
+class PublicConfigController {
+
+ @Autowired
+ HealthChecker healthChecker
+
+ @Autowired
+ UIConfigProperties uiConfigProperties
+
+ @Autowired
+ AccessSettingsStorageService accessSettingsStorageService
+
+ @Value('${skills.authorization.authMode:#{T(skills.auth.AuthMode).DEFAULT_AUTH_MODE}}')
+ AuthMode authMode
+
+ @Autowired
+ SettingsService settingsService
+
+ @Autowired
+ private ClientRegistrationRepository clientRegistrationRepository;
+
+ @RequestMapping(value = "/config", method = RequestMethod.GET, produces = "application/json")
+ @ResponseBody
+ Map getConfig(){
+ Map res = new HashMap<>(uiConfigProperties.ui)
+ res["authMode"] = authMode.name()
+ res["needToBootstrap"] = !accessSettingsStorageService.rootAdminExists()
+ List customizationSettings = settingsService.getGlobalSettingsByGroup(SystemSettingsService.CUSTOMIZATION)
+ customizationSettings?.each {
+ if (Settings.GLOBAL_CUSTOM_HEADER.settingName == it.setting) {
+ res["customHeader"] = it.value
+ } else if (Settings.GLOBAL_CUSTOM_FOOTER.settingName == it.setting) {
+ res["customFooter"] = it.value
+ }
+ }
+ return res
+ }
+
+ final private static Map statusRes = [
+ status: "OK",
+ ]
+
+ @CrossOrigin
+ @RequestMapping(value = "/status", method = RequestMethod.GET, produces = "application/json")
+ @ResponseBody
+ def status() {
+ healthChecker.checkRequiredServices()
+ Map res = new HashMap<>(statusRes)
+ res['clientLib'] = uiConfigProperties.client
+ def oAuthProviders = clientRegistrationRepository?.collect {it?.registrationId }
+ if (oAuthProviders) {
+ res['oAuthProviders'] = oAuthProviders
+ }
+ return res
+ }
+}
diff --git a/backend/src/main/java/skills/controller/PublicPropsBasedValidator.groovy b/service/src/main/java/skills/controller/PublicPropsBasedValidator.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/PublicPropsBasedValidator.groovy
rename to service/src/main/java/skills/controller/PublicPropsBasedValidator.groovy
diff --git a/backend/src/main/java/skills/controller/RootController.groovy b/service/src/main/java/skills/controller/RootController.groovy
similarity index 77%
rename from backend/src/main/java/skills/controller/RootController.groovy
rename to service/src/main/java/skills/controller/RootController.groovy
index 014f0f1c..6518a330 100644
--- a/backend/src/main/java/skills/controller/RootController.groovy
+++ b/service/src/main/java/skills/controller/RootController.groovy
@@ -29,17 +29,27 @@ import skills.controller.exceptions.SkillException
import skills.controller.exceptions.SkillsValidator
import skills.controller.request.model.GlobalSettingsRequest
import skills.controller.request.model.SuggestRequest
+import skills.controller.result.model.ProjectResult
import skills.controller.result.model.RequestResult
+import skills.controller.result.model.SettingsResult
import skills.controller.result.model.UserInfoRes
import skills.controller.result.model.UserRoleRes
import skills.profile.EnableCallStackProf
import skills.services.AccessSettingsStorageService
+import skills.services.SystemSettingsService
+import skills.services.admin.ProjAdminService
+import skills.services.settings.Settings
import skills.services.settings.SettingsService
+import skills.settings.EmailConfigurationResult
import skills.settings.EmailConnectionInfo
import skills.settings.EmailSettingsService
+import skills.settings.SystemSettings
+import skills.storage.model.Setting
import skills.storage.model.auth.RoleName
import java.security.Principal
+import java.time.Duration
+import java.time.format.DateTimeParseException
@RestController
@RequestMapping('/root')
@@ -65,6 +75,12 @@ class RootController {
@Autowired
SettingsService settingsService
+ @Autowired
+ SystemSettingsService systemSettingsService
+
+ @Autowired
+ ProjAdminService projAdminService
+
@GetMapping('/rootUsers')
@ResponseBody
List getRootUsers() {
@@ -73,7 +89,8 @@ class RootController {
@PostMapping('/users/')
@ResponseBody
- List getNonRootUsers(@RequestBody SuggestRequest suggestRequest) {
+ List getNonRootUsers(@RequestBody SuggestRequest suggestRequest,
+ @RequestParam(required = false, value = "userSuggestOption") String userSuggestOption) {
String query = suggestRequest.suggestQuery.toLowerCase()
if (authMode == AuthMode.FORM) {
boolean emptyQuery = StringUtils.isBlank(query)
@@ -84,7 +101,7 @@ class RootController {
}.unique()
} else {
List rootUsers = accessSettingsStorageService.rootUsers.collect { it.userId.toLowerCase() }
- return pkiUserLookup?.suggestUsers(query)?.findAll {
+ return pkiUserLookup?.suggestUsers(query, userSuggestOption)?.findAll {
!rootUsers.contains(it.username.toLowerCase())
}?.unique()?.take(5)?.collect { new UserInfoRes(it) }
}
@@ -158,6 +175,7 @@ class RootController {
if (roleName == RoleName.ROLE_SUPER_DUPER_USER) {
deleteRoot(userId)
} else {
+ userId = getUserId(userId)
accessSettingsStorageService.deleteUserRole(userId, null, roleName)
}
}
@@ -168,8 +186,25 @@ class RootController {
}
@PostMapping('/saveEmailSettings')
- void saveEmailSettings(@RequestBody EmailConnectionInfo emailConnectionInfo) {
- emailSettingsService.updateConnectionInfo(emailConnectionInfo)
+ RequestResult saveEmailSettings(@RequestBody EmailConnectionInfo emailConnectionInfo) {
+ EmailConfigurationResult success = emailSettingsService.updateConnectionInfo(emailConnectionInfo)
+ return new RequestResult(success: success?.configurationSuccessful, explanation: success?.explanation)
+ }
+
+ @GetMapping('/getEmailSettings')
+ EmailConnectionInfo fetchEmailSettings(){
+ emailSettingsService.fetchEmailSettings()
+ }
+
+ @PostMapping('/saveSystemSettings')
+ RequestResult saveSystemSettings(@RequestBody SystemSettings settings){
+ systemSettingsService.save(settings)
+ return RequestResult.success()
+ }
+
+ @GetMapping('/getSystemSettings')
+ SystemSettings getSystemSettings(){
+ return systemSettingsService.get()
}
@RequestMapping(value = "/global/settings/{setting}", method = [RequestMethod.PUT, RequestMethod.POST], produces = MediaType.APPLICATION_JSON_VALUE)
@@ -181,6 +216,30 @@ class RootController {
return new RequestResult(success: true)
}
+ @GetMapping('/projects')
+ List getAllProjects() {
+ return projAdminService.getAllProjects()
+ }
+
+ @GetMapping('/searchProjects')
+ List searchProjects(@RequestParam(required = true, name = "name") String nameSearch) {
+ return projAdminService.searchByProjectName(nameSearch)
+ }
+
+ @PostMapping('/pin/{projectId}')
+ RequestResult pinProject(@PathVariable("projectId") String projectId) {
+ projAdminService.pinProjectForRootUser(projectId)
+ return new RequestResult(success: true)
+ }
+
+ @DeleteMapping('/pin/{projectId}')
+ RequestResult unpinProject(@PathVariable("projectId") String projectId) {
+ projAdminService.unpinProjectForRootUser(projectId)
+ return new RequestResult(success: true)
+ }
+
+
+
private String getUserId(String userKey) {
// userKey will be the userId when in FORM authMode, or the DN when in PKI auth mode.
// When in PKI auth mode, the userDetailsService implementation will create the user
diff --git a/backend/src/main/java/skills/controller/SupervisorController.groovy b/service/src/main/java/skills/controller/SupervisorController.groovy
similarity index 95%
rename from backend/src/main/java/skills/controller/SupervisorController.groovy
rename to service/src/main/java/skills/controller/SupervisorController.groovy
index 1854f5e0..d3c23454 100644
--- a/backend/src/main/java/skills/controller/SupervisorController.groovy
+++ b/service/src/main/java/skills/controller/SupervisorController.groovy
@@ -28,6 +28,7 @@ import skills.controller.exceptions.MaxIconSizeExceeded
import skills.controller.exceptions.SkillsValidator
import skills.controller.request.model.ActionPatchRequest
import skills.controller.request.model.BadgeRequest
+import skills.controller.request.model.NameExistsRequest
import skills.controller.result.model.*
import skills.icons.CustomIconFacade
import skills.icons.UploadedIcon
@@ -62,11 +63,12 @@ class SupervisorController {
@Autowired
PublicPropsBasedValidator propsBasedValidator
- @RequestMapping(value = "/badges/name/{badgeName}/exists", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @RequestMapping(value = "/badges/name/exists", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
- boolean doesBadgeNameExist(@PathVariable("badgeName") String badgeName) {
+ boolean doesBadgeNameExist(@RequestBody() NameExistsRequest nameExistsRequest) {
+ String badgeName = nameExistsRequest.name
SkillsValidator.isNotBlank(badgeName, "Badge Name")
- String decodedName = URLDecoder.decode(badgeName, StandardCharsets.UTF_8.toString())
+ String decodedName = InputSanitizer.sanitize(badgeName)
return globalBadgesService.existsByBadgeName(decodedName)
}
@@ -74,7 +76,7 @@ class SupervisorController {
@ResponseBody
boolean doesBadgeIdExist(@PathVariable("badgeId") String badgeId) {
SkillsValidator.isNotBlank(badgeId, "Badge Id")
- String decodedId = URLDecoder.decode(badgeId, StandardCharsets.UTF_8.toString())
+ String decodedId = InputSanitizer.sanitize(URLDecoder.decode(badgeId, StandardCharsets.UTF_8.toString()))
return globalBadgesService.existsByBadgeId(decodedId)
}
diff --git a/backend/src/main/java/skills/controller/UserInfoController.groovy b/service/src/main/java/skills/controller/UserInfoController.groovy
similarity index 97%
rename from backend/src/main/java/skills/controller/UserInfoController.groovy
rename to service/src/main/java/skills/controller/UserInfoController.groovy
index adc64d27..4faa7f62 100644
--- a/backend/src/main/java/skills/controller/UserInfoController.groovy
+++ b/service/src/main/java/skills/controller/UserInfoController.groovy
@@ -132,12 +132,12 @@ class UserInfoController {
@RequestMapping(value = "/users/projects/{projectId}/suggestClientUsers", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
List suggestExistingClientUsersForProject(@PathVariable("projectId") String projectId, @RequestBody SuggestRequest suggestRequest) {
- return userAdminService.suggestUsersForProject(projectId, suggestRequest.suggestQuery, new PageRequest(0, 5)).collect { new UserInfoRes(userId: it) }
+ return userAdminService.suggestUsersForProject(projectId, suggestRequest.suggestQuery, PageRequest.of(0, 5)).collect { new UserInfoRes(userId: it) }
}
@RequestMapping(value = "/users/suggestClientUsers/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
List suggestExistingClientUsers(@RequestBody SuggestRequest suggestRequest) {
- return userAdminService.suggestUsers(suggestRequest.suggestQuery, new PageRequest(0, 5)).collect { new UserInfoRes(userId: it) }
+ return userAdminService.suggestUsers(suggestRequest.suggestQuery, PageRequest.of(0, 5)).collect { new UserInfoRes(userId: it) }
}
@RequestMapping(value = "/users/projects/{projectId}/validExistingClientUserId/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
diff --git a/backend/src/main/java/skills/controller/UserSkillsController.java b/service/src/main/java/skills/controller/UserSkillsController.java
similarity index 93%
rename from backend/src/main/java/skills/controller/UserSkillsController.java
rename to service/src/main/java/skills/controller/UserSkillsController.java
index 78b36ae0..791bec09 100644
--- a/backend/src/main/java/skills/controller/UserSkillsController.java
+++ b/service/src/main/java/skills/controller/UserSkillsController.java
@@ -42,6 +42,8 @@
import skills.utils.RetryUtil;
import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -76,6 +78,9 @@ class UserSkillsController {
@Autowired
private PublicProps publicProps;
+ @Autowired
+ private SkillEventsService skillEventsService;
+
private int getProvidedVersionOrReturnDefault(Integer versionParam) {
if (versionParam != null) {
return versionParam;
@@ -111,14 +116,24 @@ public Integer getUserLevel(@PathVariable(name = "projectId") String projectId,
@ResponseBody
@CompileStatic
@Profile
- public OverallSkillSummary getSkillsSummary(@PathVariable("projectId") String projectId,
+ public OverallSkillSummary getSkillsSummary(HttpServletRequest request,
+ @PathVariable("projectId") String projectId,
@RequestParam(name = "userId", required = false) String userIdParam,
@RequestParam(name = "version", required = false) Integer version,
@RequestParam(name = "idType", required = false) String idType) {
String userId = userInfoService.getUserName(userIdParam, true, idType);
+
+ log.debug("userId is {} and userIdParam is {}", userId, userIdParam);
return skillsLoader.loadOverallSummary(projectId, userId, getProvidedVersionOrReturnDefault(version));
}
+ private boolean isRequestFromDashboard(HttpServletRequest request) throws UnknownHostException{
+ InetAddress requestorIp = InetAddress.getByName(request.getRemoteAddr());
+ log.debug("remote port: [{}], local port: [{}]", request.getRemotePort(), request.getLocalPort());
+ return (requestorIp.isAnyLocalAddress() || requestorIp.isLoopbackAddress())
+ && (request.getRemotePort() == request.getLocalPort());
+ }
+
@RequestMapping(value = "/projects/{projectId}/subjects/{subjectId}/summary", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@CompileStatic
@@ -136,8 +151,10 @@ public SkillSubjectSummary getSubjectSummary(@PathVariable("projectId") String p
@CompileStatic
public List getSubjectSkillsDescriptions(@PathVariable("projectId") String projectId,
@PathVariable("subjectId") String subjectId,
+ @RequestParam(name = "userId", required = false) String userIdParam,
@RequestParam(name = "version", required = false) Integer version) {
- return skillsLoader.loadSubjectDescriptions(projectId, subjectId, getProvidedVersionOrReturnDefault(version));
+ String userId = userInfoService.getUserName(userIdParam, true);
+ return skillsLoader.loadSubjectDescriptions(projectId, subjectId, userId, getProvidedVersionOrReturnDefault(version));
}
/**
@@ -192,11 +209,13 @@ public List getAllBadgesSummary(@PathVariable("projectId") St
public List getBadgeSkillsDescriptions(@PathVariable("projectId") String projectId,
@PathVariable("badgeId") String badgeId,
@RequestParam(name = "version", required = false) Integer version,
+ @RequestParam(name = "userId", required = false) String userIdParam,
@RequestParam(name = "global", required = false) Boolean isGlobal) {
+ String userId = userInfoService.getUserName(userIdParam, true);
if (isGlobal != null && isGlobal) {
- return skillsLoader.loadGlobalBadgeDescriptions(badgeId, getProvidedVersionOrReturnDefault(version));
+ return skillsLoader.loadGlobalBadgeDescriptions(badgeId, userId, getProvidedVersionOrReturnDefault(version));
} else {
- return skillsLoader.loadBadgeDescriptions(projectId, badgeId, getProvidedVersionOrReturnDefault(version));
+ return skillsLoader.loadBadgeDescriptions(projectId, badgeId, userId, getProvidedVersionOrReturnDefault(version));
}
}
@@ -271,8 +290,6 @@ public SkillEventResult addSkill(@PathVariable("projectId") String projectId,
//let's account for some possible clock drift
SkillsValidator.isTrue(requestedTimestamp <= (System.currentTimeMillis() + 30000), "Skill Events may not be in the future", projectId, skillId);
incomingDate = new Date(requestedTimestamp);
- } else {
- incomingDate = new Date();
}
SkillEventResult result;
diff --git a/service/src/main/java/skills/controller/UserTokenController.groovy b/service/src/main/java/skills/controller/UserTokenController.groovy
new file mode 100644
index 00000000..22d7f5ec
--- /dev/null
+++ b/service/src/main/java/skills/controller/UserTokenController.groovy
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller
+
+import groovy.util.logging.Slf4j
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Conditional
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken
+import org.springframework.security.oauth2.common.OAuth2AccessToken
+import org.springframework.security.oauth2.provider.OAuth2Authentication
+import org.springframework.security.oauth2.provider.OAuth2Request
+import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
+import org.springframework.security.oauth2.provider.token.DefaultTokenServices
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
+import org.springframework.web.bind.annotation.*
+import skills.auth.SecurityMode
+import skills.auth.form.jwt.JwtHelper
+import skills.services.InceptionProjectService
+
+@Conditional(SecurityMode.FormAuth)
+@RestController
+@skills.profile.EnableCallStackProf
+@Slf4j
+class UserTokenController {
+
+ @Autowired
+ TokenEndpoint tokenEndpoint
+
+ @Autowired
+ InMemoryOAuth2AuthorizedClientService authorizedClientService
+
+ @Autowired
+ JwtHelper jwtHelper
+
+ @Autowired
+ private JwtAccessTokenConverter jwtAccessTokenConverter;
+
+ @Autowired
+ private DefaultTokenServices tokenServices
+
+// @PostConstruct
+// void afterPropertiesSet() {
+// tokenServices = new DefaultTokenServices()
+// tokenServices.setTokenStore(jwtAccessTokenConverter)
+// tokenServices.setTokenEnhancer(jwtAccessTokenConverter)
+// }
+
+ /**
+ * token for inception
+ * @param userId
+ * @return
+ */
+ @RequestMapping(value = "/app/projects/Inception/users/{userId}/token", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ ResponseEntity getUserToken(@PathVariable("userId") String userId) {
+ return createSkillsProxyToken(InceptionProjectService.inceptionProjectId, userId)
+ }
+
+ /**
+ * token for current user already authenticated via third party OAuth2 provider (eg, google, gitlab, etc.)
+ * @param projectId
+ * @return
+ */
+ @RequestMapping(value = "/api/projects/{projectId}/token", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ @CrossOrigin(allowCredentials = 'true')
+ ResponseEntity getOAuth2UserToken(@PathVariable("projectId") String projectId) {
+ Object authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (authentication.credentials instanceof OAuth2AuthenticationToken && authentication.isAuthenticated()) {
+ OAuth2AuthenticationToken oAuth2AuthenticationToken = authentication.credentials
+ String oauthProvider = oAuth2AuthenticationToken.authorizedClientRegistrationId
+ String userId = authentication.name
+ log.debug("Creating self-proxy OAUth Token for [{}] or project [{}], authenticated via [{}] OAuth provider", userId, projectId, oauthProvider)
+ return createSkillsProxyToken(projectId, userId)
+ } else {
+ log.error("Unexpected authentication object [{}] with credentials [{}]", authentication, authentication.credentials)
+ }
+ }
+
+ private OAuth2Authentication convertAuthentication(Authentication authentication, String clientId) {
+ OAuth2Request request = new OAuth2Request(null, clientId, null, true, null, null, null, null, null)
+ return new OAuth2Authentication(request, authentication)
+ }
+
+ /**
+ * utilized by client-display within a project that previews that project's points
+ * @param projectId
+ * @param userId
+ * @return
+ */
+ @RequestMapping(value = "/admin/projects/{projectId}/token/{userId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ @ResponseBody
+ ResponseEntity getUserToken(@PathVariable("projectId") String projectId, @PathVariable("userId") String userId) {
+ return createSkillsProxyToken(projectId, userId)
+ }
+
+ private ResponseEntity createSkillsProxyToken(String projectId, String userId) {
+ skills.controller.exceptions.SkillsValidator.isNotBlank(projectId, "Project Id")
+ skills.controller.exceptions.SkillsValidator.isNotBlank(userId, "User Id")
+
+ UsernamePasswordAuthenticationToken principal = new UsernamePasswordAuthenticationToken(projectId, null, [])
+ Map parameters = [grant_type: 'client_credentials', proxy_user: userId]
+ return tokenEndpoint.postAccessToken(principal, parameters)
+ }
+}
diff --git a/backend/src/main/java/skills/controller/exceptions/DataIntegrityViolationExceptionHandler.groovy b/service/src/main/java/skills/controller/exceptions/DataIntegrityViolationExceptionHandler.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/DataIntegrityViolationExceptionHandler.groovy
rename to service/src/main/java/skills/controller/exceptions/DataIntegrityViolationExceptionHandler.groovy
diff --git a/backend/src/main/java/skills/controller/exceptions/ErrorCode.groovy b/service/src/main/java/skills/controller/exceptions/ErrorCode.groovy
similarity index 86%
rename from backend/src/main/java/skills/controller/exceptions/ErrorCode.groovy
rename to service/src/main/java/skills/controller/exceptions/ErrorCode.groovy
index f6c91885..70910ea6 100644
--- a/backend/src/main/java/skills/controller/exceptions/ErrorCode.groovy
+++ b/service/src/main/java/skills/controller/exceptions/ErrorCode.groovy
@@ -23,5 +23,10 @@ enum ErrorCode {
ConstraintViolation,
BadParam,
AccessDenied,
- UserNotFound
+ UserNotFound,
+ SkillNotFound,
+ BadgeNotFound,
+ SubjectNotFound,
+ ProjectNotFound,
+ EmptyBadgeNotAllowed,
}
diff --git a/backend/src/main/java/skills/controller/exceptions/InvalidContentTypeException.groovy b/service/src/main/java/skills/controller/exceptions/InvalidContentTypeException.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/InvalidContentTypeException.groovy
rename to service/src/main/java/skills/controller/exceptions/InvalidContentTypeException.groovy
diff --git a/backend/src/main/java/skills/controller/exceptions/MaxIconSizeExceeded.groovy b/service/src/main/java/skills/controller/exceptions/MaxIconSizeExceeded.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/MaxIconSizeExceeded.groovy
rename to service/src/main/java/skills/controller/exceptions/MaxIconSizeExceeded.groovy
diff --git a/backend/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy b/service/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy
similarity index 95%
rename from backend/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy
rename to service/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy
index d799c59f..7671fc35 100644
--- a/backend/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy
+++ b/service/src/main/java/skills/controller/exceptions/RestExceptionHandler.groovy
@@ -37,6 +37,8 @@ import skills.controller.exceptions.SkillException.SkillExceptionLogLevel
@Slf4j
class RestExceptionHandler extends ResponseEntityExceptionHandler {
+ static final List NOT_FOUND_CODES = [ErrorCode.SkillNotFound, ErrorCode.SubjectNotFound, ErrorCode.ProjectNotFound, ErrorCode.BadgeNotFound]
+
static class BasicErrBody {
String explanation
String errorCode = ErrorCode.InternalError
@@ -52,6 +54,7 @@ class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(SkillException)
protected ResponseEntity handleSkillException(Exception ex, WebRequest webRequest) {
Object body
+ HttpStatus status = HttpStatus.BAD_REQUEST
if (ex instanceof SkillException) {
body = new DomainSpecificErrBody(userId: ex.userId, projectId: ex.projectId, skillId: ex.skillId, explanation: ex.message, errorCode: ex.errorCode.name())
String msg = "Exception for: projectId=[${ex.projectId}], skillId=${ex.skillId}"
@@ -79,10 +82,14 @@ class RestExceptionHandler extends ResponseEntityExceptionHandler {
}
}
+ if (NOT_FOUND_CODES.contains(ex.errorCode)) {
+ status = HttpStatus.NOT_FOUND
+ }
+
} else {
log.error("Unexpected exception type [${ex?.class?.simpleName}]", ex)
}
- return new ResponseEntity(body, HttpStatus.BAD_REQUEST)
+ return new ResponseEntity(body, status)
}
@ExceptionHandler(DataIntegrityViolationException)
diff --git a/backend/src/main/java/skills/controller/exceptions/SkillException.groovy b/service/src/main/java/skills/controller/exceptions/SkillException.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/SkillException.groovy
rename to service/src/main/java/skills/controller/exceptions/SkillException.groovy
diff --git a/backend/src/main/java/skills/controller/exceptions/SkillExceptionBuilder.groovy b/service/src/main/java/skills/controller/exceptions/SkillExceptionBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/SkillExceptionBuilder.groovy
rename to service/src/main/java/skills/controller/exceptions/SkillExceptionBuilder.groovy
diff --git a/backend/src/main/java/skills/controller/exceptions/SkillsValidator.groovy b/service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/exceptions/SkillsValidator.groovy
rename to service/src/main/java/skills/controller/exceptions/SkillsValidator.groovy
diff --git a/backend/src/main/java/skills/controller/filters/ClientLibVersionFilter.java b/service/src/main/java/skills/controller/filters/ClientLibVersionFilter.java
similarity index 100%
rename from backend/src/main/java/skills/controller/filters/ClientLibVersionFilter.java
rename to service/src/main/java/skills/controller/filters/ClientLibVersionFilter.java
diff --git a/backend/src/main/java/skills/controller/filters/VueEntryPointFilter.java b/service/src/main/java/skills/controller/filters/VueEntryPointFilter.java
similarity index 100%
rename from backend/src/main/java/skills/controller/filters/VueEntryPointFilter.java
rename to service/src/main/java/skills/controller/filters/VueEntryPointFilter.java
diff --git a/backend/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy b/service/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy
similarity index 81%
rename from backend/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy
rename to service/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy
index 31ddf861..43d1da41 100644
--- a/backend/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy
+++ b/service/src/main/java/skills/controller/filters/VueEntryPointFilterUtils.groovy
@@ -21,7 +21,7 @@ import org.springframework.stereotype.Component
@Component
class VueEntryPointFilterUtils {
private final List backendResources =
- Collections.unmodifiableList("/api,/admin,/app,/static,/clientDisplay,/favicon.ico,/skills.ico,/icons,/performLogin,/createAccount,/createRootAccount,/grantFirstRoot,/userExists,/oauth,/login,/logout,/bootstrap,/root,/supervisor,/public,/metrics,/skills-websocket".split(",").toList())
+ Collections.unmodifiableList("/api,/admin,/app,/static,/clientDisplay,/favicon.ico,/skilltree.ico,/icons,/performLogin,/createAccount,/createRootAccount,/grantFirstRoot,/userExists,/oauth,/login,/logout,/root,/supervisor,/public,/skills-websocket,/resetPassword,/performPasswordReset,/metrics/global".split(",").toList())
boolean isFrontendResource(String pathInfo) {
return !isBackendResource(pathInfo)
diff --git a/backend/src/main/java/skills/controller/request/model/AccessRequest.groovy b/service/src/main/java/skills/controller/request/model/AccessRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/AccessRequest.groovy
rename to service/src/main/java/skills/controller/request/model/AccessRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/ActionPatchRequest.groovy b/service/src/main/java/skills/controller/request/model/ActionPatchRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/ActionPatchRequest.groovy
rename to service/src/main/java/skills/controller/request/model/ActionPatchRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/AddSkillRequest.groovy b/service/src/main/java/skills/controller/request/model/AddSkillRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/AddSkillRequest.groovy
rename to service/src/main/java/skills/controller/request/model/AddSkillRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/BadgeRequest.groovy b/service/src/main/java/skills/controller/request/model/BadgeRequest.groovy
similarity index 97%
rename from backend/src/main/java/skills/controller/request/model/BadgeRequest.groovy
rename to service/src/main/java/skills/controller/request/model/BadgeRequest.groovy
index 41053ff0..10c955e2 100644
--- a/backend/src/main/java/skills/controller/request/model/BadgeRequest.groovy
+++ b/service/src/main/java/skills/controller/request/model/BadgeRequest.groovy
@@ -28,4 +28,6 @@ class BadgeRequest {
Date endDate
String helpUrl
+
+ String enabled
}
diff --git a/backend/src/main/java/skills/controller/request/model/DependencyOrigin.groovy b/service/src/main/java/skills/controller/request/model/DependencyOrigin.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/DependencyOrigin.groovy
rename to service/src/main/java/skills/controller/request/model/DependencyOrigin.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/EditLevelRequest.groovy b/service/src/main/java/skills/controller/request/model/EditLevelRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/EditLevelRequest.groovy
rename to service/src/main/java/skills/controller/request/model/EditLevelRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/GlobalSettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/GlobalSettingsRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/GlobalSettingsRequest.groovy
rename to service/src/main/java/skills/controller/request/model/GlobalSettingsRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/GraphCheckType.groovy b/service/src/main/java/skills/controller/request/model/GraphCheckType.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/GraphCheckType.groovy
rename to service/src/main/java/skills/controller/request/model/GraphCheckType.groovy
diff --git a/service/src/main/java/skills/controller/request/model/NameExistsRequest.groovy b/service/src/main/java/skills/controller/request/model/NameExistsRequest.groovy
new file mode 100644
index 00000000..a98f9852
--- /dev/null
+++ b/service/src/main/java/skills/controller/request/model/NameExistsRequest.groovy
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller.request.model
+
+import groovy.transform.Canonical
+
+@Canonical
+class NameExistsRequest {
+ String name
+}
diff --git a/backend/src/main/java/skills/controller/request/model/NextLevelRequest.groovy b/service/src/main/java/skills/controller/request/model/NextLevelRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/NextLevelRequest.groovy
rename to service/src/main/java/skills/controller/request/model/NextLevelRequest.groovy
diff --git a/service/src/main/java/skills/controller/request/model/ProjectExistsRequest.groovy b/service/src/main/java/skills/controller/request/model/ProjectExistsRequest.groovy
new file mode 100644
index 00000000..50e98bcd
--- /dev/null
+++ b/service/src/main/java/skills/controller/request/model/ProjectExistsRequest.groovy
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller.request.model
+
+import groovy.transform.Canonical
+
+@Canonical
+class ProjectExistsRequest {
+ String projectId
+ String name
+}
diff --git a/backend/src/main/java/skills/controller/request/model/ProjectRequest.groovy b/service/src/main/java/skills/controller/request/model/ProjectRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/ProjectRequest.groovy
rename to service/src/main/java/skills/controller/request/model/ProjectRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/ProjectSettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/ProjectSettingsRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/ProjectSettingsRequest.groovy
rename to service/src/main/java/skills/controller/request/model/ProjectSettingsRequest.groovy
diff --git a/service/src/main/java/skills/controller/request/model/RootUserProjectSettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/RootUserProjectSettingsRequest.groovy
new file mode 100644
index 00000000..97296fcf
--- /dev/null
+++ b/service/src/main/java/skills/controller/request/model/RootUserProjectSettingsRequest.groovy
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2020 SkillTree
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package skills.controller.request.model
+
+class RootUserProjectSettingsRequest extends ProjectSettingsRequest{
+}
diff --git a/backend/src/main/java/skills/controller/request/model/SettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/SettingsRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SettingsRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SettingsRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SkillDefForDependencyRes.groovy b/service/src/main/java/skills/controller/request/model/SkillDefForDependencyRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SkillDefForDependencyRes.groovy
rename to service/src/main/java/skills/controller/request/model/SkillDefForDependencyRes.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SkillEventRequest.groovy b/service/src/main/java/skills/controller/request/model/SkillEventRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SkillEventRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SkillEventRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SkillRequest.groovy b/service/src/main/java/skills/controller/request/model/SkillRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SkillRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SkillRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SkillsClientVersionRequest.groovy b/service/src/main/java/skills/controller/request/model/SkillsClientVersionRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SkillsClientVersionRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SkillsClientVersionRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SkillsFilterType.groovy b/service/src/main/java/skills/controller/request/model/SkillsFilterType.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SkillsFilterType.groovy
rename to service/src/main/java/skills/controller/request/model/SkillsFilterType.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SubjectRequest.groovy b/service/src/main/java/skills/controller/request/model/SubjectRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SubjectRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SubjectRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/SuggestRequest.groovy b/service/src/main/java/skills/controller/request/model/SuggestRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/SuggestRequest.groovy
rename to service/src/main/java/skills/controller/request/model/SuggestRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/UserProjectSettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/UserProjectSettingsRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/UserProjectSettingsRequest.groovy
rename to service/src/main/java/skills/controller/request/model/UserProjectSettingsRequest.groovy
diff --git a/backend/src/main/java/skills/controller/request/model/UserSettingsRequest.groovy b/service/src/main/java/skills/controller/request/model/UserSettingsRequest.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/request/model/UserSettingsRequest.groovy
rename to service/src/main/java/skills/controller/request/model/UserSettingsRequest.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/BadgeResult.groovy b/service/src/main/java/skills/controller/result/model/BadgeResult.groovy
similarity index 98%
rename from backend/src/main/java/skills/controller/result/model/BadgeResult.groovy
rename to service/src/main/java/skills/controller/result/model/BadgeResult.groovy
index b6a24967..2d1da1c7 100644
--- a/backend/src/main/java/skills/controller/result/model/BadgeResult.groovy
+++ b/service/src/main/java/skills/controller/result/model/BadgeResult.groovy
@@ -45,4 +45,6 @@ class BadgeResult {
List requiredSkills = []
String helpUrl
+
+ String enabled
}
diff --git a/backend/src/main/java/skills/controller/result/model/CountItem.java b/service/src/main/java/skills/controller/result/model/CountItem.java
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/CountItem.java
rename to service/src/main/java/skills/controller/result/model/CountItem.java
diff --git a/backend/src/main/java/skills/controller/result/model/CustomIconResult.groovy b/service/src/main/java/skills/controller/result/model/CustomIconResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/CustomIconResult.groovy
rename to service/src/main/java/skills/controller/result/model/CustomIconResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/DependencyCheckResult.groovy b/service/src/main/java/skills/controller/result/model/DependencyCheckResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/DependencyCheckResult.groovy
rename to service/src/main/java/skills/controller/result/model/DependencyCheckResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/GlobalBadgeLevelRes.groovy b/service/src/main/java/skills/controller/result/model/GlobalBadgeLevelRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/GlobalBadgeLevelRes.groovy
rename to service/src/main/java/skills/controller/result/model/GlobalBadgeLevelRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/GlobalBadgeResult.groovy b/service/src/main/java/skills/controller/result/model/GlobalBadgeResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/GlobalBadgeResult.groovy
rename to service/src/main/java/skills/controller/result/model/GlobalBadgeResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/LabelCountItem.groovy b/service/src/main/java/skills/controller/result/model/LabelCountItem.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/LabelCountItem.groovy
rename to service/src/main/java/skills/controller/result/model/LabelCountItem.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/LevelDefinitionRes.groovy b/service/src/main/java/skills/controller/result/model/LevelDefinitionRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/LevelDefinitionRes.groovy
rename to service/src/main/java/skills/controller/result/model/LevelDefinitionRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/NumUsersRes.groovy b/service/src/main/java/skills/controller/result/model/NumUsersRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/NumUsersRes.groovy
rename to service/src/main/java/skills/controller/result/model/NumUsersRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/OAuth2Provider.groovy b/service/src/main/java/skills/controller/result/model/OAuth2Provider.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/OAuth2Provider.groovy
rename to service/src/main/java/skills/controller/result/model/OAuth2Provider.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/ProjectResult.groovy b/service/src/main/java/skills/controller/result/model/ProjectResult.groovy
similarity index 97%
rename from backend/src/main/java/skills/controller/result/model/ProjectResult.groovy
rename to service/src/main/java/skills/controller/result/model/ProjectResult.groovy
index d6964181..3cead322 100644
--- a/backend/src/main/java/skills/controller/result/model/ProjectResult.groovy
+++ b/service/src/main/java/skills/controller/result/model/ProjectResult.groovy
@@ -29,4 +29,6 @@ class ProjectResult extends SimpleProjectResult{
boolean isLast
boolean levelsArePoints
+
+ boolean pinned
}
diff --git a/backend/src/main/java/skills/controller/result/model/ProjectUser.groovy b/service/src/main/java/skills/controller/result/model/ProjectUser.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/ProjectUser.groovy
rename to service/src/main/java/skills/controller/result/model/ProjectUser.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/RequestResult.groovy b/service/src/main/java/skills/controller/result/model/RequestResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/RequestResult.groovy
rename to service/src/main/java/skills/controller/result/model/RequestResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SettingsResult.groovy b/service/src/main/java/skills/controller/result/model/SettingsResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SettingsResult.groovy
rename to service/src/main/java/skills/controller/result/model/SettingsResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SharedSkillResult.groovy b/service/src/main/java/skills/controller/result/model/SharedSkillResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SharedSkillResult.groovy
rename to service/src/main/java/skills/controller/result/model/SharedSkillResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SimpleProjectResult.groovy b/service/src/main/java/skills/controller/result/model/SimpleProjectResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SimpleProjectResult.groovy
rename to service/src/main/java/skills/controller/result/model/SimpleProjectResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SkillDefGraphRes.groovy b/service/src/main/java/skills/controller/result/model/SkillDefGraphRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SkillDefGraphRes.groovy
rename to service/src/main/java/skills/controller/result/model/SkillDefGraphRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SkillDefPartialRes.groovy b/service/src/main/java/skills/controller/result/model/SkillDefPartialRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SkillDefPartialRes.groovy
rename to service/src/main/java/skills/controller/result/model/SkillDefPartialRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SkillDefRes.groovy b/service/src/main/java/skills/controller/result/model/SkillDefRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SkillDefRes.groovy
rename to service/src/main/java/skills/controller/result/model/SkillDefRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SkillDefSkinnyRes.groovy b/service/src/main/java/skills/controller/result/model/SkillDefSkinnyRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SkillDefSkinnyRes.groovy
rename to service/src/main/java/skills/controller/result/model/SkillDefSkinnyRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SkillsGraphRes.groovy b/service/src/main/java/skills/controller/result/model/SkillsGraphRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SkillsGraphRes.groovy
rename to service/src/main/java/skills/controller/result/model/SkillsGraphRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/SubjectResult.groovy b/service/src/main/java/skills/controller/result/model/SubjectResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/SubjectResult.groovy
rename to service/src/main/java/skills/controller/result/model/SubjectResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/TableResult.groovy b/service/src/main/java/skills/controller/result/model/TableResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/TableResult.groovy
rename to service/src/main/java/skills/controller/result/model/TableResult.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/TimestampCountItem.groovy b/service/src/main/java/skills/controller/result/model/TimestampCountItem.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/TimestampCountItem.groovy
rename to service/src/main/java/skills/controller/result/model/TimestampCountItem.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/UserInfoRes.groovy b/service/src/main/java/skills/controller/result/model/UserInfoRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/UserInfoRes.groovy
rename to service/src/main/java/skills/controller/result/model/UserInfoRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/UserRoleRes.groovy b/service/src/main/java/skills/controller/result/model/UserRoleRes.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/UserRoleRes.groovy
rename to service/src/main/java/skills/controller/result/model/UserRoleRes.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/UserSkillsStats.groovy b/service/src/main/java/skills/controller/result/model/UserSkillsStats.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/UserSkillsStats.groovy
rename to service/src/main/java/skills/controller/result/model/UserSkillsStats.groovy
diff --git a/backend/src/main/java/skills/controller/result/model/ValidationResult.groovy b/service/src/main/java/skills/controller/result/model/ValidationResult.groovy
similarity index 100%
rename from backend/src/main/java/skills/controller/result/model/ValidationResult.groovy
rename to service/src/main/java/skills/controller/result/model/ValidationResult.groovy
diff --git a/backend/src/main/java/skills/icons/CssGenerator.groovy b/service/src/main/java/skills/icons/CssGenerator.groovy
similarity index 100%
rename from backend/src/main/java/skills/icons/CssGenerator.groovy
rename to service/src/main/java/skills/icons/CssGenerator.groovy
diff --git a/backend/src/main/java/skills/icons/CustomIconFacade.groovy b/service/src/main/java/skills/icons/CustomIconFacade.groovy
similarity index 100%
rename from backend/src/main/java/skills/icons/CustomIconFacade.groovy
rename to service/src/main/java/skills/icons/CustomIconFacade.groovy
diff --git a/backend/src/main/java/skills/icons/IconCssNameUtil.groovy b/service/src/main/java/skills/icons/IconCssNameUtil.groovy
similarity index 100%
rename from backend/src/main/java/skills/icons/IconCssNameUtil.groovy
rename to service/src/main/java/skills/icons/IconCssNameUtil.groovy
diff --git a/backend/src/main/java/skills/icons/UploadedIcon.groovy b/service/src/main/java/skills/icons/UploadedIcon.groovy
similarity index 100%
rename from backend/src/main/java/skills/icons/UploadedIcon.groovy
rename to service/src/main/java/skills/icons/UploadedIcon.groovy
diff --git a/backend/src/main/java/skills/metrics/ChartParams.groovy b/service/src/main/java/skills/metrics/ChartParams.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/ChartParams.groovy
rename to service/src/main/java/skills/metrics/ChartParams.groovy
diff --git a/backend/src/main/java/skills/metrics/MetricsService.groovy b/service/src/main/java/skills/metrics/MetricsService.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/MetricsService.groovy
rename to service/src/main/java/skills/metrics/MetricsService.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/MetricsChartBuilder.java b/service/src/main/java/skills/metrics/builders/MetricsChartBuilder.java
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/MetricsChartBuilder.java
rename to service/src/main/java/skills/metrics/builders/MetricsChartBuilder.java
diff --git a/backend/src/main/java/skills/metrics/builders/badges/AchievedPerMonthChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/badges/AchievedPerMonthChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/badges/AchievedPerMonthChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/badges/AchievedPerMonthChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/badges/DistinctUsersOverTimeChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/badges/DistinctUsersOverTimeChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/badges/DistinctUsersOverTimeChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/badges/DistinctUsersOverTimeChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/global/NumUsersPerProjectBuilder.groovy b/service/src/main/java/skills/metrics/builders/global/NumUsersPerProjectBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/global/NumUsersPerProjectBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/global/NumUsersPerProjectBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/global/SkillCountPerProjectBuilder.groovy b/service/src/main/java/skills/metrics/builders/global/SkillCountPerProjectBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/global/SkillCountPerProjectBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/global/SkillCountPerProjectBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/AchievedSkillsChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/AchievedSkillsChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/AchievedSkillsChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/AchievedSkillsChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/ContinuedUsageChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/ContinuedUsageChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/ContinuedUsageChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/ContinuedUsageChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/DistinctUsersOverTimeChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/DistinctUsersOverTimeChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/DistinctUsersOverTimeChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/DistinctUsersOverTimeChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/NumUsersPerBadgeChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/NumUsersPerBadgeChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/NumUsersPerBadgeChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/NumUsersPerBadgeChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/NumUsersPerLevelChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/NumUsersPerLevelChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/NumUsersPerLevelChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/NumUsersPerLevelChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/OverlookedSkillsChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/OverlookedSkillsChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/OverlookedSkillsChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/OverlookedSkillsChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/TimeToAchieveChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/TimeToAchieveChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/TimeToAchieveChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/TimeToAchieveChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/projects/TopAchievedChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/projects/TopAchievedChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/projects/TopAchievedChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/projects/TopAchievedChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/skills/DistinctUsersOverTimeChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/skills/DistinctUsersOverTimeChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/skills/DistinctUsersOverTimeChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/skills/DistinctUsersOverTimeChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/subjects/AchievedSkillsChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/subjects/AchievedSkillsChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/subjects/AchievedSkillsChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/subjects/AchievedSkillsChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/subjects/DistinctUsersOverTimeChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/subjects/DistinctUsersOverTimeChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/subjects/DistinctUsersOverTimeChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/subjects/DistinctUsersOverTimeChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/subjects/NumUsersPerLevelChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/subjects/NumUsersPerLevelChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/subjects/NumUsersPerLevelChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/subjects/NumUsersPerLevelChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/builders/users/PointHistoryChartBuilder.groovy b/service/src/main/java/skills/metrics/builders/users/PointHistoryChartBuilder.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/builders/users/PointHistoryChartBuilder.groovy
rename to service/src/main/java/skills/metrics/builders/users/PointHistoryChartBuilder.groovy
diff --git a/backend/src/main/java/skills/metrics/model/ChartOption.java b/service/src/main/java/skills/metrics/model/ChartOption.java
similarity index 100%
rename from backend/src/main/java/skills/metrics/model/ChartOption.java
rename to service/src/main/java/skills/metrics/model/ChartOption.java
diff --git a/backend/src/main/java/skills/metrics/model/ChartType.java b/service/src/main/java/skills/metrics/model/ChartType.java
similarity index 100%
rename from backend/src/main/java/skills/metrics/model/ChartType.java
rename to service/src/main/java/skills/metrics/model/ChartType.java
diff --git a/backend/src/main/java/skills/metrics/model/MetricsChart.groovy b/service/src/main/java/skills/metrics/model/MetricsChart.groovy
similarity index 100%
rename from backend/src/main/java/skills/metrics/model/MetricsChart.groovy
rename to service/src/main/java/skills/metrics/model/MetricsChart.groovy
diff --git a/backend/src/main/java/skills/metrics/model/Section.java b/service/src/main/java/skills/metrics/model/Section.java
similarity index 100%
rename from backend/src/main/java/skills/metrics/model/Section.java
rename to service/src/main/java/skills/metrics/model/Section.java
diff --git a/backend/src/main/java/skills/profile/CallStackProfAspect.groovy b/service/src/main/java/skills/profile/CallStackProfAspect.groovy
similarity index 100%
rename from backend/src/main/java/skills/profile/CallStackProfAspect.groovy
rename to service/src/main/java/skills/profile/CallStackProfAspect.groovy
diff --git a/backend/src/main/java/skills/profile/EnableCallStackProf.java b/service/src/main/java/skills/profile/EnableCallStackProf.java
similarity index 100%
rename from backend/src/main/java/skills/profile/EnableCallStackProf.java
rename to service/src/main/java/skills/profile/EnableCallStackProf.java
diff --git a/backend/src/main/java/skills/services/AccessSettingsStorageService.groovy b/service/src/main/java/skills/services/AccessSettingsStorageService.groovy
similarity index 93%
rename from backend/src/main/java/skills/services/AccessSettingsStorageService.groovy
rename to service/src/main/java/skills/services/AccessSettingsStorageService.groovy
index 6bfaf624..68ee5e14 100644
--- a/backend/src/main/java/skills/services/AccessSettingsStorageService.groovy
+++ b/service/src/main/java/skills/services/AccessSettingsStorageService.groovy
@@ -123,6 +123,10 @@ class AccessSettingsStorageService {
@Transactional
UserRoleRes addRoot(String userId) {
UserRole userRole = addUserRoleInternal(userId, null, RoleName.ROLE_SUPER_DUPER_USER)
+ User user = userRepository.findByUserId(userId)
+ if (!(user?.roles?.find {it.projectId == null && it.roleName == RoleName.ROLE_SUPERVISOR})) {
+ addUserRoleInternal(userId, null, RoleName.ROLE_SUPERVISOR)
+ }
inceptionProjectService.createInceptionAndAssignUser(userId)
return convert(userRole)
}
@@ -131,6 +135,11 @@ class AccessSettingsStorageService {
void deleteRoot(String userId) {
userId = userId?.toLowerCase()
deleteUserRoleInternal(userId, null, RoleName.ROLE_SUPER_DUPER_USER)
+
+ User user = userRepository.findByUserId(userId)
+ if (user?.roles?.find {it.projectId == null && it.roleName == RoleName.ROLE_SUPERVISOR}) {
+ deleteUserRoleInternal(userId, null, RoleName.ROLE_SUPERVISOR)
+ }
inceptionProjectService.removeUser(userId)
}
@@ -174,7 +183,7 @@ class AccessSettingsStorageService {
UserRole existingUserRole = user?.roles?.find {it.projectId == projectId && it.roleName == roleName}
assert !existingUserRole, "CREATE FAILED -> user-role with project id [$projectId], userId [$userId] and roleName [$roleName] already exists"
} else {
- throw new SkillException("User [$userId] does not exist", (String) projectId)
+ throw new SkillException("User [$userId] does not exist", (String) projectId ?: SkillException.NA, SkillException.NA, ErrorCode.UserNotFound)
}
UserRole userRole = new UserRole(userId: userId, roleName: roleName, projectId: projectId)
@@ -238,8 +247,13 @@ class AccessSettingsStorageService {
userId: userId,
roleName: RoleName.ROLE_SUPER_DUPER_USER
)
+ UserRole supervisorRole = new UserRole(
+ userId: userId,
+ roleName: RoleName.ROLE_SUPERVISOR
+ )
user.roles.add(role)
+ user.roles.add(supervisorRole)
userRepository.save(user)
return convert(role)
}
diff --git a/backend/src/main/java/skills/services/AdminUsersService.groovy b/service/src/main/java/skills/services/AdminUsersService.groovy
similarity index 98%
rename from backend/src/main/java/skills/services/AdminUsersService.groovy
rename to service/src/main/java/skills/services/AdminUsersService.groovy
index f7d80c38..05db6bcb 100644
--- a/backend/src/main/java/skills/services/AdminUsersService.groovy
+++ b/service/src/main/java/skills/services/AdminUsersService.groovy
@@ -138,7 +138,7 @@ class AdminUsersService {
}
List getAchievementCountsPerSubject(String projectId, int topNToLoad =5) {
- List res = userAchievedRepo.getUsageFacetedViaSubject(projectId, SkillDef.ContainerType.Subject, new PageRequest(0, topNToLoad, Sort.Direction.DESC, "countRes"))
+ List res = userAchievedRepo.getUsageFacetedViaSubject(projectId, SkillDef.ContainerType.Subject, PageRequest.of(0, topNToLoad, Sort.Direction.DESC, "countRes"))
return res.collect {
new LabelCountItem(value: it.label, count: it.countRes)
@@ -146,7 +146,7 @@ class AdminUsersService {
}
List getAchievementCountsPerSkill(String projectId, String subjectId, int topNToLoad =5) {
- List res = userAchievedRepo.getSubjectUsageFacetedViaSkill(projectId, subjectId, SkillDef.ContainerType.Subject, new PageRequest(0, topNToLoad, Sort.Direction.DESC, "countRes"))
+ List