Showing preview only (9,168K chars total). Download the full file or copy to clipboard to get everything.
Repository: ARBML/Calliar
Branch: main
Commit: dc1b9f8b58bf
Files: 60
Total size: 24.9 MB
Directory structure:
gitextract_2uotyvr1/
├── .github/
│ └── workflows/
│ └── actinos_workflow.yml
├── .gitignore
├── LICENSE
├── README.md
├── calliar_dataset/
│ ├── dataset.npz
│ ├── dataset_words.npz
│ └── dataset_words_str.npz
├── calliar_server/
│ ├── calliar_server/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── server/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── models.py
│ ├── static/
│ │ ├── explore.js
│ │ ├── fabric.js
│ │ ├── keyboard.css
│ │ ├── keyboard.js
│ │ ├── main.js
│ │ ├── main2.js
│ │ ├── paper-full.js
│ │ ├── paper.js
│ │ ├── unicode.js
│ │ └── utils.js
│ ├── templates/
│ │ └── server/
│ │ ├── explore.html
│ │ ├── index.html
│ │ └── index2.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── demo.ipynb
├── media/
│ └── calliar_images/
│ └── annotations/
│ ├── 1ان المتقين في جنات وعيون ادخلوها بسلام آمنين.json
│ ├── الحمد لله copy.json
│ ├── الحمد لله.json
│ ├── خالد.json
│ └── رب يسر ولا تعسر رب تمم بالخير.json
├── notebooks/
│ ├── Check Wrong Annotations.ipynb
│ ├── Clustering Characters.ipynb
│ ├── Collect bism allah.ipynb
│ ├── Colorize text and image.ipynb
│ ├── Convert Strokes to Images.ipynb
│ ├── Fix alignments of strokes .ipynb
│ ├── Generate Characters and words.ipynb
│ ├── Generate as minified json.ipynb
│ ├── Split dataset .ipynb
│ ├── data npz generator.ipynb
│ ├── data word npz generator.ipynb
│ └── data_visualizer.ipynb
├── page/
│ ├── index.html
│ ├── index.js
│ ├── index_ar.html
│ ├── json.js
│ └── json_sm.js
├── requirements.txt
└── scripts/
├── chars.py
└── vis.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/actinos_workflow.yml
================================================
name: Deployment Workflow
on:
push:
branches: [main]
jobs:
first_job:
name: Deploy Calliar Website
runs-on: ubuntu-latest
steps:
- name: SSH Remote Commands
uses: appleboy/ssh-action@v0.1.4
with:
host: calliar.arbml.org
username: arabicmachinelearning
key: ${{ secrets.SECRET_SSHKEY }}
port: 22
script: |
cd ~
if [ ! -d "atmatah" ];
then
echo cloning atamatah as it does not exist
git clone https://github.com/ARBML/atmatah.git
cd atmatah
else
echo pulling latest changes from atmatah
cd atmatah
git pull
fi
echo creating venv if not exist and install requirements
if [ ! -d "venv" ];
then
python3 -m venv venv
fi
venv/bin/python3 -m pip install -r requirements.txt
venv/bin/ansible-galaxy install -r requirements.yml
echo prepare for deploy
mkdir -p .config
echo ${{ secrets.ANS_PASS }} > .config/ansible_pass.password
echo deploy calliar app
venv/bin/ansible-playbook -i apps/calliar/inventory apps/calliar/app.yml --vault-password-file=.config/ansible_pass.password --connection=local
echo cleanup
cd ~
================================================
FILE: .gitignore
================================================
# https://github.com/github/gitignore/blob/main/Python.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json/page/index.html
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#---------------------custom--------------------------_#
*.pyc
venv/*
*.svg
!media/*
.vscode
.ipynb_checkpoints
*/.ipynb_checkpoints/*
calliar_server/calliar_server/__pycache__/*.pyc
calliar_server/server/__pycache__/views.cpython-36.pyc
calliar_server/db.sqlite3
calliar_server/server/__pycache__/urls.cpython-36.pyc
calliar_server/server/__pycache__/views.cpython-36.pyc
.vscode/*
calliar_server/calliar_server/local_settings.py
calliar_server/db.sqlite3
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 ARBML
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Calliar
Calliar is a dataset for Arabic calligraphy. The dataset consists of 2500 json files that contain strokes manually annotated for Arabic calligraphy. This repository contains the dataset for the following paper :
> **Calliar: An Online Handwritten Dataset for Arabic Calligraphy**<br>
> Zaid Alyafeai, Maged S. Al-shaibani, Mustafa Ghaleb, Yousif Ahmed Al-Wajih<br>
> https://arxiv.org/abs/2106.10745
>
> **Abstract:** *Calligraphy is an essential part of the Arabic heritage and culture. It has been used in the past for the decoration of houses and mosques. Usually, such calligraphy is designed manually by experts with aesthetic insights. In the past few years, there has been a considerable effort to digitize such type of art by either taking a photo of decorated buildings or drawing them using digital devices. The latter is considered an online form where the drawing is tracked by recording the apparatus movement, an electronic pen for instance, on a screen. In the literature, there are many offline datasets collected with a diversity of Arabic styles for calligraphy. However, there is no available online dataset for Arabic calligraphy. In this paper, we illustrate our approach for the collection and annotation of an online dataset for Arabic calligraphy called Calliar that consists of 2,500 sentences. Calliar is annotated for stroke, character, word and sentence level prediction.*
## Stats
| Dataset | # of Samples | # of Words | # of Chars | # of Strokes |
---------|---------------|-----------|------------|---------------
| Train | 2,000 | 6,065 | 24,722 | 36,561 |
| Valid | 250 | 738 | 2,946 | 4,410 |
| Test | 250 | 753 |3,052 | 4,601 |
## Dataset Formats
Mainly, we have two basic formats.
### .json
Each `.json` file contains a list of strokes. Each list is a dictionary of the stroke character and the list of points. Each composite character like `ت` is mapped into a list of primitive strokes i.e `..ٮ `. Refer to the paper and to `chars.py` for more details on the mapping.

### .npz
The compressed format of the dataset `dataset.npz` is only 8.6 MB and uses the Ramer-Douglas-Peucker Algorithm to decrease the number of points per stroke. The python library [rdp](https://github.com/fhirschmann/rdp) was used for such task. The `.npz` format follows the same approach as [QuickDraw](https://github.com/googlecreativelab/quickdraw-dataset).
## Visualization
The `vis.py` file contains a list of python methods for easily visualizing the dataset. Here are two examples for drawing a sample json file and creating an animation. <a href="https://colab.research.google.com/github/ARBML/Calliar/blob/main/demo.ipynb">
<img src="https://colab.research.google.com/assets/colab-badge.svg" >
</a>
```python
import glob
import matplotlib.pyplot as plt
import json
from IPython.core.display import display, HTML, Video
from vis import *
## show an image of the strokes
drawing = json.load(open(json_path))
print(get_annotation(json_path))
data = convert_3d(drawing)
draw_strokes(data, stroke_width = 2, crop = True)
## create an animation.
create_animation(json_path)
Video("tmp/video.mp4")
```
## Samples

## Annotation Server
First `pip install django`, then traverse to the directory `calliar_server` and run
```basch
python manage.py runserver
```
## Animation
https://user-images.githubusercontent.com/15667714/122690255-1acff980-d231-11eb-9752-730d8024041e.mp4
https://user-images.githubusercontent.com/15667714/122690258-1c012680-d231-11eb-98d0-4c89afd8aba3.mp4
https://user-images.githubusercontent.com/15667714/122690261-1d325380-d231-11eb-99ab-158455483561.mp4
https://user-images.githubusercontent.com/15667714/122690265-1efc1700-d231-11eb-9830-328725a82c11.mp4
## Citation
```
@misc{alyafeai2021calliar,
title={Calliar: An Online Handwritten Dataset for Arabic Calligraphy},
author={Zaid Alyafeai and Maged S. Al-shaibani and Mustafa Ghaleb and Yousif Ahmed Al-Wajih},
year={2021},
eprint={2106.10745},
archivePrefix={arXiv},
primaryClass={cs.CL}
}
```
================================================
FILE: calliar_server/calliar_server/__init__.py
================================================
================================================
FILE: calliar_server/calliar_server/asgi.py
================================================
"""
ASGI config for calliar_server project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "calliar_server.settings")
application = get_asgi_application()
================================================
FILE: calliar_server/calliar_server/settings.py
================================================
"""
Django settings for calliar_server project.
Generated by 'django-admin startproject' using Django 3.2.2.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-c0eh)tcsdj2x#_3rcs7fokahj0vk*7&!3n!zi60%0_lqf1e-u^"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
# ALLOWED_HOSTS = []
ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"server",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "calliar_server.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "calliar_server.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = "/static/"
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CORS_ALLOW_ALL_ORIGINS = True
MEDIA_ROOT = os.path.join(BASE_DIR, "../media")
MEDIA_URL = "/media/"
#---------------- CUSTOM ENV VARS -------------
IMAGES_DIR = '../media/calliar_images'
try:
from .local_settings import *
except Exception as e:
print('local settings file cannot be imported')
# create these dirs if they do not exist,
os.system(f'mkdir -p {IMAGES_DIR}/annotations')
os.system(f'mkdir -p {IMAGES_DIR}/annoated_images')
# assert required directories within IMAGES_DIR exists
assert os.path.isdir(f'{IMAGES_DIR}/annotations'), 'annotations folder does not exist'
assert os.path.isdir(f'{IMAGES_DIR}/images'), 'images folder does not exist'
assert os.path.isdir(f'{IMAGES_DIR}/annoated_images'), 'processed_images folder does not exist'
================================================
FILE: calliar_server/calliar_server/urls.py
================================================
"""calliar_server URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [path("admin/", admin.site.urls), path("", include("server.urls"))]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
================================================
FILE: calliar_server/calliar_server/wsgi.py
================================================
"""
WSGI config for calliar_server project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "calliar_server.settings")
application = get_wsgi_application()
================================================
FILE: calliar_server/manage.py
================================================
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "calliar_server.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
================================================
FILE: calliar_server/server/__init__.py
================================================
================================================
FILE: calliar_server/server/admin.py
================================================
from django.contrib import admin
# Register your models here.
================================================
FILE: calliar_server/server/apps.py
================================================
from django.apps import AppConfig
class ServerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'server'
================================================
FILE: calliar_server/server/migrations/__init__.py
================================================
================================================
FILE: calliar_server/server/models.py
================================================
from django.db import models
# Create your models here.
================================================
FILE: calliar_server/server/static/explore.js
================================================
/*
variables
*/
var canvas;
var paper;
var curr_img;
var strokeWidth = 3;
var currJsonId = 0;
var Strokeindex = 0;
var paths = [];
var colors = ['#7fc97f', '#beaed4', '#fdc086', '#008ecc', '#386cb0', '#f0027f', '#bf5b16', '#666666']
var w = 600;
var h = 600;
var drawing = false;
var readonlyInput;
var speed = 2;
var canvas = Raphael('canvas', '600px', '600px');
canvas.setViewBox(0,0,w,h);
var animatePath = function(paths) {
color = colors[randomNumber(0, colors.length)]
var line = canvas.path(paths[0]).attr({
stroke: color,
'stroke-opacity': 0,
});
var rand = Date.now();
line.node.id = 'path'+rand;
$('#'+line.node.id).css("transform", "translate(30,7)");
var length = line.getTotalLength();
var prev_path;
$('#'+line.node.id).animate({
'to': 1}, {
duration: parseInt(length*speed),
step: function(pos, fx) {
var offset = length * fx.pos;
var subpath = line.getSubpath(0, offset);
if (prev_path != null)
{
prev_path.remove();
}
prev_path = canvas.path(subpath).attr({
'stroke-width': strokeWidth,
stroke: color
});
},
complete: function(){
if (paths.length > 0){
animatePath(paths.slice(1))
}
else{
enableBtns()
drawing = false
}
Strokeindex += 1;
readonlyInput.focus();
readonlyInput.setSelectionRange(0, 2*Strokeindex);
}
});
};
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function getJsonList(){
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", '/explore/list-json?', false ); // false for synchronous request
xmlHttp.send( null );
response = JSON.parse(xmlHttp.response)
return response.json_names
}
function getJsonUrl(id = undefined){
if(id){
currJsonId = id
}
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", '/explore/next-json?id='+currJsonId, false ); // false for synchronous request
xmlHttp.send( null );
response = JSON.parse(xmlHttp.response)
currJsonId = parseInt(response.id)
return response.json_path
}
function disableBtns(){
$('button').prop('disabled', true);
$(':radio').prop('disabled', true);
}
function enableBtns(){
$('button').prop('disabled', false);
$(':radio').prop('disabled', false);
}
function generateNext(){
currJsonId += 1
generate()
}
function generatePrev(){
currJsonId -= 1
generate()
}
function setImage(w, h){
const scale = 600;
if (w > h){
h = parseInt((h/w) * scale);
w = scale;
}else{
w = parseInt((w/h) * scale);
h = scale;
}
cx = 300
cy = 300
canvas.image("/media/calliar_images/processed_images/"+imageName, cx - w/2, cy - h/2, w, h).attr({
opacity: .3,
});
}
function addRaster(imageName)
{
image_url = "/media/calliar_images/processed_images/"+imageName;
const img = new Image();
img.src = image_url
img.onload = function() { setImage(this.width, this.height); }
}
function generate(id = undefined) {
canvas.clear();
Strokeindex = 0;
disableBtns()
drawing = true
json_path = getJsonUrl(id = id)
json_name = json_path.split('.json')[0]
text = preprocess(json_name)[1]
readonlyInput.value = text;
imageName = json_name+'.jpg'
addRaster(imageName)
$.getJSON('/media/calliar_images/annotations/'+json_path , function(data) {
paths = []
for (var char in data){
for (var key in data[char]){
paths.push(data[char][key]);
}
}
animatePath(paths)
});
}
async function start() {
generate()
};
function onClick(id){
generate(id)
}
function createBtn(content, id = 0){
return `<button id = ${id} type="button" class="btn btn-light" onclick="onClick(this.id)">${content}</span>`
}
window.onload = (event) => {
readonlyInput = document.getElementById("text-readonly");
strokeWidth = document.getElementById('slider').value
canvas.clear();
$( "#slider" ).on('change', function move(){
strokeWidth = parseInt(this.value);
});
console.log('page is fully loaded');
const jsonList = getJsonList()
for (var i=0; i<jsonList.length; i++) {
document.querySelector('#jsonList').innerHTML += createBtn(jsonList[i], id = i)
}
};
function speedUp(){
speed = 1
}
function speedDown(){
speed = 4
}
function speedReset(){
speed = 2
}
================================================
FILE: calliar_server/server/static/fabric.js
================================================
/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */
/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */
var fabric = fabric || { version: '2.3.1' };
if (typeof exports !== 'undefined') {
exports.fabric = fabric;
}
/* _AMD_START_ */
else if (typeof define === 'function' && define.amd) {
define([], function() { return fabric; });
}
/* _AMD_END_ */
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
fabric.document = document;
fabric.window = window;
}
else {
// assume we're running under node.js when document/window are not present
fabric.document = require('jsdom')
.jsdom(
decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'),
{ features: {
FetchExternalResources: ['img']
}
});
fabric.jsdomImplForWrapper = require('jsdom/lib/jsdom/living/generated/utils').implForWrapper;
fabric.nodeCanvas = require('jsdom/lib/jsdom/utils').Canvas;
fabric.window = fabric.document.defaultView;
DOMParser = require('xmldom').DOMParser;
}
/**
* True when in environment that supports touch events
* @type boolean
*/
fabric.isTouchSupported = 'ontouchstart' in fabric.window;
/**
* True when in environment that's probably Node.js
* @type boolean
*/
fabric.isLikelyNode = typeof Buffer !== 'undefined' &&
typeof window === 'undefined';
/* _FROM_SVG_START_ */
/**
* Attributes parsed from all SVG elements
* @type array
*/
fabric.SHARED_ATTRIBUTES = [
"display",
"transform",
"fill", "fill-opacity", "fill-rule",
"opacity",
"stroke", "stroke-dasharray", "stroke-linecap",
"stroke-linejoin", "stroke-miterlimit",
"stroke-opacity", "stroke-width",
"id", "paint-order",
"instantiated_by_use"
];
/* _FROM_SVG_END_ */
/**
* Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion.
*/
fabric.DPI = 96;
fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)';
fabric.fontPaths = { };
fabric.iMatrix = [1, 0, 0, 1, 0, 0];
fabric.canvasModule = 'canvas';
/**
* Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine.
* @since 1.7.14
* @type Number
* @default
*/
fabric.perfLimitSizeTotal = 2097152;
/**
* Pixel limit for cache canvases width or height. IE fixes the maximum at 5000
* @since 1.7.14
* @type Number
* @default
*/
fabric.maxCacheSideLimit = 4096;
/**
* Lowest pixel limit for cache canvases, set at 256PX
* @since 1.7.14
* @type Number
* @default
*/
fabric.minCacheSideLimit = 256;
/**
* Cache Object for widths of chars in text rendering.
*/
fabric.charWidthsCache = { };
/**
* if webgl is enabled and available, textureSize will determine the size
* of the canvas backend
* @since 2.0.0
* @type Number
* @default
*/
fabric.textureSize = 2048;
/**
* Enable webgl for filtering picture is available
* A filtering backend will be initialized, this will both take memory and
* time since a default 2048x2048 canvas will be created for the gl context
* @since 2.0.0
* @type Boolean
* @default
*/
fabric.enableGLFiltering = true;
/**
* Device Pixel Ratio
* @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html
*/
fabric.devicePixelRatio = fabric.window.devicePixelRatio ||
fabric.window.webkitDevicePixelRatio ||
fabric.window.mozDevicePixelRatio ||
1;
/**
* Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value,
* which is unitless and not rendered equally across browsers.
*
* Values that work quite well (as of October 2017) are:
* - Chrome: 1.5
* - Edge: 1.75
* - Firefox: 0.9
* - Safari: 0.95
*
* @since 2.0.0
* @type Number
* @default 1
*/
fabric.browserShadowBlurConstant = 1;
fabric.initFilterBackend = function() {
if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) {
console.log('max texture size: ' + fabric.maxTextureSize);
return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize }));
}
else if (fabric.Canvas2dFilterBackend) {
return (new fabric.Canvas2dFilterBackend());
}
};
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// ensure globality even if entire library were function wrapped (as in Meteor.js packaging system)
window.fabric = fabric;
}
(function() {
/**
* @private
* @param {String} eventName
* @param {Function} handler
*/
function _removeEventListener(eventName, handler) {
if (!this.__eventListeners[eventName]) {
return;
}
var eventListener = this.__eventListeners[eventName];
if (handler) {
eventListener[eventListener.indexOf(handler)] = false;
}
else {
fabric.util.array.fill(eventListener, false);
}
}
/**
* Observes specified event
* @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
* @memberOf fabric.Observable
* @alias on
* @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
* @param {Function} handler Function that receives a notification when an event of the specified type occurs
* @return {Self} thisArg
* @chainable
*/
function observe(eventName, handler) {
if (!this.__eventListeners) {
this.__eventListeners = { };
}
// one object with key/value pairs was passed
if (arguments.length === 1) {
for (var prop in eventName) {
this.on(prop, eventName[prop]);
}
}
else {
if (!this.__eventListeners[eventName]) {
this.__eventListeners[eventName] = [];
}
this.__eventListeners[eventName].push(handler);
}
return this;
}
/**
* Stops event observing for a particular event handler. Calling this method
* without arguments removes all handlers for all events
* @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
* @memberOf fabric.Observable
* @alias off
* @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
* @param {Function} handler Function to be deleted from EventListeners
* @return {Self} thisArg
* @chainable
*/
function stopObserving(eventName, handler) {
if (!this.__eventListeners) {
return;
}
// remove all key/value pairs (event name -> event handler)
if (arguments.length === 0) {
for (eventName in this.__eventListeners) {
_removeEventListener.call(this, eventName);
}
}
// one object with key/value pairs was passed
else if (arguments.length === 1 && typeof arguments[0] === 'object') {
for (var prop in eventName) {
_removeEventListener.call(this, prop, eventName[prop]);
}
}
else {
_removeEventListener.call(this, eventName, handler);
}
return this;
}
/**
* Fires event with an optional options object
* @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
* @memberOf fabric.Observable
* @alias trigger
* @param {String} eventName Event name to fire
* @param {Object} [options] Options object
* @return {Self} thisArg
* @chainable
*/
function fire(eventName, options) {
if (!this.__eventListeners) {
return;
}
var listenersForEvent = this.__eventListeners[eventName];
if (!listenersForEvent) {
return;
}
for (var i = 0, len = listenersForEvent.length; i < len; i++) {
listenersForEvent[i] && listenersForEvent[i].call(this, options || { });
}
this.__eventListeners[eventName] = listenersForEvent.filter(function(value) {
return value !== false;
});
return this;
}
/**
* @namespace fabric.Observable
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events}
* @see {@link http://fabricjs.com/events|Events demo}
*/
fabric.Observable = {
observe: observe,
stopObserving: stopObserving,
fire: fire,
on: observe,
off: stopObserving,
trigger: fire
};
})();
/**
* @namespace fabric.Collection
*/
fabric.Collection = {
_objects: [],
/**
* Adds objects to collection, Canvas or Group, then renders canvas
* (if `renderOnAddRemove` is not `false`).
* in case of Group no changes to bounding box are made.
* Objects should be instances of (or inherit from) fabric.Object
* Use of this function is highly discouraged for groups.
* you can add a bunch of objects with the add method but then you NEED
* to run a addWithUpdate call for the Group class or position/bbox will be wrong.
* @param {...fabric.Object} object Zero or more fabric instances
* @return {Self} thisArg
* @chainable
*/
add: function () {
this._objects.push.apply(this._objects, arguments);
if (this._onObjectAdded) {
for (var i = 0, length = arguments.length; i < length; i++) {
this._onObjectAdded(arguments[i]);
}
}
this.renderOnAddRemove && this.requestRenderAll();
return this;
},
/**
* Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`)
* An object should be an instance of (or inherit from) fabric.Object
* Use of this function is highly discouraged for groups.
* you can add a bunch of objects with the insertAt method but then you NEED
* to run a addWithUpdate call for the Group class or position/bbox will be wrong.
* @param {Object} object Object to insert
* @param {Number} index Index to insert object at
* @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs
* @return {Self} thisArg
* @chainable
*/
insertAt: function (object, index, nonSplicing) {
var objects = this.getObjects();
if (nonSplicing) {
objects[index] = object;
}
else {
objects.splice(index, 0, object);
}
this._onObjectAdded && this._onObjectAdded(object);
this.renderOnAddRemove && this.requestRenderAll();
return this;
},
/**
* Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`)
* @param {...fabric.Object} object Zero or more fabric instances
* @return {Self} thisArg
* @chainable
*/
remove: function() {
var objects = this.getObjects(),
index, somethingRemoved = false;
for (var i = 0, length = arguments.length; i < length; i++) {
index = objects.indexOf(arguments[i]);
// only call onObjectRemoved if an object was actually removed
if (index !== -1) {
somethingRemoved = true;
objects.splice(index, 1);
this._onObjectRemoved && this._onObjectRemoved(arguments[i]);
}
}
this.renderOnAddRemove && somethingRemoved && this.requestRenderAll();
return this;
},
/**
* Executes given function for each object in this group
* @param {Function} callback
* Callback invoked with current object as first argument,
* index - as second and an array of all objects - as third.
* Callback is invoked in a context of Global Object (e.g. `window`)
* when no `context` argument is given
*
* @param {Object} context Context (aka thisObject)
* @return {Self} thisArg
* @chainable
*/
forEachObject: function(callback, context) {
var objects = this.getObjects();
for (var i = 0, len = objects.length; i < len; i++) {
callback.call(context, objects[i], i, objects);
}
return this;
},
/**
* Returns an array of children objects of this instance
* Type parameter introduced in 1.3.10
* @param {String} [type] When specified, only objects of this type are returned
* @return {Array}
*/
getObjects: function(type) {
if (typeof type === 'undefined') {
return this._objects;
}
return this._objects.filter(function(o) {
return o.type === type;
});
},
/**
* Returns object at specified index
* @param {Number} index
* @return {Self} thisArg
*/
item: function (index) {
return this.getObjects()[index];
},
/**
* Returns true if collection contains no objects
* @return {Boolean} true if collection is empty
*/
isEmpty: function () {
return this.getObjects().length === 0;
},
/**
* Returns a size of a collection (i.e: length of an array containing its objects)
* @return {Number} Collection size
*/
size: function() {
return this.getObjects().length;
},
/**
* Returns true if collection contains an object
* @param {Object} object Object to check against
* @return {Boolean} `true` if collection contains an object
*/
contains: function(object) {
return this.getObjects().indexOf(object) > -1;
},
/**
* Returns number representation of a collection complexity
* @return {Number} complexity
*/
complexity: function () {
return this.getObjects().reduce(function (memo, current) {
memo += current.complexity ? current.complexity() : 0;
return memo;
}, 0);
}
};
/**
* @namespace fabric.CommonMethods
*/
fabric.CommonMethods = {
/**
* Sets object's properties from options
* @param {Object} [options] Options object
*/
_setOptions: function(options) {
for (var prop in options) {
this.set(prop, options[prop]);
}
},
/**
* @private
* @param {Object} [filler] Options object
* @param {String} [property] property to set the Gradient to
*/
_initGradient: function(filler, property) {
if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) {
this.set(property, new fabric.Gradient(filler));
}
},
/**
* @private
* @param {Object} [filler] Options object
* @param {String} [property] property to set the Pattern to
* @param {Function} [callback] callback to invoke after pattern load
*/
_initPattern: function(filler, property, callback) {
if (filler && filler.source && !(filler instanceof fabric.Pattern)) {
this.set(property, new fabric.Pattern(filler, callback));
}
else {
callback && callback();
}
},
/**
* @private
* @param {Object} [options] Options object
*/
_initClipping: function(options) {
if (!options.clipTo || typeof options.clipTo !== 'string') {
return;
}
var functionBody = fabric.util.getFunctionBody(options.clipTo);
if (typeof functionBody !== 'undefined') {
this.clipTo = new Function('ctx', functionBody);
}
},
/**
* @private
*/
_setObject: function(obj) {
for (var prop in obj) {
this._set(prop, obj[prop]);
}
},
/**
* Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`.
* @param {String|Object} key Property name or object (if object, iterate over the object properties)
* @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one)
* @return {fabric.Object} thisArg
* @chainable
*/
set: function(key, value) {
if (typeof key === 'object') {
this._setObject(key);
}
else {
if (typeof value === 'function' && key !== 'clipTo') {
this._set(key, value(this.get(key)));
}
else {
this._set(key, value);
}
}
return this;
},
_set: function(key, value) {
this[key] = value;
},
/**
* Toggles specified property from `true` to `false` or from `false` to `true`
* @param {String} property Property to toggle
* @return {fabric.Object} thisArg
* @chainable
*/
toggle: function(property) {
var value = this.get(property);
if (typeof value === 'boolean') {
this.set(property, !value);
}
return this;
},
/**
* Basic getter
* @param {String} property Property name
* @return {*} value of a property
*/
get: function(property) {
return this[property];
}
};
(function(global) {
var sqrt = Math.sqrt,
atan2 = Math.atan2,
pow = Math.pow,
abs = Math.abs,
PiBy180 = Math.PI / 180,
PiBy2 = Math.PI / 2;
/**
* @namespace fabric.util
*/
fabric.util = {
/**
* Calculate the cos of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
cos: function(angle) {
if (angle === 0) { return 1; }
if (angle < 0) {
// cos(a) = cos(-a)
angle = -angle;
}
var angleSlice = angle / PiBy2;
switch (angleSlice) {
case 1: case 3: return 0;
case 2: return -1;
}
return Math.cos(angle);
},
/**
* Calculate the sin of an angle, avoiding returning floats for known results
* @static
* @memberOf fabric.util
* @param {Number} angle the angle in radians or in degree
* @return {Number}
*/
sin: function(angle) {
if (angle === 0) { return 0; }
var angleSlice = angle / PiBy2, sign = 1;
if (angle < 0) {
// sin(-a) = -sin(a)
sign = -1;
}
switch (angleSlice) {
case 1: return sign;
case 2: return 0;
case 3: return -sign;
}
return Math.sin(angle);
},
/**
* Removes value from an array.
* Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
* @static
* @memberOf fabric.util
* @param {Array} array
* @param {*} value
* @return {Array} original array
*/
removeFromArray: function(array, value) {
var idx = array.indexOf(value);
if (idx !== -1) {
array.splice(idx, 1);
}
return array;
},
/**
* Returns random number between 2 specified ones.
* @static
* @memberOf fabric.util
* @param {Number} min lower limit
* @param {Number} max upper limit
* @return {Number} random value (between min and max)
*/
getRandomInt: function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
/**
* Transforms degrees to radians.
* @static
* @memberOf fabric.util
* @param {Number} degrees value in degrees
* @return {Number} value in radians
*/
degreesToRadians: function(degrees) {
return degrees * PiBy180;
},
/**
* Transforms radians to degrees.
* @static
* @memberOf fabric.util
* @param {Number} radians value in radians
* @return {Number} value in degrees
*/
radiansToDegrees: function(radians) {
return radians / PiBy180;
},
/**
* Rotates `point` around `origin` with `radians`
* @static
* @memberOf fabric.util
* @param {fabric.Point} point The point to rotate
* @param {fabric.Point} origin The origin of the rotation
* @param {Number} radians The radians of the angle for the rotation
* @return {fabric.Point} The new rotated point
*/
rotatePoint: function(point, origin, radians) {
point.subtractEquals(origin);
var v = fabric.util.rotateVector(point, radians);
return new fabric.Point(v.x, v.y).addEquals(origin);
},
/**
* Rotates `vector` with `radians`
* @static
* @memberOf fabric.util
* @param {Object} vector The vector to rotate (x and y)
* @param {Number} radians The radians of the angle for the rotation
* @return {Object} The new rotated point
*/
rotateVector: function(vector, radians) {
var sin = fabric.util.sin(radians),
cos = fabric.util.cos(radians),
rx = vector.x * cos - vector.y * sin,
ry = vector.x * sin + vector.y * cos;
return {
x: rx,
y: ry
};
},
/**
* Apply transform t to point p
* @static
* @memberOf fabric.util
* @param {fabric.Point} p The point to transform
* @param {Array} t The transform
* @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied
* @return {fabric.Point} The transformed point
*/
transformPoint: function(p, t, ignoreOffset) {
if (ignoreOffset) {
return new fabric.Point(
t[0] * p.x + t[2] * p.y,
t[1] * p.x + t[3] * p.y
);
}
return new fabric.Point(
t[0] * p.x + t[2] * p.y + t[4],
t[1] * p.x + t[3] * p.y + t[5]
);
},
/**
* Returns coordinates of points's bounding rectangle (left, top, width, height)
* @param {Array} points 4 points array
* @return {Object} Object with left, top, width, height properties
*/
makeBoundingBoxFromPoints: function(points) {
var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x],
minX = fabric.util.array.min(xPoints),
maxX = fabric.util.array.max(xPoints),
width = maxX - minX,
yPoints = [points[0].y, points[1].y, points[2].y, points[3].y],
minY = fabric.util.array.min(yPoints),
maxY = fabric.util.array.max(yPoints),
height = maxY - minY;
return {
left: minX,
top: minY,
width: width,
height: height
};
},
/**
* Invert transformation t
* @static
* @memberOf fabric.util
* @param {Array} t The transform
* @return {Array} The inverted transform
*/
invertTransform: function(t) {
var a = 1 / (t[0] * t[3] - t[1] * t[2]),
r = [a * t[3], -a * t[1], -a * t[2], a * t[0]],
o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true);
r[4] = -o.x;
r[5] = -o.y;
return r;
},
/**
* A wrapper around Number#toFixed, which contrary to native method returns number, not string.
* @static
* @memberOf fabric.util
* @param {Number|String} number number to operate on
* @param {Number} fractionDigits number of fraction digits to "leave"
* @return {Number}
*/
toFixed: function(number, fractionDigits) {
return parseFloat(Number(number).toFixed(fractionDigits));
},
/**
* Converts from attribute value to pixel value if applicable.
* Returns converted pixels or original value not converted.
* @param {Number|String} value number to operate on
* @param {Number} fontSize
* @return {Number|String}
*/
parseUnit: function(value, fontSize) {
var unit = /\D{0,2}$/.exec(value),
number = parseFloat(value);
if (!fontSize) {
fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE;
}
switch (unit[0]) {
case 'mm':
return number * fabric.DPI / 25.4;
case 'cm':
return number * fabric.DPI / 2.54;
case 'in':
return number * fabric.DPI;
case 'pt':
return number * fabric.DPI / 72; // or * 4 / 3
case 'pc':
return number * fabric.DPI / 72 * 12; // or * 16
case 'em':
return number * fontSize;
default:
return number;
}
},
/**
* Function which always returns `false`.
* @static
* @memberOf fabric.util
* @return {Boolean}
*/
falseFunction: function() {
return false;
},
/**
* Returns klass "Class" object of given namespace
* @memberOf fabric.util
* @param {String} type Type of object (eg. 'circle')
* @param {String} namespace Namespace to get klass "Class" object from
* @return {Object} klass "Class"
*/
getKlass: function(type, namespace) {
// capitalize first letter only
type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1));
return fabric.util.resolveNamespace(namespace)[type];
},
/**
* Returns array of attributes for given svg that fabric parses
* @memberOf fabric.util
* @param {String} type Type of svg element (eg. 'circle')
* @return {Array} string names of supported attributes
*/
getSvgAttributes: function(type) {
var attributes = [
'instantiated_by_use',
'style',
'id',
'class'
];
switch (type) {
case 'linearGradient':
attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']);
break;
case 'radialGradient':
attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']);
break;
case 'stop':
attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']);
break;
}
return attributes;
},
/**
* Returns object of given namespace
* @memberOf fabric.util
* @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric'
* @return {Object} Object for given namespace (default fabric)
*/
resolveNamespace: function(namespace) {
if (!namespace) {
return fabric;
}
var parts = namespace.split('.'),
len = parts.length, i,
obj = global || fabric.window;
for (i = 0; i < len; ++i) {
obj = obj[parts[i]];
}
return obj;
},
/**
* Loads image element from given url and passes it to a callback
* @memberOf fabric.util
* @param {String} url URL representing an image
* @param {Function} callback Callback; invoked with loaded image
* @param {*} [context] Context to invoke callback in
* @param {Object} [crossOrigin] crossOrigin value to set image element to
*/
loadImage: function(url, callback, context, crossOrigin) {
if (!url) {
callback && callback.call(context, url);
return;
}
var img = fabric.util.createImage();
/** @ignore */
var onLoadCallback = function () {
callback && callback.call(context, img);
img = img.onload = img.onerror = null;
};
img.onload = onLoadCallback;
/** @ignore */
img.onerror = function() {
fabric.log('Error loading ' + img.src);
callback && callback.call(context, null, true);
img = img.onload = img.onerror = null;
};
// data-urls appear to be buggy with crossOrigin
// https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767
// see https://code.google.com/p/chromium/issues/detail?id=315152
// https://bugzilla.mozilla.org/show_bug.cgi?id=935069
if (url.indexOf('data') !== 0 && crossOrigin) {
img.crossOrigin = crossOrigin;
}
// IE10 / IE11-Fix: SVG contents from data: URI
// will only be available if the IMG is present
// in the DOM (and visible)
if (url.substring(0,14) === 'data:image/svg') {
img.onload = null;
fabric.util.loadImageInDom(img, onLoadCallback);
}
img.src = url;
},
/**
* Attaches SVG image with data: URL to the dom
* @memberOf fabric.util
* @param {Object} img Image object with data:image/svg src
* @param {Function} callback Callback; invoked with loaded image
* @return {Object} DOM element (div containing the SVG image)
*/
loadImageInDom: function(img, onLoadCallback) {
var div = fabric.document.createElement('div');
div.style.width = div.style.height = '1px';
div.style.left = div.style.top = '-100%';
div.style.position = 'absolute';
div.appendChild(img);
fabric.document.querySelector('body').appendChild(div);
/**
* Wrap in function to:
* 1. Call existing callback
* 2. Cleanup DOM
*/
img.onload = function () {
onLoadCallback();
div.parentNode.removeChild(div);
div = null;
};
},
/**
* Creates corresponding fabric instances from their object representations
* @static
* @memberOf fabric.util
* @param {Array} objects Objects to enliven
* @param {Function} callback Callback to invoke when all objects are created
* @param {String} namespace Namespace to get klass "Class" object from
* @param {Function} reviver Method for further parsing of object elements,
* called after each fabric object created.
*/
enlivenObjects: function(objects, callback, namespace, reviver) {
objects = objects || [];
function onLoaded() {
if (++numLoadedObjects === numTotalObjects) {
callback && callback(enlivenedObjects);
}
}
var enlivenedObjects = [],
numLoadedObjects = 0,
numTotalObjects = objects.length;
if (!numTotalObjects) {
callback && callback(enlivenedObjects);
return;
}
objects.forEach(function (o, index) {
// if sparse array
if (!o || !o.type) {
onLoaded();
return;
}
var klass = fabric.util.getKlass(o.type, namespace);
klass.fromObject(o, function (obj, error) {
error || (enlivenedObjects[index] = obj);
reviver && reviver(o, obj, error);
onLoaded();
});
});
},
/**
* Create and wait for loading of patterns
* @static
* @memberOf fabric.util
* @param {Array} patterns Objects to enliven
* @param {Function} callback Callback to invoke when all objects are created
* called after each fabric object created.
*/
enlivenPatterns: function(patterns, callback) {
patterns = patterns || [];
function onLoaded() {
if (++numLoadedPatterns === numPatterns) {
callback && callback(enlivenedPatterns);
}
}
var enlivenedPatterns = [],
numLoadedPatterns = 0,
numPatterns = patterns.length;
if (!numPatterns) {
callback && callback(enlivenedPatterns);
return;
}
patterns.forEach(function (p, index) {
if (p && p.source) {
new fabric.Pattern(p, function(pattern) {
enlivenedPatterns[index] = pattern;
onLoaded();
});
}
else {
enlivenedPatterns[index] = p;
onLoaded();
}
});
},
/**
* Groups SVG elements (usually those retrieved from SVG document)
* @static
* @memberOf fabric.util
* @param {Array} elements SVG elements to group
* @param {Object} [options] Options object
* @param {String} path Value to set sourcePath to
* @return {fabric.Object|fabric.Group}
*/
groupSVGElements: function(elements, options, path) {
var object;
if (elements.length === 1) {
return elements[0];
}
if (options) {
if (options.width && options.height) {
options.centerPoint = {
x: options.width / 2,
y: options.height / 2
};
}
else {
delete options.width;
delete options.height;
}
}
object = new fabric.Group(elements, options);
if (typeof path !== 'undefined') {
object.sourcePath = path;
}
return object;
},
/**
* Populates an object with properties of another object
* @static
* @memberOf fabric.util
* @param {Object} source Source object
* @param {Object} destination Destination object
* @return {Array} properties Properties names to include
*/
populateWithProperties: function(source, destination, properties) {
if (properties && Object.prototype.toString.call(properties) === '[object Array]') {
for (var i = 0, len = properties.length; i < len; i++) {
if (properties[i] in source) {
destination[properties[i]] = source[properties[i]];
}
}
}
},
/**
* Draws a dashed line between two points
*
* This method is used to draw dashed line around selection area.
* See <a href="http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas">dotted stroke in canvas</a>
*
* @param {CanvasRenderingContext2D} ctx context
* @param {Number} x start x coordinate
* @param {Number} y start y coordinate
* @param {Number} x2 end x coordinate
* @param {Number} y2 end y coordinate
* @param {Array} da dash array pattern
*/
drawDashedLine: function(ctx, x, y, x2, y2, da) {
var dx = x2 - x,
dy = y2 - y,
len = sqrt(dx * dx + dy * dy),
rot = atan2(dy, dx),
dc = da.length,
di = 0,
draw = true;
ctx.save();
ctx.translate(x, y);
ctx.moveTo(0, 0);
ctx.rotate(rot);
x = 0;
while (len > x) {
x += da[di++ % dc];
if (x > len) {
x = len;
}
ctx[draw ? 'lineTo' : 'moveTo'](x, 0);
draw = !draw;
}
ctx.restore();
},
/**
* Creates canvas element
* @static
* @memberOf fabric.util
* @return {CanvasElement} initialized canvas element
*/
createCanvasElement: function() {
return fabric.document.createElement('canvas');
},
/**
* Creates image element (works on client and node)
* @static
* @memberOf fabric.util
* @return {HTMLImageElement} HTML image element
*/
createImage: function() {
return fabric.document.createElement('img');
},
/**
* @static
* @memberOf fabric.util
* @deprecated since 2.0.0
* @param {fabric.Object} receiver Object implementing `clipTo` method
* @param {CanvasRenderingContext2D} ctx Context to clip
*/
clipContext: function(receiver, ctx) {
ctx.save();
ctx.beginPath();
receiver.clipTo(ctx);
ctx.clip();
},
/**
* Multiply matrix A by matrix B to nest transformations
* @static
* @memberOf fabric.util
* @param {Array} a First transformMatrix
* @param {Array} b Second transformMatrix
* @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices
* @return {Array} The product of the two transform matrices
*/
multiplyTransformMatrices: function(a, b, is2x2) {
// Matrix multiply a * b
return [
a[0] * b[0] + a[2] * b[1],
a[1] * b[0] + a[3] * b[1],
a[0] * b[2] + a[2] * b[3],
a[1] * b[2] + a[3] * b[3],
is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
];
},
/**
* Decomposes standard 2x2 matrix into transform componentes
* @static
* @memberOf fabric.util
* @param {Array} a transformMatrix
* @return {Object} Components of transform
*/
qrDecompose: function(a) {
var angle = atan2(a[1], a[0]),
denom = pow(a[0], 2) + pow(a[1], 2),
scaleX = sqrt(denom),
scaleY = (a[0] * a[3] - a[2] * a [1]) / scaleX,
skewX = atan2(a[0] * a[2] + a[1] * a [3], denom);
return {
angle: angle / PiBy180,
scaleX: scaleX,
scaleY: scaleY,
skewX: skewX / PiBy180,
skewY: 0,
translateX: a[4],
translateY: a[5]
};
},
customTransformMatrix: function(scaleX, scaleY, skewX) {
var skewMatrixX = [1, 0, abs(Math.tan(skewX * PiBy180)), 1],
scaleMatrix = [abs(scaleX), 0, 0, abs(scaleY)];
return fabric.util.multiplyTransformMatrices(scaleMatrix, skewMatrixX, true);
},
resetObjectTransform: function (target) {
target.scaleX = 1;
target.scaleY = 1;
target.skewX = 0;
target.skewY = 0;
target.flipX = false;
target.flipY = false;
target.rotate(0);
},
/**
* Returns string representation of function body
* @param {Function} fn Function to get body of
* @return {String} Function body
*/
getFunctionBody: function(fn) {
return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1];
},
/**
* Returns true if context has transparent pixel
* at specified location (taking tolerance into account)
* @param {CanvasRenderingContext2D} ctx context
* @param {Number} x x coordinate
* @param {Number} y y coordinate
* @param {Number} tolerance Tolerance
*/
isTransparent: function(ctx, x, y, tolerance) {
// If tolerance is > 0 adjust start coords to take into account.
// If moves off Canvas fix to 0
if (tolerance > 0) {
if (x > tolerance) {
x -= tolerance;
}
else {
x = 0;
}
if (y > tolerance) {
y -= tolerance;
}
else {
y = 0;
}
}
var _isTransparent = true, i, temp,
imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1),
l = imageData.data.length;
// Split image data - for tolerance > 1, pixelDataSize = 4;
for (i = 3; i < l; i += 4) {
temp = imageData.data[i];
_isTransparent = temp <= 0;
if (_isTransparent === false) {
break; // Stop if colour found
}
}
imageData = null;
return _isTransparent;
},
/**
* Parse preserveAspectRatio attribute from element
* @param {string} attribute to be parsed
* @return {Object} an object containing align and meetOrSlice attribute
*/
parsePreserveAspectRatioAttribute: function(attribute) {
var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid',
aspectRatioAttrs = attribute.split(' '), align;
if (aspectRatioAttrs && aspectRatioAttrs.length) {
meetOrSlice = aspectRatioAttrs.pop();
if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
align = meetOrSlice;
meetOrSlice = 'meet';
}
else if (aspectRatioAttrs.length) {
align = aspectRatioAttrs.pop();
}
}
//divide align in alignX and alignY
alignX = align !== 'none' ? align.slice(1, 4) : 'none';
alignY = align !== 'none' ? align.slice(5, 8) : 'none';
return {
meetOrSlice: meetOrSlice,
alignX: alignX,
alignY: alignY
};
},
/**
* Clear char widths cache for a font family.
* @memberOf fabric.util
* @param {String} [fontFamily] font family to clear
*/
clearFabricFontCache: function(fontFamily) {
if (!fontFamily) {
fabric.charWidthsCache = { };
}
else if (fabric.charWidthsCache[fontFamily]) {
delete fabric.charWidthsCache[fontFamily];
}
},
/**
* Clear char widths cache for a font family.
* @memberOf fabric.util
* @param {Number} ar aspect ratio
* @param {Number} maximumArea Maximum area you want to achieve
* @return {Object.x} Limited dimensions by X
* @return {Object.y} Limited dimensions by Y
*/
limitDimsByArea: function(ar, maximumArea) {
var roughWidth = Math.sqrt(maximumArea * ar),
perfLimitSizeY = Math.floor(maximumArea / roughWidth);
return { x: Math.floor(roughWidth), y: perfLimitSizeY };
},
capValue: function(min, value, max) {
return Math.max(min, Math.min(value, max));
},
findScaleToFit: function(source, destination) {
return Math.min(destination.width / source.width, destination.height / source.height);
},
findScaleToCover: function(source, destination) {
return Math.max(destination.width / source.width, destination.height / source.height);
}
};
})(typeof exports !== 'undefined' ? exports : this);
(function() {
var arcToSegmentsCache = { },
segmentToBezierCache = { },
boundsOfCurveCache = { },
_join = Array.prototype.join;
/* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
* by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
* http://mozilla.org/MPL/2.0/
*/
function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
var argsString = _join.call(arguments);
if (arcToSegmentsCache[argsString]) {
return arcToSegmentsCache[argsString];
}
var PI = Math.PI, th = rotateX * PI / 180,
sinTh = fabric.util.sin(th),
cosTh = fabric.util.cos(th),
fromX = 0, fromY = 0;
rx = Math.abs(rx);
ry = Math.abs(ry);
var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
root = 0;
if (pl < 0) {
var s = Math.sqrt(1 - pl / (rx2 * ry2));
rx *= s;
ry *= s;
}
else {
root = (large === sweep ? -1.0 : 1.0) *
Math.sqrt( pl / (rx2 * py2 + ry2 * px2));
}
var cx = root * rx * py / ry,
cy = -root * ry * px / rx,
cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);
if (sweep === 0 && dtheta > 0) {
dtheta -= 2 * PI;
}
else if (sweep === 1 && dtheta < 0) {
dtheta += 2 * PI;
}
// Convert into cubic bezier segments <= 90deg
var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
result = [], mDelta = dtheta / segments,
mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
th3 = mTheta + mDelta;
for (var i = 0; i < segments; i++) {
result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
fromX = result[i][4];
fromY = result[i][5];
mTheta = th3;
th3 += mDelta;
}
arcToSegmentsCache[argsString] = result;
return result;
}
function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
var argsString2 = _join.call(arguments);
if (segmentToBezierCache[argsString2]) {
return segmentToBezierCache[argsString2];
}
var costh2 = fabric.util.cos(th2),
sinth2 = fabric.util.sin(th2),
costh3 = fabric.util.cos(th3),
sinth3 = fabric.util.sin(th3),
toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2),
cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2),
cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);
segmentToBezierCache[argsString2] = [
cp1X, cp1Y,
cp2X, cp2Y,
toX, toY
];
return segmentToBezierCache[argsString2];
}
/*
* Private
*/
function calcVectorAngle(ux, uy, vx, vy) {
var ta = Math.atan2(uy, ux),
tb = Math.atan2(vy, vx);
if (tb >= ta) {
return tb - ta;
}
else {
return 2 * Math.PI - (ta - tb);
}
}
/**
* Draws arc
* @param {CanvasRenderingContext2D} ctx
* @param {Number} fx
* @param {Number} fy
* @param {Array} coords
*/
fabric.util.drawArc = function(ctx, fx, fy, coords) {
var rx = coords[0],
ry = coords[1],
rot = coords[2],
large = coords[3],
sweep = coords[4],
tx = coords[5],
ty = coords[6],
segs = [[], [], [], []],
segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
for (var i = 0, len = segsNorm.length; i < len; i++) {
segs[i][0] = segsNorm[i][0] + fx;
segs[i][1] = segsNorm[i][1] + fy;
segs[i][2] = segsNorm[i][2] + fx;
segs[i][3] = segsNorm[i][3] + fy;
segs[i][4] = segsNorm[i][4] + fx;
segs[i][5] = segsNorm[i][5] + fy;
ctx.bezierCurveTo.apply(ctx, segs[i]);
}
};
/**
* Calculate bounding box of a elliptic-arc
* @param {Number} fx start point of arc
* @param {Number} fy
* @param {Number} rx horizontal radius
* @param {Number} ry vertical radius
* @param {Number} rot angle of horizontal axe
* @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
* @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
* @param {Number} tx end point of arc
* @param {Number} ty
*/
fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {
var fromX = 0, fromY = 0, bound, bounds = [],
segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);
for (var i = 0, len = segs.length; i < len; i++) {
bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy });
bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy });
fromX = segs[i][4];
fromY = segs[i][5];
}
return bounds;
};
/**
* Calculate bounding box of a beziercurve
* @param {Number} x0 starting point
* @param {Number} y0
* @param {Number} x1 first control point
* @param {Number} y1
* @param {Number} x2 secondo control point
* @param {Number} y2
* @param {Number} x3 end of beizer
* @param {Number} y3
*/
// taken from http://jsbin.com/ivomiq/56/edit no credits available for that.
function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
var argsString = _join.call(arguments);
if (boundsOfCurveCache[argsString]) {
return boundsOfCurveCache[argsString];
}
var sqrt = Math.sqrt,
min = Math.min, max = Math.max,
abs = Math.abs, tvalues = [],
bounds = [[], []],
a, b, c, t, t1, t2, b2ac, sqrtb2ac;
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
for (var i = 0; i < 2; ++i) {
if (i > 0) {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (abs(a) < 1e-12) {
if (abs(b) < 1e-12) {
continue;
}
t = -c / b;
if (0 < t && t < 1) {
tvalues.push(t);
}
continue;
}
b2ac = b * b - 4 * c * a;
if (b2ac < 0) {
continue;
}
sqrtb2ac = sqrt(b2ac);
t1 = (-b + sqrtb2ac) / (2 * a);
if (0 < t1 && t1 < 1) {
tvalues.push(t1);
}
t2 = (-b - sqrtb2ac) / (2 * a);
if (0 < t2 && t2 < 1) {
tvalues.push(t2);
}
}
var x, y, j = tvalues.length, jlen = j, mt;
while (j--) {
t = tvalues[j];
mt = 1 - t;
x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
bounds[0][j] = x;
y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
bounds[1][j] = y;
}
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
var result = [
{
x: min.apply(null, bounds[0]),
y: min.apply(null, bounds[1])
},
{
x: max.apply(null, bounds[0]),
y: max.apply(null, bounds[1])
}
];
boundsOfCurveCache[argsString] = result;
return result;
}
fabric.util.getBoundsOfCurve = getBoundsOfCurve;
})();
(function() {
var slice = Array.prototype.slice;
/**
* Invokes method on all items in a given array
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} method Name of a method to invoke
* @return {Array}
*/
function invoke(array, method) {
var args = slice.call(arguments, 2), result = [];
for (var i = 0, len = array.length; i < len; i++) {
result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]);
}
return result;
}
/**
* Finds maximum value in array (not necessarily "first" one)
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} byProperty
* @return {*}
*/
function max(array, byProperty) {
return find(array, byProperty, function(value1, value2) {
return value1 >= value2;
});
}
/**
* Finds minimum value in array (not necessarily "first" one)
* @memberOf fabric.util.array
* @param {Array} array Array to iterate over
* @param {String} byProperty
* @return {*}
*/
function min(array, byProperty) {
return find(array, byProperty, function(value1, value2) {
return value1 < value2;
});
}
/**
* @private
*/
function fill(array, value) {
var k = array.length;
while (k--) {
array[k] = value;
}
return array;
}
/**
* @private
*/
function find(array, byProperty, condition) {
if (!array || array.length === 0) {
return;
}
var i = array.length - 1,
result = byProperty ? array[i][byProperty] : array[i];
if (byProperty) {
while (i--) {
if (condition(array[i][byProperty], result)) {
result = array[i][byProperty];
}
}
}
else {
while (i--) {
if (condition(array[i], result)) {
result = array[i];
}
}
}
return result;
}
/**
* @namespace fabric.util.array
*/
fabric.util.array = {
fill: fill,
invoke: invoke,
min: min,
max: max
};
})();
(function() {
/**
* Copies all enumerable properties of one js object to another
* Does not clone or extend fabric.Object subclasses.
* @memberOf fabric.util.object
* @param {Object} destination Where to copy to
* @param {Object} source Where to copy from
* @return {Object}
*/
function extend(destination, source, deep) {
// JScript DontEnum bug is not taken care of
// the deep clone is for internal use, is not meant to avoid
// javascript traps or cloning html element or self referenced objects.
if (deep) {
if (!fabric.isLikelyNode && source instanceof Element) {
// avoid cloning deep images, canvases,
destination = source;
}
else if (source instanceof Array) {
destination = [];
for (var i = 0, len = source.length; i < len; i++) {
destination[i] = extend({ }, source[i], deep);
}
}
else if (source && typeof source === 'object') {
for (var property in source) {
if (source.hasOwnProperty(property)) {
destination[property] = extend({ }, source[property], deep);
}
}
}
else {
// this sounds odd for an extend but is ok for recursive use
destination = source;
}
}
else {
for (var property in source) {
destination[property] = source[property];
}
}
return destination;
}
/**
* Creates an empty object and copies all enumerable properties of another object to it
* @memberOf fabric.util.object
* TODO: this function return an empty object if you try to clone null
* @param {Object} object Object to clone
* @return {Object}
*/
function clone(object, deep) {
return extend({ }, object, deep);
}
/** @namespace fabric.util.object */
fabric.util.object = {
extend: extend,
clone: clone
};
fabric.util.object.extend(fabric.util, fabric.Observable);
})();
(function() {
/**
* Camelizes a string
* @memberOf fabric.util.string
* @param {String} string String to camelize
* @return {String} Camelized version of a string
*/
function camelize(string) {
return string.replace(/-+(.)?/g, function(match, character) {
return character ? character.toUpperCase() : '';
});
}
/**
* Capitalizes a string
* @memberOf fabric.util.string
* @param {String} string String to capitalize
* @param {Boolean} [firstLetterOnly] If true only first letter is capitalized
* and other letters stay untouched, if false first letter is capitalized
* and other letters are converted to lowercase.
* @return {String} Capitalized version of a string
*/
function capitalize(string, firstLetterOnly) {
return string.charAt(0).toUpperCase() +
(firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase());
}
/**
* Escapes XML in a string
* @memberOf fabric.util.string
* @param {String} string String to escape
* @return {String} Escaped version of a string
*/
function escapeXml(string) {
return string.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
/**
* Divide a string in the user perceived single units
* @memberOf fabric.util.string
* @param {String} textstring String to escape
* @return {Array} array containing the graphemes
*/
function graphemeSplit(textstring) {
var i = 0, chr, graphemes = [];
for (i = 0, chr; i < textstring.length; i++) {
if ((chr = getWholeChar(textstring, i)) === false) {
continue;
}
graphemes.push(chr);
}
return graphemes;
}
// taken from mdn in the charAt doc page.
function getWholeChar(str, i) {
var code = str.charCodeAt(i);
if (isNaN(code)) {
return ''; // Position not found
}
if (code < 0xD800 || code > 0xDFFF) {
return str.charAt(i);
}
// High surrogate (could change last hex to 0xDB7F to treat high private
// surrogates as single characters)
if (0xD800 <= code && code <= 0xDBFF) {
if (str.length <= (i + 1)) {
throw 'High surrogate without following low surrogate';
}
var next = str.charCodeAt(i + 1);
if (0xDC00 > next || next > 0xDFFF) {
throw 'High surrogate without following low surrogate';
}
return str.charAt(i) + str.charAt(i + 1);
}
// Low surrogate (0xDC00 <= code && code <= 0xDFFF)
if (i === 0) {
throw 'Low surrogate without preceding high surrogate';
}
var prev = str.charCodeAt(i - 1);
// (could change last hex to 0xDB7F to treat high private
// surrogates as single characters)
if (0xD800 > prev || prev > 0xDBFF) {
throw 'Low surrogate without preceding high surrogate';
}
// We can pass over low surrogates now as the second component
// in a pair which we have already processed
return false;
}
/**
* String utilities
* @namespace fabric.util.string
*/
fabric.util.string = {
camelize: camelize,
capitalize: capitalize,
escapeXml: escapeXml,
graphemeSplit: graphemeSplit
};
})();
(function() {
var slice = Array.prototype.slice, emptyFunction = function() { },
IS_DONTENUM_BUGGY = (function() {
for (var p in { toString: 1 }) {
if (p === 'toString') {
return false;
}
}
return true;
})(),
/** @ignore */
addMethods = function(klass, source, parent) {
for (var property in source) {
if (property in klass.prototype &&
typeof klass.prototype[property] === 'function' &&
(source[property] + '').indexOf('callSuper') > -1) {
klass.prototype[property] = (function(property) {
return function() {
var superclass = this.constructor.superclass;
this.constructor.superclass = parent;
var returnValue = source[property].apply(this, arguments);
this.constructor.superclass = superclass;
if (property !== 'initialize') {
return returnValue;
}
};
})(property);
}
else {
klass.prototype[property] = source[property];
}
if (IS_DONTENUM_BUGGY) {
if (source.toString !== Object.prototype.toString) {
klass.prototype.toString = source.toString;
}
if (source.valueOf !== Object.prototype.valueOf) {
klass.prototype.valueOf = source.valueOf;
}
}
}
};
function Subclass() { }
function callSuper(methodName) {
var parentMethod = null,
_this = this;
// climb prototype chain to find method not equal to callee's method
while (_this.constructor.superclass) {
var superClassMethod = _this.constructor.superclass.prototype[methodName];
if (_this[methodName] !== superClassMethod) {
parentMethod = superClassMethod;
break;
}
// eslint-disable-next-line
_this = _this.constructor.superclass.prototype;
}
if (!parentMethod) {
return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this);
}
return (arguments.length > 1)
? parentMethod.apply(this, slice.call(arguments, 1))
: parentMethod.call(this);
}
/**
* Helper for creation of "classes".
* @memberOf fabric.util
* @param {Function} [parent] optional "Class" to inherit from
* @param {Object} [properties] Properties shared by all instances of this class
* (be careful modifying objects defined here as this would affect all instances)
*/
function createClass() {
var parent = null,
properties = slice.call(arguments, 0);
if (typeof properties[0] === 'function') {
parent = properties.shift();
}
function klass() {
this.initialize.apply(this, arguments);
}
klass.superclass = parent;
klass.subclasses = [];
if (parent) {
Subclass.prototype = parent.prototype;
klass.prototype = new Subclass();
parent.subclasses.push(klass);
}
for (var i = 0, length = properties.length; i < length; i++) {
addMethods(klass, properties[i], parent);
}
if (!klass.prototype.initialize) {
klass.prototype.initialize = emptyFunction;
}
klass.prototype.constructor = klass;
klass.prototype.callSuper = callSuper;
return klass;
}
fabric.util.createClass = createClass;
})();
(function () {
var unknown = 'unknown';
/* EVENT HANDLING */
function areHostMethods(object) {
var methodNames = Array.prototype.slice.call(arguments, 1),
t, i, len = methodNames.length;
for (i = 0; i < len; i++) {
t = typeof object[methodNames[i]];
if (!(/^(?:function|object|unknown)$/).test(t)) {
return false;
}
}
return true;
}
/** @ignore */
var getElement,
setElement,
getUniqueId = (function () {
var uid = 0;
return function (element) {
return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
};
})();
(function () {
var elements = { };
/** @ignore */
getElement = function (uid) {
return elements[uid];
};
/** @ignore */
setElement = function (uid, element) {
elements[uid] = element;
};
})();
function createListener(uid, handler) {
return {
handler: handler,
wrappedHandler: createWrappedHandler(uid, handler)
};
}
function createWrappedHandler(uid, handler) {
return function (e) {
handler.call(getElement(uid), e || fabric.window.event);
};
}
function createDispatcher(uid, eventName) {
return function (e) {
if (handlers[uid] && handlers[uid][eventName]) {
var handlersForEvent = handlers[uid][eventName];
for (var i = 0, len = handlersForEvent.length; i < len; i++) {
handlersForEvent[i].call(this, e || fabric.window.event);
}
}
};
}
var shouldUseAddListenerRemoveListener = (
areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
shouldUseAttachEventDetachEvent = (
areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
// IE branch
listeners = { },
// DOM L0 branch
handlers = { },
addListener, removeListener;
if (shouldUseAddListenerRemoveListener) {
/** @ignore */
addListener = function (element, eventName, handler, options) {
// since ie10 or ie9 can use addEventListener but they do not support options, i need to check
element && element.addEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
};
/** @ignore */
removeListener = function (element, eventName, handler, options) {
element && element.removeEventListener(eventName, handler, shouldUseAttachEventDetachEvent ? false : options);
};
}
else if (shouldUseAttachEventDetachEvent) {
/** @ignore */
addListener = function (element, eventName, handler) {
if (!element) {
return;
}
var uid = getUniqueId(element);
setElement(uid, element);
if (!listeners[uid]) {
listeners[uid] = { };
}
if (!listeners[uid][eventName]) {
listeners[uid][eventName] = [];
}
var listener = createListener(uid, handler);
listeners[uid][eventName].push(listener);
element.attachEvent('on' + eventName, listener.wrappedHandler);
};
/** @ignore */
removeListener = function (element, eventName, handler) {
if (!element) {
return;
}
var uid = getUniqueId(element), listener;
if (listeners[uid] && listeners[uid][eventName]) {
for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
listener = listeners[uid][eventName][i];
if (listener && listener.handler === handler) {
element.detachEvent('on' + eventName, listener.wrappedHandler);
listeners[uid][eventName][i] = null;
}
}
}
};
}
else {
/** @ignore */
addListener = function (element, eventName, handler) {
if (!element) {
return;
}
var uid = getUniqueId(element);
if (!handlers[uid]) {
handlers[uid] = { };
}
if (!handlers[uid][eventName]) {
handlers[uid][eventName] = [];
var existingHandler = element['on' + eventName];
if (existingHandler) {
handlers[uid][eventName].push(existingHandler);
}
element['on' + eventName] = createDispatcher(uid, eventName);
}
handlers[uid][eventName].push(handler);
};
/** @ignore */
removeListener = function (element, eventName, handler) {
if (!element) {
return;
}
var uid = getUniqueId(element);
if (handlers[uid] && handlers[uid][eventName]) {
var handlersForEvent = handlers[uid][eventName];
for (var i = 0, len = handlersForEvent.length; i < len; i++) {
if (handlersForEvent[i] === handler) {
handlersForEvent.splice(i, 1);
}
}
}
};
}
/**
* Adds an event listener to an element
* @function
* @memberOf fabric.util
* @param {HTMLElement} element
* @param {String} eventName
* @param {Function} handler
*/
fabric.util.addListener = addListener;
/**
* Removes an event listener from an element
* @function
* @memberOf fabric.util
* @param {HTMLElement} element
* @param {String} eventName
* @param {Function} handler
*/
fabric.util.removeListener = removeListener;
/**
* Cross-browser wrapper for getting event's coordinates
* @memberOf fabric.util
* @param {Event} event Event object
*/
function getPointer(event) {
event || (event = fabric.window.event);
var element = event.target ||
(typeof event.srcElement !== unknown ? event.srcElement : null),
scroll = fabric.util.getScrollLeftTop(element);
return {
x: pointerX(event) + scroll.left,
y: pointerY(event) + scroll.top
};
}
var pointerX = function(event) {
return event.clientX;
},
pointerY = function(event) {
return event.clientY;
};
function _getPointer(event, pageProp, clientProp) {
var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches';
var pointer, eventTouchProp = event[touchProp];
if (eventTouchProp && eventTouchProp[0]) {
pointer = eventTouchProp[0][clientProp];
}
if (typeof pointer === 'undefined') {
pointer = event[clientProp];
}
return pointer;
}
if (fabric.isTouchSupported) {
pointerX = function(event) {
return _getPointer(event, 'pageX', 'clientX');
};
pointerY = function(event) {
return _getPointer(event, 'pageY', 'clientY');
};
}
fabric.util.getPointer = getPointer;
})();
(function () {
/**
* Cross-browser wrapper for setting element's style
* @memberOf fabric.util
* @param {HTMLElement} element
* @param {Object} styles
* @return {HTMLElement} Element that was passed as a first argument
*/
function setStyle(element, styles) {
var elementStyle = element.style;
if (!elementStyle) {
return element;
}
if (typeof styles === 'string') {
element.style.cssText += ';' + styles;
return styles.indexOf('opacity') > -1
? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1])
: element;
}
for (var property in styles) {
if (property === 'opacity') {
setOpacity(element, styles[property]);
}
else {
var normalizedProperty = (property === 'float' || property === 'cssFloat')
? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat')
: property;
elementStyle[normalizedProperty] = styles[property];
}
}
return element;
}
var parseEl = fabric.document.createElement('div'),
supportsOpacity = typeof parseEl.style.opacity === 'string',
supportsFilters = typeof parseEl.style.filter === 'string',
reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,
/** @ignore */
setOpacity = function (element) { return element; };
if (supportsOpacity) {
/** @ignore */
setOpacity = function(element, value) {
element.style.opacity = value;
return element;
};
}
else if (supportsFilters) {
/** @ignore */
setOpacity = function(element, value) {
var es = element.style;
if (element.currentStyle && !element.currentStyle.hasLayout) {
es.zoom = 1;
}
if (reOpacity.test(es.filter)) {
value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')');
es.filter = es.filter.replace(reOpacity, value);
}
else {
es.filter += ' alpha(opacity=' + (value * 100) + ')';
}
return element;
};
}
fabric.util.setStyle = setStyle;
})();
(function() {
var _slice = Array.prototype.slice;
/**
* Takes id and returns an element with that id (if one exists in a document)
* @memberOf fabric.util
* @param {String|HTMLElement} id
* @return {HTMLElement|null}
*/
function getById(id) {
return typeof id === 'string' ? fabric.document.getElementById(id) : id;
}
var sliceCanConvertNodelists,
/**
* Converts an array-like object (e.g. arguments or NodeList) to an array
* @memberOf fabric.util
* @param {Object} arrayLike
* @return {Array}
*/
toArray = function(arrayLike) {
return _slice.call(arrayLike, 0);
};
try {
sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
}
catch (err) { }
if (!sliceCanConvertNodelists) {
toArray = function(arrayLike) {
var arr = new Array(arrayLike.length), i = arrayLike.length;
while (i--) {
arr[i] = arrayLike[i];
}
return arr;
};
}
/**
* Creates specified element with specified attributes
* @memberOf fabric.util
* @param {String} tagName Type of an element to create
* @param {Object} [attributes] Attributes to set on an element
* @return {HTMLElement} Newly created element
*/
function makeElement(tagName, attributes) {
var el = fabric.document.createElement(tagName);
for (var prop in attributes) {
if (prop === 'class') {
el.className = attributes[prop];
}
else if (prop === 'for') {
el.htmlFor = attributes[prop];
}
else {
el.setAttribute(prop, attributes[prop]);
}
}
return el;
}
/**
* Adds class to an element
* @memberOf fabric.util
* @param {HTMLElement} element Element to add class to
* @param {String} className Class to add to an element
*/
function addClass(element, className) {
if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
element.className += (element.className ? ' ' : '') + className;
}
}
/**
* Wraps element with another element
* @memberOf fabric.util
* @param {HTMLElement} element Element to wrap
* @param {HTMLElement|String} wrapper Element to wrap with
* @param {Object} [attributes] Attributes to set on a wrapper
* @return {HTMLElement} wrapper
*/
function wrapElement(element, wrapper, attributes) {
if (typeof wrapper === 'string') {
wrapper = makeElement(wrapper, attributes);
}
if (element.parentNode) {
element.parentNode.replaceChild(wrapper, element);
}
wrapper.appendChild(element);
return wrapper;
}
/**
* Returns element scroll offsets
* @memberOf fabric.util
* @param {HTMLElement} element Element to operate on
* @return {Object} Object with left/top values
*/
function getScrollLeftTop(element) {
var left = 0,
top = 0,
docElement = fabric.document.documentElement,
body = fabric.document.body || {
scrollLeft: 0, scrollTop: 0
};
// While loop checks (and then sets element to) .parentNode OR .host
// to account for ShadowDOM. We still want to traverse up out of ShadowDOM,
// but the .parentNode of a root ShadowDOM node will always be null, instead
// it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938
while (element && (element.parentNode || element.host)) {
// Set element to element parent, or 'host' in case of ShadowDOM
element = element.parentNode || element.host;
if (element === fabric.document) {
left = body.scrollLeft || docElement.scrollLeft || 0;
top = body.scrollTop || docElement.scrollTop || 0;
}
else {
left += element.scrollLeft || 0;
top += element.scrollTop || 0;
}
if (element.nodeType === 1 && element.style.position === 'fixed') {
break;
}
}
return { left: left, top: top };
}
/**
* Returns offset for a given element
* @function
* @memberOf fabric.util
* @param {HTMLElement} element Element to get offset for
* @return {Object} Object with "left" and "top" properties
*/
function getElementOffset(element) {
var docElem,
doc = element && element.ownerDocument,
box = { left: 0, top: 0 },
offset = { left: 0, top: 0 },
scrollLeftTop,
offsetAttributes = {
borderLeftWidth: 'left',
borderTopWidth: 'top',
paddingLeft: 'left',
paddingTop: 'top'
};
if (!doc) {
return offset;
}
for (var attr in offsetAttributes) {
offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
}
docElem = doc.documentElement;
if ( typeof element.getBoundingClientRect !== 'undefined' ) {
box = element.getBoundingClientRect();
}
scrollLeftTop = getScrollLeftTop(element);
return {
left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top
};
}
/**
* Returns style attribute value of a given element
* @memberOf fabric.util
* @param {HTMLElement} element Element to get style attribute for
* @param {String} attr Style attribute to get for element
* @return {String} Style attribute value of the given element.
*/
var getElementStyle;
if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
getElementStyle = function(element, attr) {
var style = fabric.document.defaultView.getComputedStyle(element, null);
return style ? style[attr] : undefined;
};
}
else {
getElementStyle = function(element, attr) {
var value = element.style[attr];
if (!value && element.currentStyle) {
value = element.currentStyle[attr];
}
return value;
};
}
(function () {
var style = fabric.document.documentElement.style,
selectProp = 'userSelect' in style
? 'userSelect'
: 'MozUserSelect' in style
? 'MozUserSelect'
: 'WebkitUserSelect' in style
? 'WebkitUserSelect'
: 'KhtmlUserSelect' in style
? 'KhtmlUserSelect'
: '';
/**
* Makes element unselectable
* @memberOf fabric.util
* @param {HTMLElement} element Element to make unselectable
* @return {HTMLElement} Element that was passed in
*/
function makeElementUnselectable(element) {
if (typeof element.onselectstart !== 'undefined') {
element.onselectstart = fabric.util.falseFunction;
}
if (selectProp) {
element.style[selectProp] = 'none';
}
else if (typeof element.unselectable === 'string') {
element.unselectable = 'on';
}
return element;
}
/**
* Makes element selectable
* @memberOf fabric.util
* @param {HTMLElement} element Element to make selectable
* @return {HTMLElement} Element that was passed in
*/
function makeElementSelectable(element) {
if (typeof element.onselectstart !== 'undefined') {
element.onselectstart = null;
}
if (selectProp) {
element.style[selectProp] = '';
}
else if (typeof element.unselectable === 'string') {
element.unselectable = '';
}
return element;
}
fabric.util.makeElementUnselectable = makeElementUnselectable;
fabric.util.makeElementSelectable = makeElementSelectable;
})();
(function() {
/**
* Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
* @memberOf fabric.util
* @param {String} url URL of a script to load
* @param {Function} callback Callback to execute when script is finished loading
*/
function getScript(url, callback) {
var headEl = fabric.document.getElementsByTagName('head')[0],
scriptEl = fabric.document.createElement('script'),
loading = true;
/** @ignore */
scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
if (loading) {
if (typeof this.readyState === 'string' &&
this.readyState !== 'loaded' &&
this.readyState !== 'complete') {
return;
}
loading = false;
callback(e || fabric.window.event);
scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
}
};
scriptEl.src = url;
headEl.appendChild(scriptEl);
// causes issue in Opera
// headEl.removeChild(scriptEl);
}
fabric.util.getScript = getScript;
})();
function getNodeCanvas(element) {
var impl = fabric.jsdomImplForWrapper(element);
return impl._canvas || impl._image;
};
fabric.util.getById = getById;
fabric.util.toArray = toArray;
fabric.util.makeElement = makeElement;
fabric.util.addClass = addClass;
fabric.util.wrapElement = wrapElement;
fabric.util.getScrollLeftTop = getScrollLeftTop;
fabric.util.getElementOffset = getElementOffset;
fabric.util.getElementStyle = getElementStyle;
fabric.util.getNodeCanvas = getNodeCanvas;
})();
(function() {
function addParamToUrl(url, param) {
return url + (/\?/.test(url) ? '&' : '?') + param;
}
var makeXHR = (function() {
var factories = [
function() { return new ActiveXObject('Microsoft.XMLHTTP'); },
function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
function() { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); },
function() { return new XMLHttpRequest(); }
];
for (var i = factories.length; i--; ) {
try {
var req = factories[i]();
if (req) {
return factories[i];
}
}
catch (err) { }
}
})();
function emptyFn() { }
/**
* Cross-browser abstraction for sending XMLHttpRequest
* @memberOf fabric.util
* @param {String} url URL to send XMLHttpRequest to
* @param {Object} [options] Options object
* @param {String} [options.method="GET"]
* @param {String} [options.parameters] parameters to append to url in GET or in body
* @param {String} [options.body] body to send with POST or PUT request
* @param {Function} options.onComplete Callback to invoke when request is completed
* @return {XMLHttpRequest} request
*/
function request(url, options) {
options || (options = { });
var method = options.method ? options.method.toUpperCase() : 'GET',
onComplete = options.onComplete || function() { },
xhr = makeXHR(),
body = options.body || options.parameters;
/** @ignore */
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
onComplete(xhr);
xhr.onreadystatechange = emptyFn;
}
};
if (method === 'GET') {
body = null;
if (typeof options.parameters === 'string') {
url = addParamToUrl(url, options.parameters);
}
}
xhr.open(method, url, true);
if (method === 'POST' || method === 'PUT') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
xhr.send(body);
return xhr;
}
fabric.util.request = request;
})();
/**
* Wrapper around `console.log` (when available)
* @param {*} [values] Values to log
*/
fabric.log = function() { };
/**
* Wrapper around `console.warn` (when available)
* @param {*} [values] Values to log as a warning
*/
fabric.warn = function() { };
/* eslint-disable */
if (typeof console !== 'undefined') {
['log', 'warn'].forEach(function(methodName) {
if (typeof console[methodName] !== 'undefined' &&
typeof console[methodName].apply === 'function') {
fabric[methodName] = function() {
return console[methodName].apply(console, arguments);
};
}
});
}
/* eslint-enable */
(function() {
function noop() {
return false;
}
/**
* Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
* @memberOf fabric.util
* @param {Object} [options] Animation options
* @param {Function} [options.onChange] Callback; invoked on every value change
* @param {Function} [options.onComplete] Callback; invoked when value change is completed
* @param {Number} [options.startValue=0] Starting value
* @param {Number} [options.endValue=100] Ending value
* @param {Number} [options.byValue=100] Value to modify the property by
* @param {Function} [options.easing] Easing function
* @param {Number} [options.duration=500] Duration of change (in ms)
*/
function animate(options) {
requestAnimFrame(function(timestamp) {
options || (options = { });
var start = timestamp || +new Date(),
duration = options.duration || 500,
finish = start + duration, time,
onChange = options.onChange || noop,
abort = options.abort || noop,
onComplete = options.onComplete || noop,
easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;},
startValue = 'startValue' in options ? options.startValue : 0,
endValue = 'endValue' in options ? options.endValue : 100,
byValue = options.byValue || endValue - startValue;
options.onStart && options.onStart();
(function tick(ticktime) {
if (abort()) {
onComplete(endValue, 1, 1);
return;
}
time = ticktime || +new Date();
var currentTime = time > finish ? duration : (time - start),
timePerc = currentTime / duration,
current = easing(currentTime, startValue, byValue, duration),
valuePerc = Math.abs((current - startValue) / byValue);
onChange(current, valuePerc, timePerc);
if (time > finish) {
options.onComplete && options.onComplete();
return;
}
requestAnimFrame(tick);
})(start);
});
}
var _requestAnimFrame = fabric.window.requestAnimationFrame ||
fabric.window.webkitRequestAnimationFrame ||
fabric.window.mozRequestAnimationFrame ||
fabric.window.oRequestAnimationFrame ||
fabric.window.msRequestAnimationFrame ||
function(callback) {
return fabric.window.setTimeout(callback, 1000 / 60);
};
var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout;
/**
* requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
* In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method
* @memberOf fabric.util
* @param {Function} callback Callback to invoke
* @param {DOMElement} element optional Element to associate with animation
*/
function requestAnimFrame() {
return _requestAnimFrame.apply(fabric.window, arguments);
}
function cancelAnimFrame() {
return _cancelAnimFrame.apply(fabric.window, arguments);
}
fabric.util.animate = animate;
fabric.util.requestAnimFrame = requestAnimFrame;
fabric.util.cancelAnimFrame = cancelAnimFrame;
})();
(function() {
// Calculate an in-between color. Returns a "rgba()" string.
// Credit: Edwin Martin <edwin@bitstorm.org>
// http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js
function calculateColor(begin, end, pos) {
var color = 'rgba('
+ parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ','
+ parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ','
+ parseInt((begin[2] + pos * (end[2] - begin[2])), 10);
color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1);
color += ')';
return color;
}
/**
* Changes the color from one to another within certain period of time, invoking callbacks as value is being changed.
* @memberOf fabric.util
* @param {String} fromColor The starting color in hex or rgb(a) format.
* @param {String} toColor The starting color in hex or rgb(a) format.
* @param {Number} [duration] Duration of change (in ms).
* @param {Object} [options] Animation options
* @param {Function} [options.onChange] Callback; invoked on every value change
* @param {Function} [options.onComplete] Callback; invoked when value change is completed
* @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used.
*/
function animateColor(fromColor, toColor, duration, options) {
var startColor = new fabric.Color(fromColor).getSource(),
endColor = new fabric.Color(toColor).getSource();
options = options || {};
fabric.util.animate(fabric.util.object.extend(options, {
duration: duration || 500,
startValue: startColor,
endValue: endColor,
byValue: endColor,
easing: function (currentTime, startValue, byValue, duration) {
var posValue = options.colorEasing
? options.colorEasing(currentTime, duration)
: 1 - Math.cos(currentTime / duration * (Math.PI / 2));
return calculateColor(startValue, byValue, posValue);
}
}));
}
fabric.util.animateColor = animateColor;
})();
(function() {
function normalize(a, c, p, s) {
if (a < Math.abs(c)) {
a = c;
s = p / 4;
}
else {
//handle the 0/0 case:
if (c === 0 && a === 0) {
s = p / (2 * Math.PI) * Math.asin(1);
}
else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
}
return { a: a, c: c, p: p, s: s };
}
function elastic(opts, t, d) {
return opts.a *
Math.pow(2, 10 * (t -= 1)) *
Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p );
}
/**
* Cubic easing out
* @memberOf fabric.util.ease
*/
function easeOutCubic(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
}
/**
* Cubic easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutCubic(t, b, c, d) {
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t + b;
}
return c / 2 * ((t -= 2) * t * t + 2) + b;
}
/**
* Quartic easing in
* @memberOf fabric.util.ease
*/
function easeInQuart(t, b, c, d) {
return c * (t /= d) * t * t * t + b;
}
/**
* Quartic easing out
* @memberOf fabric.util.ease
*/
function easeOutQuart(t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
}
/**
* Quartic easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutQuart(t, b, c, d) {
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t * t + b;
}
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
/**
* Quintic easing in
* @memberOf fabric.util.ease
*/
function easeInQuint(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
}
/**
* Quintic easing out
* @memberOf fabric.util.ease
*/
function easeOutQuint(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
}
/**
* Quintic easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutQuint(t, b, c, d) {
t /= d / 2;
if (t < 1) {
return c / 2 * t * t * t * t * t + b;
}
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
}
/**
* Sinusoidal easing in
* @memberOf fabric.util.ease
*/
function easeInSine(t, b, c, d) {
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
}
/**
* Sinusoidal easing out
* @memberOf fabric.util.ease
*/
function easeOutSine(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
}
/**
* Sinusoidal easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutSine(t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
}
/**
* Exponential easing in
* @memberOf fabric.util.ease
*/
function easeInExpo(t, b, c, d) {
return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
}
/**
* Exponential easing out
* @memberOf fabric.util.ease
*/
function easeOutExpo(t, b, c, d) {
return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
}
/**
* Exponential easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutExpo(t, b, c, d) {
if (t === 0) {
return b;
}
if (t === d) {
return b + c;
}
t /= d / 2;
if (t < 1) {
return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
}
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
/**
* Circular easing in
* @memberOf fabric.util.ease
*/
function easeInCirc(t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
}
/**
* Circular easing out
* @memberOf fabric.util.ease
*/
function easeOutCirc(t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
}
/**
* Circular easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutCirc(t, b, c, d) {
t /= d / 2;
if (t < 1) {
return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
}
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
}
/**
* Elastic easing in
* @memberOf fabric.util.ease
*/
function easeInElastic(t, b, c, d) {
var s = 1.70158, p = 0, a = c;
if (t === 0) {
return b;
}
t /= d;
if (t === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
var opts = normalize(a, c, p, s);
return -elastic(opts, t, d) + b;
}
/**
* Elastic easing out
* @memberOf fabric.util.ease
*/
function easeOutElastic(t, b, c, d) {
var s = 1.70158, p = 0, a = c;
if (t === 0) {
return b;
}
t /= d;
if (t === 1) {
return b + c;
}
if (!p) {
p = d * 0.3;
}
var opts = normalize(a, c, p, s);
return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b;
}
/**
* Elastic easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutElastic(t, b, c, d) {
var s = 1.70158, p = 0, a = c;
if (t === 0) {
return b;
}
t /= d / 2;
if (t === 2) {
return b + c;
}
if (!p) {
p = d * (0.3 * 1.5);
}
var opts = normalize(a, c, p, s);
if (t < 1) {
return -0.5 * elastic(opts, t, d) + b;
}
return opts.a * Math.pow(2, -10 * (t -= 1)) *
Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b;
}
/**
* Backwards easing in
* @memberOf fabric.util.ease
*/
function easeInBack(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
return c * (t /= d) * t * ((s + 1) * t - s) + b;
}
/**
* Backwards easing out
* @memberOf fabric.util.ease
*/
function easeOutBack(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
}
/**
* Backwards easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutBack(t, b, c, d, s) {
if (s === undefined) {
s = 1.70158;
}
t /= d / 2;
if (t < 1) {
return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
}
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
}
/**
* Bouncing easing in
* @memberOf fabric.util.ease
*/
function easeInBounce(t, b, c, d) {
return c - easeOutBounce (d - t, 0, c, d) + b;
}
/**
* Bouncing easing out
* @memberOf fabric.util.ease
*/
function easeOutBounce(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
}
else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
}
else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b;
}
else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
}
}
/**
* Bouncing easing in and out
* @memberOf fabric.util.ease
*/
function easeInOutBounce(t, b, c, d) {
if (t < d / 2) {
return easeInBounce (t * 2, 0, c, d) * 0.5 + b;
}
return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
}
/**
* Easing functions
* See <a href="http://gizma.com/easing/">Easing Equations by Robert Penner</a>
* @namespace fabric.util.ease
*/
fabric.util.ease = {
/**
* Quadratic easing in
* @memberOf fabric.util.ease
*/
easeInQuad: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
/**
* Quadratic easing out
* @memberOf fabric.util.ease
*/
easeOutQuad: function(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
/**
* Quadratic easing in and out
* @memberOf fabric.util.ease
*/
easeInOutQuad: function(t, b, c, d) {
t /= (d / 2);
if (t < 1) {
return c / 2 * t * t + b;
}
return -c / 2 * ((--t) * (t - 2) - 1) + b;
},
/**
* Cubic easing in
* @memberOf fabric.util.ease
*/
easeInCubic: function(t, b, c, d) {
return c * (t /= d) * t * t + b;
},
easeOutCubic: easeOutCubic,
easeInOutCubic: easeInOutCubic,
easeInQuart: easeInQuart,
easeOutQuart: easeOutQuart,
easeInOutQuart: easeInOutQuart,
easeInQuint: easeInQuint,
easeOutQuint: easeOutQuint,
easeInOutQuint: easeInOutQuint,
easeInSine: easeInSine,
easeOutSine: easeOutSine,
easeInOutSine: easeInOutSine,
easeInExpo: easeInExpo,
easeOutExpo: easeOutExpo,
easeInOutExpo: easeInOutExpo,
easeInCirc: easeInCirc,
easeOutCirc: easeOutCirc,
easeInOutCirc: easeInOutCirc,
easeInElastic: easeInElastic,
easeOutElastic: easeOutElastic,
easeInOutElastic: easeInOutElastic,
easeInBack: easeInBack,
easeOutBack: easeOutBack,
easeInOutBack: easeInOutBack,
easeInBounce: easeInBounce,
easeOutBounce: easeOutBounce,
easeInOutBounce: easeInOutBounce
};
})();
(function(global) {
'use strict';
/**
* @name fabric
* @namespace
*/
var fabric = global.fabric || (global.fabric = { }),
extend = fabric.util.object.extend,
clone = fabric.util.object.clone,
toFixed = fabric.util.toFixed,
parseUnit = fabric.util.parseUnit,
multiplyTransformMatrices = fabric.util.multiplyTransformMatrices,
svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line',
'image', 'text', 'linearGradient', 'radialGradient', 'stop'],
svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'],
svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'],
svgValidParents = ['symbol', 'g', 'a', 'svg'],
attributesMap = {
cx: 'left',
x: 'left',
r: 'radius',
cy: 'top',
y: 'top',
display: 'visible',
visibility: 'visible',
transform: 'transformMatrix',
'fill-opacity': 'fillOpacity',
'fill-rule': 'fillRule',
'font-family': 'fontFamily',
'font-size': 'fontSize',
'font-style': 'fontStyle',
'font-weight': 'fontWeight',
'letter-spacing': 'charSpacing',
'paint-order': 'paintFirst',
'stroke-dasharray': 'strokeDashArray',
'stroke-linecap': 'strokeLineCap',
'stroke-linejoin': 'strokeLineJoin',
'stroke-miterlimit': 'strokeMiterLimit',
'stroke-opacity': 'strokeOpacity',
'stroke-width': 'strokeWidth',
'text-decoration': 'textDecoration',
'text-anchor': 'textAnchor',
opacity: 'opacity'
},
colorAttributes = {
stroke: 'strokeOpacity',
fill: 'fillOpacity'
};
fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames);
fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements);
fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors);
fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents);
fabric.cssRules = { };
fabric.gradientDefs = { };
function normalizeAttr(attr) {
// transform attribute names
if (attr in attributesMap) {
return attributesMap[attr];
}
return attr;
}
function normalizeValue(attr, value, parentAttributes, fontSize) {
var isArray = Object.prototype.toString.call(value) === '[object Array]',
parsed;
if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
value = '';
}
else if (attr === 'strokeDashArray') {
if (value === 'none') {
value = null;
}
else {
value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) {
return parseFloat(n);
});
}
}
else if (attr === 'transformMatrix') {
if (parentAttributes && parentAttributes.transformMatrix) {
value = multiplyTransformMatrices(
parentAttributes.transformMatrix, fabric.parseTransformAttribute(value));
}
else {
value = fabric.parseTransformAttribute(value);
}
}
else if (attr === 'visible') {
value = value !== 'none' && value !== 'hidden';
// display=none on parent element always takes precedence over child element
if (parentAttributes && parentAttributes.visible === false) {
value = false;
}
}
else if (attr === 'opacity') {
value = parseFloat(value);
if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') {
value *= parentAttributes.opacity;
}
}
else if (attr === 'textAnchor' /* text-anchor */) {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
else if (attr === 'charSpacing') {
// parseUnit returns px and we convert it to em
parsed = parseUnit(value, fontSize) / fontSize * 1000;
}
else if (attr === 'paintFirst') {
var fillIndex = value.indexOf('fill');
var strokeIndex = value.indexOf('stroke');
var value = 'fill';
if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) {
value = 'stroke';
}
else if (fillIndex === -1 && strokeIndex > -1) {
value = 'stroke';
}
}
else {
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
return (!isArray && isNaN(parsed) ? value : parsed);
}
/**
* @private
*/
function getSvgRegex(arr) {
return new RegExp('^(' + arr.join('|') + ')\\b', 'i');
}
/**
* @private
* @param {Object} attributes Array of attributes to parse
*/
function _setStrokeFillOpacity(attributes) {
for (var attr in colorAttributes) {
if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') {
continue;
}
if (typeof attributes[attr] === 'undefined') {
if (!fabric.Object.prototype[attr]) {
continue;
}
attributes[attr] = fabric.Object.prototype[attr];
}
if (attributes[attr].indexOf('url(') === 0) {
continue;
}
var color = new fabric.Color(attributes[attr]);
attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba();
}
return attributes;
}
/**
* @private
*/
function _getMultipleNodes(doc, nodeNames) {
var nodeName, nodeArray = [], nodeList, i, len;
for (i = 0, len = nodeNames.length; i < len; i++) {
nodeName = nodeNames[i];
nodeList = doc.getElementsByTagName(nodeName);
nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList));
}
return nodeArray;
}
/**
* Parses "transform" attribute, returning an array of values
* @static
* @function
* @memberOf fabric
* @param {String} attributeValue String containing attribute value
* @return {Array} Array of 6 elements representing transformation matrix
*/
fabric.parseTransformAttribute = (function() {
function rotateMatrix(matrix, args) {
var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]),
x = 0, y = 0;
if (args.length === 3) {
x = args[1];
y = args[2];
}
matrix[0] = cos;
matrix[1] = sin;
matrix[2] = -sin;
matrix[3] = cos;
matrix[4] = x - (cos * x - sin * y);
matrix[5] = y - (sin * x + cos * y);
}
function scaleMatrix(matrix, args) {
var multiplierX = args[0],
multiplierY = (args.length === 2) ? args[1] : args[0];
matrix[0] = multiplierX;
matrix[3] = multiplierY;
}
function skewMatrix(matrix, args, pos) {
matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0]));
}
function translateMatrix(matrix, args) {
matrix[4] = args[0];
if (args.length === 2) {
matrix[5] = args[1];
}
}
// identity matrix
var iMatrix = [
1, // a
0, // b
0, // c
1, // d
0, // e
0 // f
],
// == begin transform regexp
number = fabric.reNum,
commaWsp = '(?:\\s+,?\\s*|,\\s*)',
skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + ')' +
commaWsp + '(' + number + '))?\\s*\\))',
scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' +
commaWsp + '(' + number + '))?\\s*\\))',
matrix = '(?:(matrix)\\s*\\(\\s*' +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' + commaWsp +
'(' + number + ')' +
'\\s*\\))',
transform = '(?:' +
matrix + '|' +
translate + '|' +
scale + '|' +
rotate + '|' +
skewX + '|' +
skewY +
')',
transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')',
transformList = '^\\s*(?:' + transforms + '?)\\s*$',
// http://www.w3.org/TR/SVG/coords.html#TransformAttribute
reTransformList = new RegExp(transformList),
// == end transform regexp
reTransform = new RegExp(transform, 'g');
return function(attributeValue) {
// start with identity matrix
var matrix = iMatrix.concat(),
matrices = [];
// return if no argument was given or
// an argument does not match transform attribute regexp
if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) {
return matrix;
}
attributeValue.replace(reTransform, function(match) {
var m = new RegExp(transform).exec(match).filter(function (match) {
// match !== '' && match != null
return (!!match);
}),
operation = m[1],
args = m.slice(2).map(parseFloat);
switch (operation) {
case 'translate':
translateMatrix(matrix, args);
break;
case 'rotate':
args[0] = fabric.util.degreesToRadians(args[0]);
rotateMatrix(matrix, args);
break;
case 'scale':
scaleMatrix(matrix, args);
break;
case 'skewX':
skewMatrix(matrix, args, 2);
break;
case 'skewY':
skewMatrix(matrix, args, 1);
break;
case 'matrix':
matrix = args;
break;
}
// snapshot current matrix into matrices array
matrices.push(matrix.concat());
// reset
matrix = iMatrix.concat();
});
var combinedMatrix = matrices[0];
while (matrices.length > 1) {
matrices.shift();
combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]);
}
return combinedMatrix;
};
})();
/**
* @private
*/
function parseStyleString(style, oStyle) {
var attr, value;
style.replace(/;\s*$/, '').split(';').forEach(function (chunk) {
var pair = chunk.split(':');
attr = pair[0].trim().toLowerCase();
value = pair[1].trim();
oStyle[attr] = value;
});
}
/**
* @private
*/
function parseStyleObject(style, oStyle) {
var attr, value;
for (var prop in style) {
if (typeof style[prop] === 'undefined') {
continue;
}
attr = prop.toLowerCase();
value = style[prop];
oStyle[attr] = value;
}
}
/**
* @private
*/
function getGlobalStylesForElement(element, svgUid) {
var styles = { };
for (var rule in fabric.cssRules[svgUid]) {
if (elementMatchesRule(element, rule.split(' '))) {
for (var property in fabric.cssRules[svgUid][rule]) {
styles[property] = fabric.cssRules[svgUid][rule][property];
}
}
}
return styles;
}
/**
* @private
*/
function elementMatchesRule(element, selectors) {
var firstMatching, parentMatching = true;
//start from rightmost selector.
firstMatching = selectorMatches(element, selectors.pop());
if (firstMatching && selectors.length) {
parentMatching = doesSomeParentMatch(element, selectors);
}
return firstMatching && parentMatching && (selectors.length === 0);
}
function doesSomeParentMatch(element, selectors) {
var selector, parentMatching = true;
while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) {
if (parentMatching) {
selector = selectors.pop();
}
element = element.parentNode;
parentMatching = selectorMatches(element, selector);
}
return selectors.length === 0;
}
/**
* @private
*/
function selectorMatches(element, selector) {
var nodeName = element.nodeName,
classNames = element.getAttribute('class'),
id = element.getAttribute('id'), matcher, i;
// i check if a selector matches slicing away part from it.
// if i get empty string i should match
matcher = new RegExp('^' + nodeName, 'i');
selector = selector.replace(matcher, '');
if (id && selector.length) {
matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
selector = selector.replace(matcher, '');
}
if (classNames && selector.length) {
classNames = classNames.split(' ');
for (i = classNames.length; i--;) {
matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i');
selector = selector.replace(matcher, '');
}
}
return selector.length === 0;
}
/**
* @private
* to support IE8 missing getElementById on SVGdocument
*/
function elementById(doc, id) {
var el;
doc.getElementById && (el = doc.getElementById(id));
if (el) {
return el;
}
var node, i, len, nodelist = doc.getElementsByTagName('*');
for (i = 0, len = nodelist.length; i < len; i++) {
node = nodelist[i];
if (id === node.getAttribute('id')) {
return node;
}
}
}
/**
* @private
*/
function parseUseDirectives(doc) {
var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0;
while (nodelist.length && i < nodelist.length) {
var el = nodelist[i],
xlink = el.getAttribute('xlink:href').substr(1),
x = el.getAttribute('x') || 0,
y = el.getAttribute('y') || 0,
el2 = elementById(doc, xlink).cloneNode(true),
currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')',
parentNode, oldLength = nodelist.length, attr, j, attrs, len;
applyViewboxTransform(el2);
if (/^svg$/i.test(el2.nodeName)) {
var el3 = el2.ownerDocument.createElement('g');
for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) {
attr = attrs.item(j);
el3.setAttribute(attr.nodeName, attr.nodeValue);
}
// el2.firstChild != null
while (el2.firstChild) {
el3.appendChild(el2.firstChild);
}
el2 = el3;
}
for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) {
attr = attrs.item(j);
if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') {
continue;
}
if (attr.nodeName === 'transform') {
currentTrans = attr.nodeValue + ' ' + currentTrans;
}
else {
el2.setAttribute(attr.nodeName, attr.nodeValue);
}
}
el2.setAttribute('transform', currentTrans);
el2.setAttribute('instantiated_by_use', '1');
el2.removeAttribute('id');
parentNode = el.parentNode;
parentNode.replaceChild(el2, el);
// some browsers do not shorten nodelist after replaceChild (IE8)
if (nodelist.length === oldLength) {
i++;
}
}
}
// http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
// matches, e.g.: +14.56e-12, etc.
var reViewBoxAttrValue = new RegExp(
'^' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*,?' +
'\\s*(' + fabric.reNum + '+)\\s*' +
'$'
);
/**
* Add a <g> element that envelop all child elements and makes the viewbox transformMatrix descend on all elements
*/
function applyViewboxTransform(element) {
var viewBoxAttr = element.getAttribute('viewBox'),
scaleX = 1,
scaleY = 1,
minX = 0,
minY = 0,
viewBoxWidth, viewBoxHeight, matrix, el,
widthAttr = element.getAttribute('width'),
heightAttr = element.getAttribute('height'),
x = element.getAttribute('x') || 0,
y = element.getAttribute('y') || 0,
preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '',
missingViewBox = (!viewBoxAttr || !fabric.svgViewBoxElementsRegEx.test(element.nodeName)
|| !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))),
missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'),
toBeParsed = missingViewBox && missingDimAttr,
parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0;
parsedDim.width = 0;
parsedDim.height = 0;
parsedDim.toBeParsed = toBeParsed;
if (toBeParsed) {
return parsedDim;
}
if (missingViewBox) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
return parsedDim;
}
minX = -parseFloat(viewBoxAttr[1]);
minY = -parseFloat(viewBoxAttr[2]);
viewBoxWidth = parseFloat(viewBoxAttr[3]);
viewBoxHeight = parseFloat(viewBoxAttr[4]);
if (!missingDimAttr) {
parsedDim.width = parseUnit(widthAttr);
parsedDim.height = parseUnit(heightAttr);
scaleX = parsedDim.width / viewBoxWidth;
scaleY = parsedDim.height / viewBoxHeight;
}
else {
parsedDim.width = viewBoxWidth;
parsedDim.height = viewBoxHeight;
}
// default is to preserve aspect ratio
preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio);
if (preserveAspectRatio.alignX !== 'none') {
//translate all container for the effect of Mid, Min, Max
if (preserveAspectRatio.meetOrSlice === 'meet') {
scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX);
// calculate additional translation to move the viewbox
}
if (preserveAspectRatio.meetOrSlice === 'slice') {
scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY);
// calculate additional translation to move the viewbox
}
widthDiff = parsedDim.width - viewBoxWidth * scaleX;
heightDiff = parsedDim.height - viewBoxHeight * scaleX;
if (preserveAspectRatio.alignX === 'Mid') {
widthDiff /= 2;
}
if (preserveAspectRatio.alignY === 'Mid') {
heightDiff /= 2;
}
if (preserveAspectRatio.alignX === 'Min') {
widthDiff = 0;
}
if (preserveAspectRatio.alignY === 'Min') {
heightDiff = 0;
}
}
if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) {
return parsedDim;
}
if (x || y) {
translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') ';
}
matrix = translateMatrix + ' matrix(' + scaleX +
' 0' +
' 0 ' +
scaleY + ' ' +
(minX * scaleX + widthDiff) + ' ' +
(minY * scaleY + heightDiff) + ') ';
if (element.nodeName === 'svg') {
el = element.ownerDocument.createElement('g');
// element.firstChild != null
while (element.firstChild) {
el.appendChild(element.firstChild);
}
element.appendChild(el);
}
else {
el = element;
matrix = el.getAttribute('transform') + matrix;
}
el.setAttribute('transform', matrix);
return parsedDim;
}
function hasAncestorWithNodeName(element, nodeName) {
while (element && (element = element.parentNode)) {
if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', ''))
&& !element.getAttribute('instantiated_by_use')) {
return true;
}
}
return false;
}
/**
* Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @param {Function} callback Callback to call when parsing is finished;
* It's being passed an array of elements (parsed from a document).
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [parsingOptions] options for parsing document
* @param {String} [parsingOptions.crossOrigin] crossOrigin settings
*/
fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) {
if (!doc) {
return;
}
parseUseDirectives(doc);
var svgUid = fabric.Object.__uid++, i, len,
options = applyViewboxTransform(doc),
descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
options.crossOrigin = parsingOptions && parsingOptions.crossOrigin;
options.svgUid = svgUid;
if (descendants.length === 0 && fabric.isLikelyNode) {
// we're likely in node, where "o3-xml" library fails to gEBTN("*")
// https://github.com/ajaxorg/node-o3-xml/issues/21
descendants = doc.selectNodes('//*[name(.)!="svg"]');
var arr = [];
for (i = 0, len = descendants.length; i < len; i++) {
arr[i] = descendants[i];
}
descendants = arr;
}
var elements = descendants.filter(function(el) {
applyViewboxTransform(el);
return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) &&
!hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement
});
if (!elements || (elements && !elements.length)) {
callback && callback([], {});
return;
}
fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc);
fabric.cssRules[svgUid] = fabric.getCSSRules(doc);
// Precedence of rules: style > class > attribute
fabric.parseElements(elements, function(instances, elements) {
if (callback) {
callback(instances, options, elements, descendants);
}
}, clone(options), reviver, parsingOptions);
};
var reFontDeclaration = new RegExp(
'(normal|italic)?\\s*(normal|small-caps)?\\s*' +
'(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' +
fabric.reNum +
'(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)');
extend(fabric, {
/**
* Parses a short font declaration, building adding its properties to a style object
* @static
* @function
* @memberOf fabric
* @param {String} value font declaration
* @param {Object} oStyle definition
*/
parseFontDeclaration: function(value, oStyle) {
var match = value.match(reFontDeclaration);
if (!match) {
return;
}
var fontStyle = match[1],
// font variant is not used
// fontVariant = match[2],
fontWeight = match[3],
fontSize = match[4],
lineHeight = match[5],
fontFamily = match[6];
if (fontStyle) {
oStyle.fontStyle = fontStyle;
}
if (fontWeight) {
oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight);
}
if (fontSize) {
oStyle.fontSize = parseUnit(fontSize);
}
if (fontFamily) {
oStyle.fontFamily = fontFamily;
}
if (lineHeight) {
oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight;
}
},
/**
* Parses an SVG document, returning all of the gradient declarations found in it
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
*/
getGradientDefs: function(doc) {
var tagArray = [
'linearGradient',
'radialGradient',
'svg:linearGradient',
'svg:radialGradient'],
elList = _getMultipleNodes(doc, tagArray),
el, j = 0, id, xlink,
gradientDefs = { }, idsToXlinkMap = { };
j = elList.length;
while (j--) {
el = elList[j];
xlink = el.getAttribute('xlink:href');
id = el.getAttribute('id');
if (xlink) {
idsToXlinkMap[id] = xlink.substr(1);
}
gradientDefs[id] = el;
}
for (id in idsToXlinkMap) {
var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true);
el = gradientDefs[id];
while (el2.firstChild) {
el.appendChild(el2.firstChild);
}
}
return gradientDefs;
},
/**
* Returns an object of attributes' name/value, given element and an array of attribute names;
* Parses parent "g" nodes recursively upwards.
* @static
* @memberOf fabric
* @param {DOMElement} element Element to parse
* @param {Array} attributes Array of attributes to parse
* @return {Object} object containing parsed attributes' names/values
*/
parseAttributes: function(element, attributes, svgUid) {
if (!element) {
return;
}
var value,
parentAttributes = { },
fontSize;
if (typeof svgUid === 'undefined') {
svgUid = element.getAttribute('svgUid');
}
// if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) {
parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid);
}
fontSize = (parentAttributes && parentAttributes.fontSize ) ||
element.getAttribute('font-size') || fabric.Text.DEFAULT_SVG_FONT_SIZE;
var ownAttributes = attributes.reduce(function(memo, attr) {
value = element.getAttribute(attr);
if (value) { // eslint-disable-line
memo[attr] = value;
}
return memo;
}, { });
// add values parsed from style, which take precedence over attributes
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
ownAttributes = extend(ownAttributes,
extend(getGlobalStylesForElement(element, svgUid), fabric.parseStyleAttribute(element)));
var normalizedAttr, normalizedValue, normalizedStyle = {};
for (var attr in ownAttributes) {
normalizedAttr = normalizeAttr(attr);
normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize);
normalizedStyle[normalizedAttr] = normalizedValue;
}
if (normalizedStyle && normalizedStyle.font) {
fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle);
}
var mergedAttrs = extend(parentAttributes, normalizedStyle);
return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs);
},
/**
* Transforms an array of svg elements to corresponding fabric.* instances
* @static
* @memberOf fabric
* @param {Array} elements Array of elements to parse
* @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements)
* @param {Object} [options] Options object
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
*/
parseElements: function(elements, callback, options, reviver, parsingOptions) {
new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse();
},
/**
* Parses "style" attribute, retuning an object with values
* @static
* @memberOf fabric
* @param {SVGElement} element Element to parse
* @return {Object} Objects with values parsed from style attribute of an element
*/
parseStyleAttribute: function(element) {
var oStyle = { },
style = element.getAttribute('style');
if (!style) {
return oStyle;
}
if (typeof style === 'string') {
parseStyleString(style, oStyle);
}
else {
parseStyleObject(style, oStyle);
}
return oStyle;
},
/**
* Parses "points" attribute, returning an array of values
* @static
* @memberOf fabric
* @param {String} points points attribute string
* @return {Array} array of points
*/
parsePointsAttribute: function(points) {
// points attribute is required and must not be empty
if (!points) {
return null;
}
// replace commas with whitespace and remove bookending whitespace
points = points.replace(/,/g, ' ').trim();
points = points.split(/\s+/);
var parsedPoints = [], i, len;
for (i = 0, len = points.length; i < len; i += 2) {
parsedPoints.push({
x: parseFloat(points[i]),
y: parseFloat(points[i + 1])
});
}
// odd number of points is an error
// if (parsedPoints.length % 2 !== 0) {
// return null;
// }
return parsedPoints;
},
/**
* Returns CSS rules for a given SVG document
* @static
* @function
* @memberOf fabric
* @param {SVGDocument} doc SVG document to parse
* @return {Object} CSS rules of this document
*/
getCSSRules: function(doc) {
var styles = doc.getElementsByTagName('style'), i, len,
allRules = { }, rules;
// very crude parsing of style contents
for (i = 0, len = styles.length; i < len; i++) {
// IE9 doesn't support textContent, but provides text instead.
var styleContents = styles[i].textContent || styles[i].text;
// remove comments
styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
if (styleContents.trim() === '') {
continue;
}
rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
rules = rules.map(function(rule) { return rule.trim(); });
// eslint-disable-next-line no-loop-func
rules.forEach(function(rule) {
var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
ruleObj = { }, declaration = match[2].trim(),
propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
for (i = 0, len = propertyValuePairs.length; i < len; i++) {
var pair = propertyValuePairs[i].split(/\s*:\s*/),
property = pair[0],
value = pair[1];
ruleObj[property] = value;
}
rule = match[1];
rule.split(',').forEach(function(_rule) {
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
}
if (allRules[_rule]) {
fabric.util.object.extend(allRules[_rule], ruleObj);
}
else {
allRules[_rule] = fabric.util.object.clone(ruleObj);
}
});
});
}
return allRules;
},
/**
* Takes url corresponding to an SVG document, and parses it into a set of fabric objects.
* Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy)
* @memberOf fabric
* @param {String} url
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
*/
loadSVGFromURL: function(url, callback, reviver, options) {
url = url.replace(/^\n\s*/, '').trim();
new fabric.util.request(url, {
method: 'get',
onComplete: onComplete
});
function onComplete(r) {
var xml = r.responseXML;
if (xml && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = 'false';
//IE chokes on DOCTYPE
xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
}
if (!xml || !xml.documentElement) {
callback && callback(null);
}
fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) {
callback && callback(results, _options, elements, allElements);
}, reviver, options);
}
},
/**
* Takes string corresponding to an SVG document, and parses it into a set of fabric objects
* @memberOf fabric
* @param {String} string
* @param {Function} callback
* @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
* @param {Object} [options] Object containing options for parsing
* @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources
*/
loadSVGFromString: function(string, callback, reviver, options) {
string = string.trim();
var doc;
if (typeof DOMParser !== 'undefined') {
var parser = new DOMParser();
if (parser && parser.parseFromString) {
doc = parser.parseFromString(string, 'text/xml');
}
}
else if (fabric.window.ActiveXObject) {
doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = 'false';
// IE chokes on DOCTYPE
doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i, ''));
}
fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) {
callback(results, _options, elements, allElements);
}, reviver, options);
}
});
})(typeof exports !== 'undefined' ? exports : this);
fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions) {
this.elements = elements;
this.callback = callback;
this.options = options;
this.reviver = reviver;
this.svgUid = (options && options.svgUid) || 0;
this.parsingOptions = parsingOptions;
this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g;
};
fabric.ElementsParser.prototype.parse = function() {
this.instances = new Array(this.elements.length);
this.numElements = this.elements.length;
this.createObjects();
};
fabric.ElementsParser.prototype.createObjects = function() {
for (var i = 0, len = this.elements.length; i < len; i++) {
this.elements[i].setAttribute('svgUid', this.svgUid);
(function(_obj, i) {
setTimeout(function() {
_obj.createObject(_obj.elements[i], i);
}, 0);
})(this, i);
}
};
fabric.ElementsParser.prototype.createObject = function(el, index) {
var klass = fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))];
if (klass && klass.fromElement) {
try {
this._createObject(klass, el, index);
}
catch (err) {
fabric.log(err);
}
}
else {
this.checkIfDone();
}
};
fabric.ElementsParser.prototype._createObject = function(klass, el, index) {
klass.fromElement(el, this.createCallback(index, el), this.options);
};
fabric.ElementsParser.prototype.createCallback = function(index, el) {
var _this = this;
return function(obj) {
var _options;
_this.resolveGradient(obj, 'fill');
_this.resolveGradient(obj, 'stroke');
if (obj instanceof fabric.Image) {
_options = obj.parsePreserveAspectRatioAttribute(el);
}
obj._removeTransformMatrix(_options);
_this.reviver && _this.reviver(el, obj);
_this.instances[index] = obj;
_this.checkIfDone();
};
};
fabric.ElementsParser.prototype.resolveGradient = function(obj, property) {
var instanceFillValue = obj[property];
if (!(/^url\(/).test(instanceFillValue)) {
return;
}
var gradientId = this.regexUrl.exec(instanceFillValue)[1];
this.regexUrl.lastIndex = 0;
if (fabric.gradientDefs[this.svgUid][gradientId]) {
obj.set(property,
fabric.Gradient.fromElement(fabric.gradientDefs[this.svgUid][gradientId], obj));
}
};
fabric.ElementsParser.prototype.checkIfDone = function() {
if (--this.numElements === 0) {
this.instances = this.instances.filter(function(el) {
// eslint-disable-next-line no-eq-null, eqeqeq
return el != null;
});
this.callback(this.instances, this.elements);
}
};
(function(global) {
'use strict';
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
var fabric = global.fabric || (global.fabric = { });
if (fabric.Point) {
fabric.warn('fabric.Point is already defined');
return;
}
fabric.Point = Point;
/**
* Point class
* @class fabric.Point
* @memberOf fabric
* @constructor
* @param {Number} x
* @param {Number} y
* @return {fabric.Point} thisArg
*/
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = /** @lends fabric.Point.prototype */ {
type: 'point',
constructor: Point,
/**
* Adds another point to this one and returns another one
* @param {fabric.Point} that
* @return {fabric.Point} new Point instance with added values
*/
add: function (that) {
return new Point(this.x + that.x, this.y + that.y);
},
/**
* Adds another point to this one
* @param {fabric.Point} that
* @return {fabric.Point} thisArg
* @chainable
*/
addEquals: function (that) {
this.x += that.x;
this.y += that.y;
return this;
},
/**
* Adds value to this point and returns a new one
* @param {Number} scalar
* @return {fabric.Point} new Point with added value
*/
scalarAdd: function (scalar) {
return new Point(this.x + scalar, this.y + scalar);
},
/**
* Adds value to this point
* @param {Number} scalar
* @return {fabric.Point} thisArg
* @chainable
*/
scalarAddEquals: function (scalar) {
this.x += scalar;
this.y += scalar;
return this;
},
/**
* Subtracts another point from this point and returns a new one
* @param {fabric.Point} that
* @return {fabric.Point} new Point object with subtracted values
*/
subtract: function (that) {
return new Point(this.x - that.x, this.y - that.y);
},
/**
* Subtracts another point from this point
* @param {fabric.Point} that
* @return {fabric.Point} thisArg
* @chainable
*/
subtractEquals: function (that) {
this.x -= that.x;
this.y -= that.y;
return this;
},
/**
* Subtracts value from this point and returns a new one
* @param {Number} scalar
* @return {fabric.Point}
*/
scalarSubtract: function (scalar) {
return new Point(this.x - scalar, this.y - scalar);
},
/**
* Subtracts value from this point
* @param {Number} scalar
* @return {fabric.Point} thisArg
* @chainable
*/
scalarSubtractEquals: function (scalar) {
this.x -= scalar;
this.y -= scalar;
return this;
},
/**
* Multiplies this point by a value and returns a new one
* TODO: rename in scalarMultiply in 2.0
* @param {Number} scalar
* @return {fabric.Point}
*/
multiply: function (scalar) {
return new Point(this.x * scalar, this.y * scalar);
},
/**
* Multiplies this point by a value
* TODO: rename in scalarMultiplyEquals in 2.0
* @param {Number} scalar
* @return {fabric.Point} thisArg
* @chainable
*/
multiplyEquals: function (scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
},
/**
* Divides this point by a value and returns a new one
* TODO: rename in scalarDivide in 2.0
* @param {Number} scalar
* @return {fabric.Point}
*/
divide: function (scalar) {
return new Point(this.x / scalar, this.y / scalar);
},
/**
* Divides this point by a value
* TODO: rename in scalarDivideEquals in 2.0
* @param {Number} scalar
* @return {fabric.Point} thisArg
* @chainable
*/
divideEquals: function (scalar) {
this.x /= scalar;
this.y /= scalar;
return this;
},
/**
* Returns true if this point is equal to another one
* @param {fabric.Point} that
* @return {Boolean}
*/
eq: function (that) {
return (this.x === that.x && this.y === that.y);
},
/**
* Returns true if this point is less than another one
* @param {fabric.Point} that
* @return {Boolean}
*/
lt: function (that) {
return (this.x < that.x && this.y < that.y);
},
/**
* Returns true if this point is less than or equal to another one
* @param {fabric.Point} that
* @return {Boolean}
*/
lte: function (that) {
return (this.x <= that.x && this.y <= that.y);
},
/**
* Returns true if this point is greater another one
* @param {fabric.Point} that
* @return {Boolean}
*/
gt: function (that) {
return (this.x > that.x && this.y > that.y);
},
/**
* Returns true if this point is greater than or equal to another one
* @param {fabric.Point} that
* @return {Boolean}
*/
gte: function (that) {
return (this.x >= that.x && this.y >= that.y);
},
/**
* Returns new point which is the result of linear interpolation with this one and another one
* @param {fabric.Point} that
* @param {Number} t , position of interpolation, between 0 and 1 default 0.5
* @return {fabric.Point}
*/
lerp: function (that, t) {
if (typeof t === 'undefined') {
t = 0.5;
}
t = Math.max(Math.min(1, t), 0);
return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
},
/**
* Returns distance from this point and another one
* @param {fabric.Point} that
* @return {Number}
*/
distanceFrom: function (that) {
var dx = this.x - that.x,
dy = this.y - that.y;
return Math.sqrt(dx * dx + dy * dy);
},
/**
* Returns the point between this point and another one
* @param {fabric.Point} that
* @return {fabric.Point}
*/
midPointFrom: function (that) {
return this.lerp(that);
},
/**
* Returns a new point which is the min of this and another one
* @param {fabric.Point} that
* @return {fabric.Point}
*/
min: function (that) {
return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
},
/**
* Returns a new point which is the max of this and another one
* @param {fabric.Point} that
* @return {fabric.Point}
*/
max: function (that) {
return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
},
/**
* Returns string representation of this point
* @return {String}
*/
toString: function () {
return this.x + ',' + this.y;
},
/**
* Sets x/y of this point
* @param {Number} x
* @param {Number} y
* @chainable
*/
setXY: function (x, y) {
this.x = x;
this.y = y;
return this;
},
/**
* Sets x of this point
* @param {Number} x
* @chainable
*/
setX: function (x) {
this.x = x;
return this;
},
/**
* Sets y of this point
* @param {Number} y
* @chainable
*/
setY: function (y) {
this.y = y;
return this;
},
/**
* Sets x/y of this point from another point
* @param {fabric.Point} that
* @chainable
*/
setFromPoint: function (that) {
this.x = that.x;
this.y = that.y;
return this;
},
/**
* Swaps x/y of this point and another point
* @param {fabric.Point} that
*/
swap: function (that) {
var x = this.x,
y = this.y;
this.x = that.x;
this.y = that.y;
that.x = x;
that.y = y;
},
/**
* return a cloned instance of the point
* @return {fabric.Point}
*/
clone: function () {
return new Point(this.x, this.y);
}
};
})(typeof exports !== 'undefined' ? exports : this);
(function(global) {
'use strict';
/* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */
var fabric = global.fabric || (global.fabric = { });
if (fabric.Intersection) {
fabric.warn('fabric.Intersection is already defined');
return;
}
/**
* Intersection class
* @class fabric.Intersection
* @memberOf fabric
* @constructor
*/
function Intersection(status) {
this.status = status;
this.points = [];
}
fabric.Intersection = Intersection;
fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ {
constructor: Intersection,
/**
* Appends a point to intersection
* @param {fabric.Point} point
* @return {fabric.Intersection} thisArg
* @chainable
*/
appendPoint: function (point) {
this.points.push(point);
return this;
},
/**
* Appends points to intersection
* @param {Array} points
* @return {fabric.Intersection} thisArg
* @chainable
*/
appendPoints: function (points) {
this.points = this.points.concat(points);
return this;
}
};
/**
* Checks if one line intersects another
* TODO: rename in intersectSegmentSegment
* @static
* @param {fabric.Point} a1
* @param {fabric.Point} a2
* @param {fabric.Point} b1
* @param {fabric.Point} b2
* @return {fabric.Intersection}
*/
fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
var result,
uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
if (uB !== 0) {
var ua = uaT / uB,
ub = ubT / uB;
if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
result = new Intersection('Intersection');
result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
}
else {
result = new Intersection();
}
}
else {
if (uaT === 0 || ubT === 0) {
result = new Intersection('Coincident');
}
else {
result = new Intersection('Parallel');
}
}
return result;
};
/**
* Checks if line intersects polygon
* TODO: rename in intersectSegmentPolygon
* fix detection of coincident
* @static
* @param {fabric.Point} a1
* @param {fabric.Point} a2
* @param {Array} points
* @return {fabric.Intersection}
*/
fabric.Intersection.intersectLinePolygon = function(a1, a2, points) {
var result = new Intersection(),
length = points.length,
b1, b2, inter, i;
for (i = 0; i < length; i++) {
b1 = points[i];
b2 = points[(i + 1) % length];
inter = Intersection.intersectLineLine(a1, a2, b1, b2);
result.appendPoints(inter.points);
}
if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
};
/**
* Checks if polygon intersects another polygon
* @static
* @param {Array} points1
* @param {Array} points2
* @return {fabric.Intersection}
*/
fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
var result = new Intersection(),
length = points1.length, i;
for (i = 0; i < length; i++) {
var a1 = points1[i],
a2 = points1[(i + 1) % length],
inter = Intersection.intersectLinePolygon(a1, a2, points2);
result.appendPoints(inter.points);
}
if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
};
/**
* Checks if polygon intersects rectangle
* @static
* @param {Array} points
* @param {fabric.Point} r1
* @param {fabric.Point} r2
* @return {fabric.Intersection}
*/
fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) {
var min = r1.min(r2),
max = r1.max(r2),
topRight = new fabric.Point(max.x, min.y),
bottomLeft = new fabric.Point(min.x, max.y),
inter1 = Intersection.intersectLinePolygon(min, topRight, points),
inter2 = Intersection.intersectLinePolygon(topRight, max, points),
inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points),
inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points),
result = new Intersection();
result.appendPoints(inter1.points);
result.appendPoints(inter2.points);
result.appendPoints(inter3.points);
result.appendPoints(inter4.points);
if (result.points.length > 0) {
result.status = 'Intersection';
}
return result;
};
})(typeof exports !== 'undefined' ? exports : this);
(function(global) {
'use strict';
var fabric = global.fabric || (global.fabric = { });
if (fabric.Color) {
fabric.warn('fabric.Color is already defined.');
return;
}
/**
* Color class
* The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations;
* {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects.
*
* @class fabric.Color
* @param {String} color optional in hex or rgb(a) or hsl format or from known color list
* @return {fabric.Color} thisArg
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors}
*/
function Color(color) {
if (!color) {
this.setSource([0, 0, 0, 1]);
}
else {
this._tryParsingColor(color);
}
}
fabric.Color = Color;
fabric.Color.prototype = /** @lends fabric.Color.prototype */ {
/**
* @private
* @param {String|Array} color Color value to parse
*/
_tryParsingColor: function(color) {
var source;
if (color in Color.colorNameMap) {
color = Color.colorNameMap[color];
}
if (color === 'transparent') {
source = [255, 255, 255, 0];
}
if (!source) {
source = Color.sourceFromHex(color);
}
if (!source) {
source = Color.sourceFromRgb(color);
}
if (!source) {
source = Color.sourceFromHsl(color);
}
if (!source) {
//if color is not recognize let's make black as canvas does
source = [0, 0, 0, 1];
}
if (source) {
this.setSource(source);
}
},
/**
* Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
* @private
* @param {Number} r Red color value
* @param {Number} g Green color value
* @param {Number} b Blue color value
* @return {Array} Hsl color
*/
_rgbToHsl: function(r, g, b) {
r /= 255; g /= 255; b /= 255;
var h, s, l,
max = fabric.util.array.max([r, g, b]),
min = fabric.util.array.min([r, g, b]);
l = (max + min) / 2;
if (max === min) {
h = s = 0; // achromatic
}
else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return [
Math.round(h * 360),
Math.round(s * 100),
Math.round(l * 100)
];
},
/**
* Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
* @return {Array}
*/
getSource: function() {
return this._source;
},
/**
* Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
* @param {Array} source
*/
setSource: function(source) {
this._source = source;
},
/**
* Returns color representation in RGB format
* @return {String} ex: rgb(0-255,0-255,0-255)
*/
toRgb: function() {
var source = this.getSource();
return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')';
},
/**
* Returns color representation in RGBA format
* @return {String} ex: rgba(0-255,0-255,0-255,0-1)
*/
toRgba: function() {
var source = this.getSource();
return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')';
},
/**
* Returns color representation in HSL format
* @return {String} ex: hsl(0-360,0%-100%,0%-100%)
*/
toHsl: function() {
var source = this.getSource(),
hsl = this._rgbToHsl(source[0], source[1], source[2]);
return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)';
},
/**
* Returns color representation in HSLA format
* @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1)
*/
toHsla: function() {
var source = this.getSource(),
hsl = this._rgbToHsl(source[0], source[1], source[2]);
return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')';
},
/**
* Returns color representation in HEX format
* @return {String} ex: FF5555
*/
toHex: function() {
var source = this.getSource(), r, g, b;
r = source[0].toString(16);
r = (r.length === 1) ? ('0' + r) : r;
g = source[1].toString(16);
g = (g.length === 1) ? ('0' + g) : g;
b = source[2].toString(16);
b = (b.length === 1) ? ('0' + b) : b;
return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
},
/**
* Returns color representation in HEXA format
* @return {String} ex: FF5555CC
*/
toHexa: function() {
var source = this.getSource(), a;
a = Math.round(source[3] * 255);
a = a.toString(16);
a = (a.length === 1) ? ('0' + a) : a;
return this.toHex() + a.toUpperCase();
},
/**
* Gets value of alpha channel for this color
* @return {Number} 0-1
*/
getAlpha: function() {
return this.getSource()[3];
},
/**
* Sets value of alpha channel for this color
* @param {Number} alpha Alpha value 0-1
* @return {fabric.Color} thisArg
*/
setAlpha: function(alpha) {
var source = this.getSource();
source[3] = alpha;
this.setSource(source);
return this;
},
/**
* Transforms color to its grayscale representation
* @return {fabric.Color} thisArg
*/
toGrayscale: function() {
var source = this.getSource(),
average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10),
currentAlpha = source[3];
this.setSource([average, average, average, currentAlpha]);
return this;
},
/**
* Transforms color to its black and white representation
* @param {Number} threshold
* @return {fabric.Color} thisArg
*/
toBlackWhite: function(threshold) {
var source = this.getSource(),
average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0),
currentAlpha = source[3];
threshold = threshold || 127;
average = (Number(average) < Number(threshold)) ? 0 : 255;
this.setSource([average, average, average, currentAlpha]);
return this;
},
/**
* Overlays color with another color
* @param {String|fabric.Color} otherColor
* @return {fabric.Color} thisArg
*/
overlayWith: function(otherColor) {
if (!(otherColor instanceof Color)) {
otherColor = new Color(otherColor);
}
var result = [],
alpha = this.getAlpha(),
otherAlpha = 0.5,
source = this.getSource(),
otherSource = otherColor.getSource(), i;
for (i = 0; i < 3; i++) {
result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha)));
}
result[3] = alpha;
this.setSource(result);
return this;
}
};
/**
* Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5))
* @static
* @field
* @memberOf fabric.Color
*/
// eslint-disable-next-line max-len
fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/;
/**
* Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 ))
* @static
* @field
* @memberOf fabric.Color
*/
fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/;
/**
* Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff)
* @static
* @field
* @memberOf fabric.Color
*/
fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i;
/**
* Map of the 148 color names with HEX code
* @static
* @field
* @memberOf fabric.Color
* @see: https://www.w3.org/TR/css3-color/#svg-color
*/
fabric.Color.colorNameMap = {
aliceblue: '#F0F8FF',
antiquewhite: '#FAEBD7',
aqua: '#00FFFF',
aquamarine: '#7FFFD4',
azure: '#F0FFFF',
beige: '#F5F5DC',
bisque: '#FFE4C4',
black: '#000000',
blanchedalmond: '#FFEBCD',
blue: '#0000FF',
blueviolet: '#8A2BE2',
brown: '#A52A2A',
burlywood: '#DEB887',
cadetblue: '#5F9EA0',
chartreuse: '#7FFF00',
chocolate: '#D2691E',
coral: '#FF7F50',
cornflowerblue: '#6495ED',
cornsilk: '#FFF8DC',
crimson: '#DC143C',
cyan: '#00FFFF',
darkblue: '#00008B',
darkcyan: '#008B8B',
darkgoldenrod: '#B8860B',
darkgray: '#A9A9A9',
darkgrey: '#A9A9A9',
darkgreen: '#006400',
darkkhaki: '#BDB76B',
darkmagenta: '#8B008B',
darkolivegreen: '#556B2F',
darkorange: '#FF8C00',
darkorchid: '#9932CC',
darkred: '#8B0000',
darksalmon: '#E9967A',
darkseagreen: '#8FBC8F',
darkslateblue: '#483D8B',
darkslategray: '#2F4F4F',
darkslategrey: '#2F4F4F',
darkturquoise: '#00CED1',
darkviolet: '#9400D3',
deeppink: '#FF1493',
deepskyblue: '#00BFFF',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1E90FF',
firebrick: '#B22222',
floralwhite: '#FFFAF0',
forestgreen: '#228B22',
fuchsia: '#FF00FF',
gainsboro: '#DCDCDC',
ghostwhite: '#F8F8FF',
gold: '#FFD700',
goldenrod: '#DAA520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#ADFF2F',
honeydew: '#F0FFF0',
hotpink: '#FF69B4',
indianred: '#CD5C5C',
indigo: '#4B0082',
ivory: '#FFFFF0',
khaki: '#F0E68C',
lavender: '#E6E6FA',
lavenderblush: '#FFF0F5',
lawngreen: '#7CFC00',
lemonchiffon: '#FFFACD',
lightblue: '#ADD8E6',
lightcoral: '#F08080',
lightcyan: '#E0FFFF',
lightgoldenrodyellow: '#FAFAD2',
lightgray: '#D3D3D3',
lightgrey: '#D3D3D3',
lightgreen: '#90EE90',
lightpink: '#FFB6C1',
lightsalmon: '#FFA07A',
lightseagreen: '#20B2AA',
lightskyblue: '#87CEFA',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#B0C4DE',
lightyellow: '#FFFFE0',
lime: '#00FF00',
limegreen: '#32CD32',
linen: '#FAF0E6',
magenta: '#FF00FF',
maroon: '#800000',
mediumaquamarine: '#66CDAA',
mediumblue: '#0000CD',
mediumorchid: '#BA55D3',
mediumpurple: '#9370DB',
mediumseagreen: '#3CB371',
mediumslateblue: '#7B68EE',
mediumspringgreen: '#00FA9A',
mediumturquoise: '#48D1CC',
mediumvioletred: '#C71585',
midnightblue: '#191970',
mintcream: '#F5FFFA',
mistyrose: '#FFE4E1',
moccasin: '#FFE4B5',
navajowhite: '#FFDEAD',
navy: '#000080',
oldlace: '#FDF5E6',
olive: '#808000',
olivedrab: '#6B8E23',
orange: '#FFA500',
orangered: '#FF4500',
orchid: '#DA70D6',
palegoldenrod: '#EEE8AA',
palegreen: '#98FB98',
paleturquoise: '#AFEEEE',
palevioletred: '#DB7093',
papayawhip: '#FFEFD5',
peachpuff: '#FFDAB9',
peru: '#CD853F',
pink: '#FFC0CB',
plum: '#DDA0DD',
powderblue: '#B0E0E6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#FF0000',
rosybrown: '#BC8F8F',
royalblue: '#4169E1',
saddlebrown: '#8B4513',
salmon: '#FA8072',
sandybrown: '#F4A460',
seagreen: '#2E8B57',
seashell: '#FFF5EE',
sienna: '#A0522D',
silver: '#C0C0C0',
skyblue: '#87CEEB',
slateblue: '#6A5ACD',
slategray: '#708090',
slategrey: '#708090',
snow: '#FFFAFA',
springgreen: '#00FF7F',
steelblue: '#4682B4',
tan: '#D2B48C',
teal: '#008080',
thistle: '#D8BFD8',
tomato: '#FF6347',
turquoise: '#40E0D0',
violet: '#EE82EE',
wheat: '#F5DEB3',
white: '#FFFFFF',
whitesmoke: '#F5F5F5',
yellow: '#FFFF00',
yellowgreen: '#9ACD32'
};
/**
* @private
* @param {Number} p
* @param {Number} q
* @param {Number} t
* @return {Number}
*/
function hue2rgb(p, q, t) {
if (t < 0) {
t += 1;
}
if (t > 1) {
t -= 1;
}
if (t < 1 / 6) {
return p + (q - p) * 6 * t;
}
if (t < 1 / 2) {
return q;
}
if (t < 2 / 3) {
return p + (q - p) * (2 / 3 - t) * 6;
}
return p;
}
/**
* Returns new color object, when given a color in RGB format
* @memberOf fabric.Color
* @param {String} color Color value ex: rgb(0-255,0-255,0-255)
* @return {fabric.Color}
*/
fabric.Color.fromRgb = function(color) {
return Color.fromSource(Color.sourceFromRgb(color));
};
/**
* Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
* @memberOf fabric.Color
* @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%)
* @return {Array} source
*/
fabric.Color.sourceFromRgb = function(color) {
var match = color.match(Color.reRGBa);
if (match) {
var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1),
g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1),
b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1);
return [
parseInt(r, 10),
parseInt(g, 10),
parseInt(b, 10),
match[4] ? parseFloat(match[4]) : 1
];
}
};
/**
* Returns new color object, when given a color in RGBA format
* @static
* @function
* @memberOf fabric.Color
* @param {String} color
* @return {fabric.Color}
*/
fabric.Color.fromRgba = Color.fromRgb;
/**
* Returns new color object, when given a color in HSL format
* @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%)
* @memberOf fabric.Color
* @return {fabric.Color}
*/
fabric.Color.fromHsl = function(color) {
return Color.fromSource(Color.sourceFromHsl(color));
};
/**
* Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format.
* Adapted from <a href="https://rawgithub.com/mjijackson/mjijackson.github.com/master/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript.html">https://github.com/mjijackson</a>
* @memberOf fabric.Color
* @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1)
* @return {Array} source
* @see http://http://www.w3.org/TR/css3-color/#hsl-color
*/
fabric.Color.sourceFromHsl = function(color) {
var match = color.match(Color.reHSLa);
if (!match) {
return;
}
var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360,
s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1),
l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1),
r, g, b;
if (s === 0) {
r = g = b = l;
}
else {
var q = l <= 0.5 ? l * (s + 1) : l + s - l * s,
p = l * 2 - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255),
match[4] ? parseFloat(match[4]) : 1
];
};
/**
* Returns new color object, when given a color in HSLA format
* @static
* @function
* @memberOf fabric.Color
* @param {String} color
* @return {fabric.Color}
*/
fabric.Color.fromHsla = Color.fromHsl;
/**
* Returns new color object, when given a color in HEX format
* @static
* @memberOf fabric.Color
* @param {String} color Color value ex: FF5555
* @return {fabric.Color}
*/
fabric.Color.fromHex = function(color) {
return Color.fromSource(Color.sourceFromHex(color));
};
/**
* Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format
* @static
* @memberOf fabric.Color
* @param {String} color ex: FF5555 or FF5544CC (RGBa)
* @return {Array} source
*/
fabric.Color.sourceFromHex = function(color) {
if (color.match(Color.reHex)) {
var value = color.slice(color.indexOf('#') + 1),
isShortNotation = (value.length === 3 || value.length === 4),
isRGBa = (value.length === 8 || value.length === 4),
r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2),
g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4),
b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6),
a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF';
return [
parseInt(r, 16),
parseInt(g, 16),
parseInt(b, 16),
parseFloat((parseInt(a, 16) / 255).toFixed(2))
];
}
};
/**
* Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
* @static
* @memberOf fabric.Color
* @param {Array} source
* @return {fabric.Color}
*/
fabric.Color.fromSource = function(source) {
var oColor = new Color();
oColor.setSource(source);
return oColor;
};
})(typeof exports !== 'undefined' ? exports : this);
(function() {
/* _FROM_SVG_START_ */
function getColorStop(el) {
var style = el.getAttribute('style'),
offset = el.getAttribute('offset') || 0,
color, colorAlpha, opacity, i;
// convert percents to absolute values
offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
offset = offset < 0 ? 0 : offset > 1 ? 1 : offset;
if (style) {
var keyValuePairs = style.split(/\s*;\s*/);
if (keyValuePairs[keyValuePairs.length - 1] === '') {
keyValuePairs.pop();
}
for (i = keyValuePairs.length; i--; ) {
var split = keyValuePairs[i].split(/\s*:\s*/),
key = split[0].trim(),
value = split[1].trim();
if (key === 'stop-color') {
color = value;
}
else if (key === 'stop-opacity') {
opacity = value;
}
}
}
if (!color) {
color = el.getAttribute('stop-color') || 'rgb(0,0,0)';
}
if (!opacity) {
opacity = el.getAttribute('stop-opacity');
}
color = new fabric.Color(color);
colorAlpha = color.getAlpha();
opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity);
opacity *= colorAlpha;
return {
offset: offset,
color: color.toRgb(),
opacity: opacity
};
}
function getLinearCoords(el) {
return {
x1: el.getAttribute('x1') || 0,
y1: el.getAttribute('y1') || 0,
x2: el.getAttribute('x2') || '100%',
y2: el.getAttribute('y2') || 0
};
}
function getRadialCoords(el) {
return {
x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%',
y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%',
r1: 0,
x2: el.getAttribute('cx') || '50%',
y2: el.getAttribute('cy') || '50%',
r2: el.getAttribute('r') || '50%'
};
}
/* _FROM_SVG_END_ */
var clone = fabric.util.object.clone;
/**
* Gradient class
* @class fabric.Gradient
* @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients}
* @see {@link fabric.Gradient#initialize} for constructor definition
*/
fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ {
/**
* Horizontal offset for aligning gradients coming from SVG when outside pathgroups
* @type Number
* @default 0
*/
offsetX: 0,
/**
* Vertical offset for aligning gradients coming from SVG when outside pathgroups
* @type Number
* @default 0
*/
offsetY: 0,
/**
* Constructor
* @param {Object} [options] Options object with type, coords, gradientUnits and colorStops
* @return {fabric.Gradient} thisArg
*/
initialize: function(options) {
options || (options = { });
var coords = { };
this.id = fabric.Object.__uid++;
this.type = options.type || 'linear';
coords = {
x1: options.coords.x1 || 0,
y1: options.coords.y1 || 0,
x2: options.coords.x2 || 0,
y2: options.coords.y2 || 0
};
if (this.type === 'radial') {
coords.r1 = options.coords.r1 || 0;
coords.r2 = options.coords.r2 || 0;
}
this.coords = coords;
this.colorStops = options.colorStops.slice();
if (options.gradientTransform) {
this.gradientTransform = options.gradientTransform;
}
this.offsetX = options.offsetX || this.offsetX;
this.offsetY = options.offsetY || this.offsetY;
},
/**
* Adds another colorStop
* @param {Object} colorStop Object with offset and color
* @return {fabric.Gradient} thisArg
*/
addColorStop: function(colorStops) {
for (var position in colorStops) {
var color = new fabric.Color(colorStops[position]);
this.colorStops.push({
offset: parseFloat(position),
color: color.toRgb(),
opacity: color.getAlpha()
});
}
return this;
},
/**
* Returns object representation of a gradient
* @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output
* @return {Object}
*/
toObject: function(propertiesToInclude) {
var object = {
type: this.type,
coords: this.coords,
colorStops: this.colorStops,
offsetX: this.offsetX,
offsetY: this.offsetY,
gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform
};
fabric.util.populateWithProperties(this, object, propertiesToInclude);
return object;
},
/* _TO_SVG_START_ */
/**
* Returns SVG representation of an gradient
* @param {Object} object Object to create a gradient for
* @return {String} SVG representation of an gradient (linear/radial)
*/
toSVG: function(object) {
var coords = clone(this.coords, true), i, len,
markup, commonAttributes, colorStops = clone(this.colorStops, true),
needsSwap = coords.r1 > coords.r2,
offsetX = object.width / 2, offsetY = object.height / 2;
// colorStops must be sorted ascending
colorStops.sort(function(a, b) {
return a.offset - b.offset;
});
if (object.type === 'path') {
offsetX -= object.pathOffset.x;
offsetY -= object.pathOffset.y;
}
for (var prop in coords) {
if (prop === 'x1' || prop === 'x2') {
coords[prop] += this.offsetX - offsetX;
}
else if (prop === 'y1' || prop === 'y2') {
coords[prop] += this.offsetY - offsetY;
}
}
commonAttributes = 'id="SVGID_' + this.id +
'" gradientUnits="userSpaceOnUse"';
if (this.gradientTransform) {
commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" ';
}
if (this.type === 'linear') {
markup = [
'<linearGradient ',
commonAttributes,
' x1="', coords.x1,
'" y1="', coords.y1,
'" x2="', coords.x2,
'" y2="', coords.y2,
'">\n'
];
}
else if (this.type === 'radial') {
// svg radial gradient has just 1 radius. the biggest.
markup = [
'<radialGradient ',
commonAttributes,
' cx="', needsSwap ? coords.x1 : coords.x2,
'" cy="', needsSwap ? coords.y1 : coords.y2,
'" r="', needsSwap ? coords.r1 : coords.r2,
'" fx="', needsSwap ? coords.x2 : coords.x1,
'" fy="', needsSwap ? coords.y2 : coords.y1,
'">\n'
];
}
if (this.type === 'radial') {
if (needsSwap) {
// svg goes from internal to external radius. if radius are inverted, swap color stops.
colorStops = colorStops.concat();
colorStops.reverse();
for (i = 0, len = colorStops.length; i < len; i++) {
colorStops[i].offset = 1 - colorStops[i].offset;
}
}
var minRadius = Math.min(coords.r1, coords.r2);
if (minRadius > 0) {
// i have to shift all colorStops and add new one in 0.
var maxRadius = Math.max(coords.r1, coords.r2),
percentageShift = minRadius / maxRadius;
for (i = 0, len = colorStops.length; i < len; i++) {
colorStops[i].offset += percentageShift * (1 - colorStops[i].offset);
}
}
}
for (i = 0, len = colorStops.length; i < len; i++) {
var colorStop = colorStops[i];
markup.push(
'<stop ',
'offset="', (colorStop.offset * 100) + '%',
'" style="stop-color:', colorStop.color,
(typeof colorStop.opacity !== 'undefined' ? ';stop-opacity: ' + colorStop.opacity : ';'),
'"/>\n'
);
}
markup.push((this.type === 'linear' ? '</linearGradient>\n' : '</radialGradient>\n'));
return markup.join('');
},
/* _TO_SVG_END_ */
/**
* Returns an instance of CanvasGradient
* @param {CanvasRenderingContext2D} ctx Context to render on
* @return {CanvasGradient}
*/
toLive: function(ctx) {
var gradient, coords = fabric.util.object.clone(this.coords), i, len;
if (!this.type) {
return;
}
if (this.type === 'linear') {
gradient = ctx.createLinearGradient(
coords.x1, coords.y1, coords.x2, coords.y2);
}
else if (this.type === 'radial') {
gradient = ctx.createRadialGradient(
coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2);
}
for (i = 0, len = this.colorStops.length; i < len; i++) {
var color = this.colorStops[i].color,
opacity = this.colorStops[i].opacity,
offset = this.colorStops[i].offset;
if (typeof opacity !== 'undefined') {
color = new fabric.Color(color).setAlpha(opacity).toRgba();
}
gradient.addColorStop(offset, color);
}
return gradient;
}
});
fabric.util.object.extend(fabric.Gradient, {
/* _FROM_SVG_START_ */
/**
* Returns {@link fabric.Gradient} instance from an SVG element
* @static
* @memberOf fabric.Gradient
* @param {SVGGradientElement} el SVG gradient element
* @param {fabric.Object} instance
* @return {fabric.Gradient} Gradient instance
* @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
* @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement
*/
fromElement: function(el, instance) {
/**
* @example:
*
* <linearGradient id="linearGrad1">
* <stop offset="0%" stop-color="white"/>
* <stop offset="100%" stop-color="black"/>
* </linearGradient>
*
* OR
*
* <linearGradient id="linearGrad2">
* <stop offset="0" style="stop-color:rgb(255,255,255)"/>
* <stop offset="1" style="stop-color:rgb(0,0,0)"/>
* </linearGradient>
*
* OR
*
* <radialGradient id="radialGrad1">
* <stop offset="0%" stop-color="white" stop-opacity="1" />
* <stop offset="50%" stop-color="black" stop-opacity="0.5" />
* <stop offset="100%" stop-color="white" stop-opacity="1" />
* </radialGradient>
*
* OR
*
* <radialGradient id="radialGrad2">
* <stop offset="0" stop-color="rgb(255,255,255)" />
* <stop offset="0.5" stop-color="rgb(0,0,0)" />
* <stop offset="1" stop-color="rgb(255,255,255)" />
* </radialGradient>
*
*/
var colorStopEls = el.getElementsByTagName('stop'),
type,
gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox',
gradientTransform = el.getAttribute('gradientTransform'),
colorStops = [],
coords, ellipseMatrix, i;
if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') {
type = 'linear';
}
else {
type = 'radial';
}
if (type === 'linear') {
coords = getLinearCoords(el);
}
else if (type === 'radial') {
coords = getRadialCoords(el);
}
for (i = colorStopEls.length; i--; ) {
colorStops.push(getColorStop(colorStopEls[i]));
}
ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits);
var gradient = new fabric.Gradient({
type: type,
coords: coords,
colorStops: colorStops,
offsetX: -instance.left,
offsetY: -instance.top
});
if (gradientTransform || ellipseMatrix !== '') {
gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix);
}
return gradient;
},
/* _FROM_SVG_END_ */
/**
* Returns {@link fabric.Gradient} instance from its object representation
* @static
* @memberOf fabric.Gradient
* @param {Object} obj
* @param {Object} [options] Options object
*/
forObject: function(obj, options) {
options || (options = { });
_convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse');
return new fabric.Gradient(options);
}
});
/**
* @private
*/
function _convertPercentUnitsToValues(object, options, gradientUnits) {
var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = '';
for (var prop in options) {
if (options[prop] === 'Infinity') {
options[prop] = 1;
}
else if (options[prop] === '-Infinity') {
options[prop] = 0;
}
propValue = parseFloat(options[prop], 10);
if (typeof options[prop] === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(options[prop])) {
multFactor = 0.01;
}
else {
multFactor = 1;
}
if (prop === 'x1' || prop === 'x2' || prop === 'r2') {
multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1;
addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0;
}
else if (prop === 'y1' || prop === 'y2') {
multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1;
addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0;
}
options[prop] = propValue * multFactor + addFactor;
}
if (object.type === 'ellipse' &&
options.r2 !== nu
gitextract_2uotyvr1/
├── .github/
│ └── workflows/
│ └── actinos_workflow.yml
├── .gitignore
├── LICENSE
├── README.md
├── calliar_dataset/
│ ├── dataset.npz
│ ├── dataset_words.npz
│ └── dataset_words_str.npz
├── calliar_server/
│ ├── calliar_server/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── server/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ │ └── __init__.py
│ ├── models.py
│ ├── static/
│ │ ├── explore.js
│ │ ├── fabric.js
│ │ ├── keyboard.css
│ │ ├── keyboard.js
│ │ ├── main.js
│ │ ├── main2.js
│ │ ├── paper-full.js
│ │ ├── paper.js
│ │ ├── unicode.js
│ │ └── utils.js
│ ├── templates/
│ │ └── server/
│ │ ├── explore.html
│ │ ├── index.html
│ │ └── index2.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── demo.ipynb
├── media/
│ └── calliar_images/
│ └── annotations/
│ ├── 1ان المتقين في جنات وعيون ادخلوها بسلام آمنين.json
│ ├── الحمد لله copy.json
│ ├── الحمد لله.json
│ ├── خالد.json
│ └── رب يسر ولا تعسر رب تمم بالخير.json
├── notebooks/
│ ├── Check Wrong Annotations.ipynb
│ ├── Clustering Characters.ipynb
│ ├── Collect bism allah.ipynb
│ ├── Colorize text and image.ipynb
│ ├── Convert Strokes to Images.ipynb
│ ├── Fix alignments of strokes .ipynb
│ ├── Generate Characters and words.ipynb
│ ├── Generate as minified json.ipynb
│ ├── Split dataset .ipynb
│ ├── data npz generator.ipynb
│ ├── data word npz generator.ipynb
│ └── data_visualizer.ipynb
├── page/
│ ├── index.html
│ ├── index.js
│ ├── index_ar.html
│ ├── json.js
│ └── json_sm.js
├── requirements.txt
└── scripts/
├── chars.py
└── vis.py
SYMBOL INDEX (429 symbols across 13 files)
FILE: calliar_server/manage.py
function main (line 7) | def main():
FILE: calliar_server/server/apps.py
class ServerConfig (line 4) | class ServerConfig(AppConfig):
FILE: calliar_server/server/static/explore.js
function randomNumber (line 69) | function randomNumber(min, max) {
function getJsonList (line 73) | function getJsonList(){
function getJsonUrl (line 81) | function getJsonUrl(id = undefined){
function disableBtns (line 94) | function disableBtns(){
function enableBtns (line 99) | function enableBtns(){
function generateNext (line 103) | function generateNext(){
function generatePrev (line 108) | function generatePrev(){
function setImage (line 113) | function setImage(w, h){
function addRaster (line 130) | function addRaster(imageName)
function generate (line 138) | function generate(id = undefined) {
function start (line 164) | async function start() {
function onClick (line 171) | function onClick(id){
function createBtn (line 174) | function createBtn(content, id = 0){
function speedUp (line 196) | function speedUp(){
function speedDown (line 200) | function speedDown(){
function speedReset (line 204) | function speedReset(){
FILE: calliar_server/server/static/fabric.js
function _removeEventListener (line 168) | function _removeEventListener(eventName, handler) {
function observe (line 191) | function observe(eventName, handler) {
function stopObserving (line 221) | function stopObserving(eventName, handler) {
function fire (line 254) | function fire(eventName, options) {
function onLoaded (line 980) | function onLoaded() {
function onLoaded (line 1021) | function onLoaded() {
function arcToSegments (line 1373) | function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
function segmentToBezier (line 1434) | function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, f...
function calcVectorAngle (line 1462) | function calcVectorAngle(ux, uy, vx, vy) {
function getBoundsOfCurve (line 1541) | function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
function invoke (line 1634) | function invoke(array, method) {
function max (line 1649) | function max(array, byProperty) {
function min (line 1662) | function min(array, byProperty) {
function fill (line 1671) | function fill(array, value) {
function find (line 1682) | function find(array, byProperty, condition) {
function extend (line 1729) | function extend(destination, source, deep) {
function clone (line 1771) | function clone(object, deep) {
function camelize (line 1792) | function camelize(string) {
function capitalize (line 1807) | function capitalize(string, firstLetterOnly) {
function escapeXml (line 1818) | function escapeXml(string) {
function graphemeSplit (line 1832) | function graphemeSplit(textstring) {
function getWholeChar (line 1844) | function getWholeChar(str, i) {
function Subclass (line 1946) | function Subclass() { }
function callSuper (line 1948) | function callSuper(methodName) {
function createClass (line 1979) | function createClass() {
function areHostMethods (line 2019) | function areHostMethods(object) {
function createListener (line 2053) | function createListener(uid, handler) {
function createWrappedHandler (line 2060) | function createWrappedHandler(uid, handler) {
function createDispatcher (line 2066) | function createDispatcher(uid, eventName) {
function getPointer (line 2203) | function getPointer(event) {
function _getPointer (line 2224) | function _getPointer(event, pageProp, clientProp) {
function setStyle (line 2262) | function setStyle(element, styles) {
function getById (line 2335) | function getById(id) {
function makeElement (line 2372) | function makeElement(tagName, attributes) {
function addClass (line 2394) | function addClass(element, className) {
function wrapElement (line 2408) | function wrapElement(element, wrapper, attributes) {
function getScrollLeftTop (line 2425) | function getScrollLeftTop(element) {
function getElementOffset (line 2467) | function getElementOffset(element) {
function makeElementUnselectable (line 2543) | function makeElementUnselectable(element) {
function makeElementSelectable (line 2562) | function makeElementSelectable(element) {
function getScript (line 2587) | function getScript(url, callback) {
function getNodeCanvas (line 2614) | function getNodeCanvas(element) {
function addParamToUrl (line 2634) | function addParamToUrl(url, param) {
function emptyFn (line 2656) | function emptyFn() { }
function request (line 2669) | function request(url, options) {
function noop (line 2738) | function noop() {
function animate (line 2754) | function animate(options) {
function requestAnimFrame (line 2811) | function requestAnimFrame() {
function cancelAnimFrame (line 2815) | function cancelAnimFrame() {
function calculateColor (line 2829) | function calculateColor(begin, end, pos) {
function animateColor (line 2851) | function animateColor(fromColor, toColor, duration, options) {
function normalize (line 2878) | function normalize(a, c, p, s) {
function elastic (line 2895) | function elastic(opts, t, d) {
function easeOutCubic (line 2905) | function easeOutCubic(t, b, c, d) {
function easeInOutCubic (line 2913) | function easeInOutCubic(t, b, c, d) {
function easeInQuart (line 2925) | function easeInQuart(t, b, c, d) {
function easeOutQuart (line 2933) | function easeOutQuart(t, b, c, d) {
function easeInOutQuart (line 2941) | function easeInOutQuart(t, b, c, d) {
function easeInQuint (line 2953) | function easeInQuint(t, b, c, d) {
function easeOutQuint (line 2961) | function easeOutQuint(t, b, c, d) {
function easeInOutQuint (line 2969) | function easeInOutQuint(t, b, c, d) {
function easeInSine (line 2981) | function easeInSine(t, b, c, d) {
function easeOutSine (line 2989) | function easeOutSine(t, b, c, d) {
function easeInOutSine (line 2997) | function easeInOutSine(t, b, c, d) {
function easeInExpo (line 3005) | function easeInExpo(t, b, c, d) {
function easeOutExpo (line 3013) | function easeOutExpo(t, b, c, d) {
function easeInOutExpo (line 3021) | function easeInOutExpo(t, b, c, d) {
function easeInCirc (line 3039) | function easeInCirc(t, b, c, d) {
function easeOutCirc (line 3047) | function easeOutCirc(t, b, c, d) {
function easeInOutCirc (line 3055) | function easeInOutCirc(t, b, c, d) {
function easeInElastic (line 3067) | function easeInElastic(t, b, c, d) {
function easeOutElastic (line 3087) | function easeOutElastic(t, b, c, d) {
function easeInOutElastic (line 3107) | function easeInOutElastic(t, b, c, d) {
function easeInBack (line 3131) | function easeInBack(t, b, c, d, s) {
function easeOutBack (line 3142) | function easeOutBack(t, b, c, d, s) {
function easeInOutBack (line 3153) | function easeInOutBack(t, b, c, d, s) {
function easeInBounce (line 3168) | function easeInBounce(t, b, c, d) {
function easeOutBounce (line 3176) | function easeOutBounce(t, b, c, d) {
function easeInOutBounce (line 3195) | function easeInOutBounce(t, b, c, d) {
function normalizeAttr (line 3339) | function normalizeAttr(attr) {
function normalizeValue (line 3347) | function normalizeValue(attr, value, parentAttributes, fontSize) {
function getSvgRegex (line 3414) | function getSvgRegex(arr) {
function _setStrokeFillOpacity (line 3422) | function _setStrokeFillOpacity(attributes) {
function _getMultipleNodes (line 3449) | function _getMultipleNodes(doc, nodeNames) {
function rotateMatrix (line 3468) | function rotateMatrix(matrix, args) {
function scaleMatrix (line 3484) | function scaleMatrix(matrix, args) {
function skewMatrix (line 3492) | function skewMatrix(matrix, args, pos) {
function translateMatrix (line 3496) | function translateMatrix(matrix, args) {
function parseStyleString (line 3621) | function parseStyleString(style, oStyle) {
function parseStyleObject (line 3636) | function parseStyleObject(style, oStyle) {
function getGlobalStylesForElement (line 3653) | function getGlobalStylesForElement(element, svgUid) {
function elementMatchesRule (line 3668) | function elementMatchesRule(element, selectors) {
function doesSomeParentMatch (line 3678) | function doesSomeParentMatch(element, selectors) {
function selectorMatches (line 3693) | function selectorMatches(element, selector) {
function elementById (line 3719) | function elementById(doc, id) {
function parseUseDirectives (line 3737) | function parseUseDirectives(doc) {
function applyViewboxTransform (line 3803) | function applyViewboxTransform(element) {
function hasAncestorWithNodeName (line 3912) | function hasAncestorWithNodeName(element, nodeName) {
function onComplete (line 4264) | function onComplete(r) {
function Point (line 4429) | function Point(x, y) {
function Intersection (line 4762) | function Intersection(status) {
function Color (line 4939) | function Color(color) {
function hue2rgb (line 5383) | function hue2rgb(p, q, t) {
function getColorStop (line 5559) | function getColorStop(el) {
function getLinearCoords (line 5608) | function getLinearCoords(el) {
function getRadialCoords (line 5617) | function getRadialCoords(el) {
function _convertPercentUnitsToValues (line 5960) | function _convertPercentUnitsToValues(object, options, gradientUnits) {
function checkClick (line 10695) | function checkClick(e, value) {
function getCoords (line 14262) | function getCoords(coords) {
function getSvgColorString (line 14980) | function getSvgColorString(prop, value) {
function saveProps (line 15206) | function saveProps(origin, destination, props) {
function _isEqual (line 15214) | function _isEqual(origValue, currentValue, firstPass) {
function _callback (line 16257) | function _callback(instance) {
function makeEdgeToOriginGetter (line 16269) | function makeEdgeToOriginGetter(propertyNames, originValues) {
function isValidRadius (line 16492) | function isValidRadius(attributes) {
function testPrecision (line 19880) | function testPrecision(gl, precision){
function WebglFilterBackend (line 19925) | function WebglFilterBackend(options) {
function resizeCanvasIfNeeded (line 20248) | function resizeCanvasIfNeeded(pipelineState) {
function copyGLTo2DDrawImage (line 20270) | function copyGLTo2DDrawImage(gl, pipelineState) {
function copyGLTo2DPutImageData (line 20289) | function copyGLTo2DPutImageData(gl, pipelineState) {
function Canvas2dFilterBackend (line 20317) | function Canvas2dFilterBackend() {}
function process (line 22956) | function process(u) {
function parseDecoration (line 25638) | function parseDecoration(object) {
FILE: calliar_server/server/static/keyboard.js
function VKI_buildKeyboardInputs (line 27) | function VKI_buildKeyboardInputs() {
function VKI_findPos (line 489) | function VKI_findPos(obj) {
function VKI_innerDimensions (line 498) | function VKI_innerDimensions() {
function VKI_getStyle (line 508) | function VKI_getStyle(obj, styleProp) {
function VKI_disableSelection (line 516) | function VKI_disableSelection(elem) {
FILE: calliar_server/server/static/main.js
function recordCoor (line 22) | function recordCoor(event) {
function preprocess (line 31) | function preprocess(name)
function addImage (line 72) | function addImage(imageName)
function getImageUrl (line 114) | function getImageUrl(){
function start (line 125) | async function start() {
function undo (line 189) | function undo() {
function save (line 203) | function save() {
function clearCanvas (line 234) | function clearCanvas()
function next (line 241) | function next() {
function erase (line 250) | function erase() {
FILE: calliar_server/server/static/main2.js
function recordCoor (line 17) | function recordCoor(event) {
function addImage (line 30) | function addImage(imageName)
function getImageUrl (line 50) | function getImageUrl(){
function start (line 58) | async function start() {
function save (line 78) | function save() {
function clearCanvas (line 100) | function clearCanvas()
function next (line 107) | function next() {
function erase (line 119) | function erase() {
FILE: calliar_server/server/static/paper-full.js
function inject (line 80) | function inject(dest, src, enumerable, beans, preserve) {
function Base (line 128) | function Base() {
function handleKey (line 436) | function handleKey(key) {
function handleAttribute (line 885) | function handleAttribute(name) {
function getBounds (line 947) | function getBounds(items) {
function getBounds (line 964) | function getBounds(curves) {
function binarySearch (line 1005) | function binarySearch(indices, coord, value) {
function clamp (line 1182) | function clamp(value, min, max) {
function getDiscriminant (line 1186) | function getDiscriminant(a, b, c) {
function getNormalizationFactor (line 1211) | function getNormalizationFactor() {
function evaluate (line 1323) | function evaluate(x0) {
function serialize (line 3245) | function serialize(fields) {
function hitTest (line 3978) | function hitTest() {
function hitTestAll (line 3985) | function hitTestAll() {
function hitTestChildren (line 3994) | function hitTestChildren(point, options, viewMatrix, _exclude) {
function filter (line 4051) | function filter(hit) {
function checkPoint (line 4059) | function checkPoint(type, part) {
function matchObject (line 4111) | function matchObject(obj1, obj2) {
function getList (line 4464) | function getList(item) {
function onFrame (line 4892) | function onFrame(event) {
function getCornerCenter (line 5233) | function getCornerCenter(that, point, expand) {
function isOnEllipseStroke (line 5250) | function isOnEllipseStroke(point, radius, padding, quadrant) {
function createShape (line 5307) | function createShape(type, point, size, radius, args) {
function emit (line 5491) | function emit(event) {
function refine (line 6760) | function refine(t) {
function add (line 6832) | function add(value, padding) {
function getLengthIntegrand (line 7040) | function getLengthIntegrand(v) {
function getIterations (line 7061) | function getIterations(a, b) {
function evaluate (line 7065) | function evaluate(v, t, type, normalized) {
function type (line 7150) | function type(type, t1, t2) {
function f (line 7227) | function f(t) {
function addLocation (line 7287) | function addLocation(locations, include, c1, t1, c2, t2, overlap) {
function addCurveIntersections (line 7307) | function addCurveIntersections(v1, v2, c1, c2, locations, include, flip,
function getConvexHull (line 7380) | function getConvexHull(dq0, dq1, dq2, dq3) {
function clipConvexHull (line 7402) | function clipConvexHull(hullTop, hullBottom, dMin, dMax) {
function clipConvexHullPart (line 7412) | function clipConvexHullPart(part, top, threshold) {
function getCurveLineIntersections (line 7428) | function getCurveLineIntersections(v, px, py, vx, vy) {
function addCurveLineIntersections (line 7450) | function addCurveLineIntersections(v1, v2, c1, c2, locations, include,
function addLineIntersection (line 7467) | function addLineIntersection(v1, v2, c1, c2, locations, include) {
function getCurveIntersections (line 7478) | function getCurveIntersections(v1, v2, c1, c2, locations, include) {
function getSelfIntersection (line 7534) | function getSelfIntersection(v1, c1, locations, include) {
function getIntersections (line 7545) | function getIntersections(curves1, curves2, include, matrix1, matrix2,
function getOverlaps (line 7591) | function getOverlaps(v1, v2) {
function getTimesWithTangent (line 7661) | function getTimesWithTangent(v, tangent) {
function trySegment (line 7790) | function trySegment(segment) {
function addOffsets (line 7956) | function addOffsets(curve, end) {
function isInRange (line 7966) | function isInRange(angle, min, max) {
function insert (line 8016) | function insert(locations, loc, merge) {
function getCoord (line 8130) | function getCoord(index, coord) {
function getPoint (line 8137) | function getPoint(index) {
function addSegment (line 8492) | function addSegment(segment, skipLine) {
function getIndex (line 8904) | function getIndex(value, _default) {
function isCollinear (line 9013) | function isCollinear(i, j) {
function isOrthogonal (line 9024) | function isOrthogonal(i) {
function isArc (line 9034) | function isArc(i) {
function getDistance (line 9053) | function getDistance(i, j) {
function isCloseEnough (line 9191) | function isCloseEnough(pt, padding) {
function checkSegmentPoint (line 9195) | function checkSegmentPoint(seg, pt, name) {
function checkSegmentPoints (line 9209) | function checkSegmentPoints(seg, ends) {
function addToArea (line 9217) | function addToArea(point) {
function checkSegmentStroke (line 9221) | function checkSegmentStroke(segment) {
function drawHandles (line 9361) | function drawHandles(ctx, segments, matrix, size) {
function drawSegments (line 9404) | function drawSegments(ctx, path, matrix) {
function getOffset (line 9481) | function getOffset(i) {
function getCurrentSegment (line 9525) | function getCurrentSegment(that) {
function processSegment (line 9780) | function processSegment(segment) {
function addPoint (line 9819) | function addPoint(point) {
function addRound (line 9823) | function addRound(segment) {
function addJoin (line 9828) | function addJoin(segment, join) {
function addCap (line 9837) | function addCap(segment, cap) {
function createPath (line 9980) | function createPath(segments, closed, args) {
function createEllipse (line 9988) | function createEllipse(center, radius, args) {
function getCurrentPath (line 10280) | function getCurrentPath(that, check) {
function getPaths (line 10339) | function getPaths(path) {
function preparePath (line 10343) | function preparePath(path, resolve) {
function createResult (line 10365) | function createResult(paths, simplify, path1, path2, options) {
function filterIntersection (line 10377) | function filterIntersection(inter) {
function traceBoolean (line 10381) | function traceBoolean(path1, path2, operation, options) {
function splitBoolean (line 10464) | function splitBoolean(path1, path2, operation) {
function linkIntersections (line 10494) | function linkIntersections(from, to) {
function clearCurveHandles (line 10511) | function clearCurveHandles(curves) {
function reorientPaths (line 10516) | function reorientPaths(paths, isInside, clockwise) {
function divideLocations (line 10569) | function divideLocations(locations, include, clearLater) {
function getWinding (line 10649) | function getWinding(point, curves, dir, closed, dontFlip) {
function propagateWinding (line 10819) | function propagateWinding(segment, path1, path2, curveCollisionsMap,
function tracePaths (line 10879) | function tracePaths(segments, operator) {
function hasOverlap (line 11089) | function hasOverlap(seg, path) {
function addCurve (line 11233) | function addCurve(segment1, segment2) {
function computeParts (line 11239) | function computeParts(curve, index, t1, t2) {
function fromCSS (line 11659) | function fromCSS(string) {
function convert (line 12094) | function convert(val) {
function compare (line 12565) | function compare(style1, style2, secondary) {
function handlePrefix (line 12636) | function handlePrefix(el, name, set, value) {
function handleCallbacks (line 12788) | function handleCallbacks() {
function getSize (line 12816) | function getSize(name) {
function getCanvasSize (line 12820) | function getCanvasSize() {
function getView (line 13214) | function getView(event) {
function updateFocus (line 13220) | function updateFocus() {
function handleMouseMove (line 13232) | function handleMouseMove(view, event, point) {
function emitMouseEvent (line 13340) | function emitMouseEvent(obj, target, type, event, point, prevPoint,
function emitMouseEvents (line 13374) | function emitMouseEvents(view, hitItem, type, event, point, prevPoint) {
function responds (line 13418) | function responds(type) {
function emit (line 13505) | function emit(obj) {
function getKey (line 13734) | function getKey(event) {
function handleKey (line 13746) | function handleKey(down, key, character, event) {
function update (line 14007) | function update(minDistance, maxDistance) {
function emit (line 14036) | function emit() {
function getLum (line 14384) | function getLum(r, g, b) {
function setLum (line 14388) | function setLum(r, g, b, l) {
function getSat (line 14411) | function getSat(r, g, b) {
function setSat (line 14415) | function setSat(r, g, b, s) {
function create (line 14638) | function create(tag, attributes, formatter) {
function get (line 14642) | function get(node, name) {
function set (line 14650) | function set(node, attributes, formatter) {
function getTransform (line 14725) | function getTransform(matrix, coordinates, center) {
function exportGroup (line 14766) | function exportGroup(item, options) {
function exportRaster (line 14789) | function exportRaster(item, options) {
function exportPath (line 14802) | function exportPath(item, options) {
function exportShape (line 14839) | function exportShape(item) {
function exportCompoundPath (line 14866) | function exportCompoundPath(item, options) {
function exportSymbolItem (line 14874) | function exportSymbolItem(item, options) {
function exportGradient (line 14896) | function exportGradient(color) {
function exportText (line 14947) | function exportText(item) {
function applyStyle (line 14965) | function applyStyle(item, node, isRoot) {
function getDefinition (line 15012) | function getDefinition(item, type) {
function setDefinition (line 15019) | function setDefinition(item, node, type) {
function exportDefinitions (line 15027) | function exportDefinitions(node, options) {
function exportSVG (line 15050) | function exportSVG(item, options, isRoot) {
function setOptions (line 15064) | function setOptions(options) {
function getValue (line 15122) | function getValue(node, name, isString, allowNull, allowPercent,
function getPoint (line 15138) | function getPoint(node, x, y, allowNull, allowPercent, defaultX, default...
function getSize (line 15145) | function getSize(node, w, h, allowNull, allowPercent) {
function convertValue (line 15152) | function convertValue(value, type, lookup) {
function importGroup (line 15162) | function importGroup(node, type, options, isRoot) {
function importPoly (line 15200) | function importPoly(node, type) {
function importPath (line 15214) | function importPath(node) {
function importGradient (line 15218) | function importGradient(node, type) {
function applyTransform (line 15342) | function applyTransform(item, value, name, node) {
function applyOpacity (line 15381) | function applyOpacity(item, value, name) {
function getAttribute (line 15485) | function getAttribute(node, name, styles) {
function applyAttributes (line 15499) | function applyAttributes(item, node, isRoot) {
function getDefinition (line 15514) | function getDefinition(value) {
function importNode (line 15527) | function importNode(node, options, isRoot) {
function importSVG (line 15586) | function importSVG(source, options, owner) {
function setOptions (line 15712) | function setOptions(opts) {
function getToken (line 15737) | function getToken(forceRegexp) {
function raise (line 15778) | function raise(pos, message) {
function makePredicate (line 15841) | function makePredicate(words) {
function line_loc_t (line 15913) | function line_loc_t() {
function initTokenState (line 15918) | function initTokenState() {
function finishToken (line 15925) | function finishToken(type, val) {
function skipBlockComment (line 15934) | function skipBlockComment() {
function skipLineComment (line 15952) | function skipLineComment() {
function skipSpace (line 15965) | function skipSpace() {
function readToken_dot (line 16005) | function readToken_dot() {
function readToken_slash (line 16012) | function readToken_slash() {
function readToken_mult_modulo (line 16019) | function readToken_mult_modulo() {
function readToken_pipe_amp (line 16025) | function readToken_pipe_amp(code) {
function readToken_caret (line 16032) | function readToken_caret() {
function readToken_plus_min (line 16038) | function readToken_plus_min(code) {
function readToken_lt_gt (line 16054) | function readToken_lt_gt(code) {
function readToken_eq_excl (line 16074) | function readToken_eq_excl(code) {
function getTokenFromCode (line 16080) | function getTokenFromCode(code) {
function readToken (line 16133) | function readToken(forceRegexp) {
function finishOp (line 16153) | function finishOp(type, size) {
function readRegexp (line 16159) | function readRegexp() {
function readInt (line 16186) | function readInt(radix, len) {
function readHexNumber (line 16203) | function readHexNumber() {
function readNumber (line 16211) | function readNumber(startsWithDot) {
function readString (line 16236) | function readString(quote) {
function readHexChar (line 16284) | function readHexChar(len) {
function readWord1 (line 16292) | function readWord1() {
function readWord (line 16320) | function readWord() {
function next (line 16328) | function next() {
function setStrict (line 16335) | function setStrict(strct) {
function node_t (line 16348) | function node_t() {
function node_loc_t (line 16354) | function node_loc_t() {
function startNode (line 16360) | function startNode() {
function startNodeFrom (line 16371) | function startNodeFrom(other) {
function finishNode (line 16384) | function finishNode(node, type) {
function isUseStrict (line 16394) | function isUseStrict(stmt) {
function eat (line 16399) | function eat(type) {
function canInsertSemicolon (line 16406) | function canInsertSemicolon() {
function semicolon (line 16411) | function semicolon() {
function expect (line 16415) | function expect(type) {
function unexpected (line 16420) | function unexpected() {
function checkLVal (line 16424) | function checkLVal(expr) {
function parseTopLevel (line 16431) | function parseTopLevel(program) {
function parseStatement (line 16451) | function parseStatement() {
function parseParenExpression (line 16638) | function parseParenExpression() {
function parseBlock (line 16645) | function parseBlock(allowStrict) {
function parseFor (line 16662) | function parseFor(node, init) {
function parseForIn (line 16674) | function parseForIn(node, init) {
function parseVar (line 16683) | function parseVar(node, noIn) {
function parseExpression (line 16698) | function parseExpression(noComma, noIn) {
function parseMaybeAssign (line 16709) | function parseMaybeAssign(noIn) {
function parseMaybeConditional (line 16723) | function parseMaybeConditional(noIn) {
function parseExprOps (line 16736) | function parseExprOps(noIn) {
function parseExprOp (line 16740) | function parseExprOp(left, minPrec, noIn) {
function parseMaybeUnary (line 16757) | function parseMaybeUnary() {
function parseExprSubscripts (line 16784) | function parseExprSubscripts() {
function parseSubscripts (line 16788) | function parseSubscripts(base, noCalls) {
function parseExprAtom (line 16810) | function parseExprAtom() {
function parseNew (line 16869) | function parseNew() {
function parseObj (line 16878) | function parseObj() {
function parsePropertyName (line 16917) | function parsePropertyName() {
function parseFunction (line 16922) | function parseFunction(node, isStatement) {
function parseExprList (line 16952) | function parseExprList(close, allowTrailingComma, allowEmpty) {
function parseIdent (line 16966) | function parseIdent(liberal) {
function parse (line 16993) | function parse(code, options) {
function __$__ (line 17027) | function __$__(left, operator, right) {
function $__ (line 17044) | function $__(operator, value) {
function compile (line 17054) | function compile(code, options) {
function execute (line 17301) | function execute(code, scope, options) {
function loadScript (line 17377) | function loadScript(script) {
function loadAll (line 17408) | function loadAll() {
function load (line 17413) | function load(script) {
FILE: calliar_server/server/static/paper.js
function addRaster (line 28) | function addRaster(url)
function addImage (line 67) | function addImage(imageName)
function getImageUrl (line 80) | function getImageUrl(){
function start (line 92) | async function start() {
function undo (line 185) | function undo() {
function save (line 199) | function save() {
function clearCanvas (line 233) | function clearCanvas()
function next (line 241) | function next() {
function erase (line 252) | function erase() {
function getNext (line 261) | function getNext(){
function getPrev (line 266) | function getPrev(){
function loadImage (line 271) | function loadImage(event) {
function clearImage (line 290) | function clearImage() {
FILE: calliar_server/server/static/utils.js
function preprocess (line 1) | function preprocess(name)
function getPoints (line 43) | function getPoints(path){
FILE: calliar_server/server/views.py
class EndpointView (line 17) | class EndpointView(View):
method render_to_template (line 20) | def render_to_template(self):
method get (line 29) | def get(self, request, *args, **kwargs):
method post (line 32) | def post(self, request, *args, **kwargs):
class NextImageView (line 61) | class NextImageView(View):
method get_next_image_name (line 63) | def get_next_image_name(self, curr_id):
method get (line 84) | def get(self, request, *args, **kwargs):
class ExploreView (line 87) | class ExploreView(View):
method render_to_template (line 90) | def render_to_template(self):
method get (line 99) | def get(self, request, *args, **kwargs):
class NextJsonView (line 102) | class NextJsonView(View):
method get_next_json_name (line 104) | def get_next_json_name(self, curr_id):
method get (line 115) | def get(self, request, *args, **kwargs):
class ListJsonView (line 119) | class ListJsonView(View):
method get_json_list (line 121) | def get_json_list(self):
method get (line 127) | def get(self, request, *args, **kwargs):
FILE: page/index.js
function create_data (line 7) | function create_data(drawing){
function getSvgPathFromStroke (line 60) | function getSvgPathFromStroke(stroke) {
function randomNumber (line 121) | function randomNumber(min, max) {
FILE: scripts/vis.py
function get_bounds (line 22) | def get_bounds(data):
function convert_3d (line 38) | def convert_3d(drawing, return_flag = False, threshold=10):
function make_square (line 74) | def make_square(im, min_size=256, fill_color=(255, 255, 255)):
function draw_strokes (line 81) | def draw_strokes(data, factor=1, svg_filename = 'tmp/sample.svg', stroke...
function apply_rdb (line 122) | def apply_rdb(drawing, verbose = 0):
function preprocess (line 141) | def preprocess(text):
function concatenate (line 172) | def concatenate(images, mode='h', margin=10):
function generate_characters (line 197) | def generate_characters(file):
function generate_words (line 211) | def generate_words(file):
function get_annotation (line 235) | def get_annotation(json_path):
function clean_text (line 238) | def clean_text(text):
function concatenate (line 248) | def concatenate(images, mode='h', margin=10):
function save_video (line 273) | def save_video(drawing, mx):
function create_animation (line 307) | def create_animation(json_path):
Condensed preview — 60 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (9,292K chars).
[
{
"path": ".github/workflows/actinos_workflow.yml",
"chars": 1378,
"preview": "name: Deployment Workflow\non:\n push:\n branches: [main]\n \njobs:\n first_job:\n name: Deploy Calliar Website\n ru"
},
{
"path": ".gitignore",
"chars": 3619,
"preview": "# https://github.com/github/gitignore/blob/main/Python.gitignore\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*."
},
{
"path": "LICENSE",
"chars": 1062,
"preview": "MIT License\n\nCopyright (c) 2023 ARBML\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
},
{
"path": "README.md",
"chars": 4191,
"preview": "# Calliar\nCalliar is a dataset for Arabic calligraphy. The dataset consists of 2500 json files that contain strokes manu"
},
{
"path": "calliar_server/calliar_server/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "calliar_server/calliar_server/asgi.py",
"chars": 405,
"preview": "\"\"\"\nASGI config for calliar_server project.\n\nIt exposes the ASGI callable as a module-level variable named ``application"
},
{
"path": "calliar_server/calliar_server/settings.py",
"chars": 4041,
"preview": "\"\"\"\nDjango settings for calliar_server project.\n\nGenerated by 'django-admin startproject' using Django 3.2.2.\n\nFor more "
},
{
"path": "calliar_server/calliar_server/urls.py",
"chars": 946,
"preview": "\"\"\"calliar_server URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n h"
},
{
"path": "calliar_server/calliar_server/wsgi.py",
"chars": 405,
"preview": "\"\"\"\nWSGI config for calliar_server project.\n\nIt exposes the WSGI callable as a module-level variable named ``application"
},
{
"path": "calliar_server/manage.py",
"chars": 670,
"preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n "
},
{
"path": "calliar_server/server/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "calliar_server/server/admin.py",
"chars": 63,
"preview": "from django.contrib import admin\n\n# Register your models here.\n"
},
{
"path": "calliar_server/server/apps.py",
"chars": 144,
"preview": "from django.apps import AppConfig\n\n\nclass ServerConfig(AppConfig):\n default_auto_field = 'django.db.models.BigAutoFie"
},
{
"path": "calliar_server/server/migrations/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "calliar_server/server/models.py",
"chars": 57,
"preview": "from django.db import models\n\n# Create your models here.\n"
},
{
"path": "calliar_server/server/static/explore.js",
"chars": 4747,
"preview": "/*\nvariables\n*/\nvar canvas;\nvar paper; \nvar curr_img;\nvar strokeWidth = 3;\nvar currJsonId = 0;\nvar Strokeindex = 0;\nvar "
},
{
"path": "calliar_server/server/static/fabric.js",
"chars": 895774,
"preview": "/* build: `node build.js modules=ALL exclude=gestures,accessors requirejs minifier=uglifyjs` */\n/*! Fabric.js Copyright "
},
{
"path": "calliar_server/server/static/keyboard.css",
"chars": 2907,
"preview": "#keyboardInputMaster {\n position:absolute;\n border:2px groove #dddddd;\n color:#000000;\n background-color:#dddddd;\n "
},
{
"path": "calliar_server/server/static/keyboard.js",
"chars": 22233,
"preview": "/* ********************************************************************\n ***********************************************"
},
{
"path": "calliar_server/server/static/main.js",
"chars": 6423,
"preview": "/*\nvariables\n*/\n\nvar canvas;\nvar mousePressed = false;\nvar curr_img;\nvar currStroke = [];\nvar currSketch = [];\nconst col"
},
{
"path": "calliar_server/server/static/main2.js",
"chars": 2620,
"preview": "/*\nvariables\n*/\n\nvar canvas;\nvar mousePressed = false;\nvar curr_img;\nvar currStroke = [];\nvar currSketch = [];\nconst col"
},
{
"path": "calliar_server/server/static/paper-full.js",
"chars": 450637,
"preview": "/*!\n * Paper.js v0.12.15 - The Swiss Army Knife of Vector Graphics Scripting.\n * http://paperjs.org/\n *\n * Copyright (c)"
},
{
"path": "calliar_server/server/static/paper.js",
"chars": 7160,
"preview": "/*\nvariables\n*/\n\nvar canvas;\nvar mousePressed = false;\nvar curr_img;\nvar currStroke = \"\";\nvar currSketch = [];\nvar currS"
},
{
"path": "calliar_server/server/static/unicode.js",
"chars": 992,
"preview": "//https://jrgraphix.net/r/Unicode/0600-06FF\nmap_chars = {\n \"\\u0623\":[\"\\u0621\", \"\\u0627\"], // أ\n \"\\u0622\":[\"\\u0605\""
},
{
"path": "calliar_server/server/static/utils.js",
"chars": 1158,
"preview": "function preprocess(name)\n{\n var text = name;\n const diacritics = \"[ًٌٍَُِّْ]\"\n const numbers = '0123456789'\n "
},
{
"path": "calliar_server/server/templates/server/explore.html",
"chars": 5618,
"preview": "{% load static %}\n<!DOCTYPE html>\n<html lang=\"\" xml:lang=\"\" xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta char"
},
{
"path": "calliar_server/server/templates/server/index.html",
"chars": 3357,
"preview": "{% load static %}\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <title>Calliar</title>\n\n <link href=\"https://fon"
},
{
"path": "calliar_server/server/templates/server/index2.html",
"chars": 1862,
"preview": "{% load static %}\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<title>Calliar</title>\n\n<!-- bs4 css -->\n<link rel=\"s"
},
{
"path": "calliar_server/server/tests.py",
"chars": 60,
"preview": "from django.test import TestCase\n\n# Create your tests here.\n"
},
{
"path": "calliar_server/server/urls.py",
"chars": 615,
"preview": "from .views import EndpointView, NextImageView, ExploreView, NextJsonView, ListJsonView\nfrom django.urls import path\nfro"
},
{
"path": "calliar_server/server/views.py",
"chars": 4444,
"preview": "from django.shortcuts import render\nfrom django.views.generic import View\n# Create your views here.\nfrom django.http imp"
},
{
"path": "demo.ipynb",
"chars": 61070,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": null,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": "
},
{
"path": "media/calliar_images/annotations/1ان المتقين في جنات وعيون ادخلوها بسلام آمنين.json",
"chars": 8384,
"preview": "[{\"\\u0627\": \"M592.5,252c0,11.66667 0,23.33333 0,35c0,1.912 -1.21678,10.78322 0,12c1.83333,1.83333 -0.83333,8.16667 1,10c"
},
{
"path": "media/calliar_images/annotations/الحمد لله copy.json",
"chars": 553,
"preview": "[{\"\\u0627\": \"M318.27277,269.69035c1.37437,0 2.69616,0.56539 4,1\"}, {\"\\u0644\": \"M280.27277,295.69035\"}, {\"\\u062d\": \"M268."
},
{
"path": "media/calliar_images/annotations/الحمد لله.json",
"chars": 350,
"preview": "[{\"\\u0627\": \"M356.27277,351.69035\"}, {\"\\u0644\": \"M345.27277,343.69035c-0.74536,0 -2,-0.25464 -2,-1\"}, {\"\\u062d\": \"M336.2"
},
{
"path": "media/calliar_images/annotations/خالد.json",
"chars": 2222,
"preview": "[{\".\": \"M385.39999,291c1.01843,0 3,1.98157 3,3\"}, {\"\\u062d\": \"M346.39999,397c0,-7.6845 1,-14.70921 1,-22c0,-1.27188 0.80"
},
{
"path": "media/calliar_images/annotations/رب يسر ولا تعسر رب تمم بالخير.json",
"chars": 9017,
"preview": "[{\"\\u0631\": \"M245.5,424c0,-11.33423 -33.5198,-12.4802 -42,-4c-4.15064,4.15064 -11.48051,5.48051 -15,9c-10.92615,10.92615"
},
{
"path": "notebooks/Check Wrong Annotations.ipynb",
"chars": 66615,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 10,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n"
},
{
"path": "notebooks/Clustering Characters.ipynb",
"chars": 337739,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [\n {\n \"name\":"
},
{
"path": "notebooks/Collect bism allah.ipynb",
"chars": 15174,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 5,\n \"id\": \"graduate-blake\",\n \"metadata\": {\n \"scro"
},
{
"path": "notebooks/Colorize text and image.ipynb",
"chars": 20670,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"id\": \"binding-membrane\",\n \"metadata\": {},\n \"o"
},
{
"path": "notebooks/Convert Strokes to Images.ipynb",
"chars": 572374,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"id\": \"composed-partition\",\n \"metadata\": {},\n "
},
{
"path": "notebooks/Fix alignments of strokes .ipynb",
"chars": 998297,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 12,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n"
},
{
"path": "notebooks/Generate Characters and words.ipynb",
"chars": 3912597,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [\n {\n \"name\":"
},
{
"path": "notebooks/Generate as minified json.ipynb",
"chars": 3594,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"id\": \"alternative-quality\",\n \"metadata\": {},\n "
},
{
"path": "notebooks/Split dataset .ipynb",
"chars": 68618,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 1,\n \"metadata\": {},\n \"outputs\": [\n {\n \"name\":"
},
{
"path": "notebooks/data npz generator.ipynb",
"chars": 16109,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 6,\n \"metadata\": {},\n \"outputs\": [\n {\n \"name\":"
},
{
"path": "notebooks/data word npz generator.ipynb",
"chars": 27385,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 30,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n"
},
{
"path": "notebooks/data_visualizer.ipynb",
"chars": 740805,
"preview": "{\n \"cells\": [\n {\n \"cell_type\": \"code\",\n \"execution_count\": 6,\n \"metadata\": {},\n \"outputs\": [],\n \"source\": [\n "
},
{
"path": "page/index.html",
"chars": 24123,
"preview": "\n<!DOCTYPE html>\n<html lang=\"\" xml:lang=\"\" xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta charset=\"utf-8\"/>\n "
},
{
"path": "page/index.js",
"chars": 3910,
"preview": "var w = 600;\nvar h = 600;\nvar canvas = Raphael('canvas', '300px', '300px');\ncanvas.setViewBox(0,0,w,h);\ncanvas.setSize('"
},
{
"path": "page/index_ar.html",
"chars": 22163,
"preview": "\n<!DOCTYPE html>\n<html lang=\"ar\" xml:lang=\"\" xmlns=\"http://www.w3.org/1999/xhtml\">\n <head>\n <meta charset=\"utf-8\"/>\n "
},
{
"path": "page/json_sm.js",
"chars": 794698,
"preview": "stroke_0 = [[[232, 173],[223, 171],[220, 171],[218, 171],[217, 172],[215, 173],[214, 174],[213, 176],[212, 178],[212, 18"
},
{
"path": "requirements.txt",
"chars": 71,
"preview": "cairosvg\nsvgwrite\nrdp\nmatplotlib\nnumpy\ntqdm\ndjango~=3.2\nbackcall==0.2.0"
},
{
"path": "scripts/chars.py",
"chars": 966,
"preview": "#https:#jrgraphix.net/r/Unicode/0600-06FF\nmap_chars = {\n \"\\u0623\":[\"\\u0621\", \"\\u0627\"], # أ\n \"\\u0622\":[\"\\u0605\", \""
},
{
"path": "scripts/vis.py",
"chars": 9169,
"preview": "import glob\nimport json\nimport math\nimport os\nimport pickle\nimport re\nfrom collections import defaultdict\n\nimport cairos"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the ARBML/Calliar GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 60 files (24.9 MB), approximately 2.3M tokens, and a symbol index with 429 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.