From 683e4b9381171505a0ba37714605c1579a71d4d3 Mon Sep 17 00:00:00 2001 From: bleeptrack Date: Thu, 16 May 2024 16:29:58 +0200 Subject: [PATCH] init --- .gitignore | 15 + Dockerfile | 8 + DrawData.py | 305 + Model.py | 619 ++ config.py | 11 + requirements.txt | 5 + server.py | 224 + static/drawing.js | 422 + static/edgeDetection.js | 538 ++ static/jsfeat-min.js | 1 + static/paper-full.js | 17459 ++++++++++++++++++++++++++++++++++++++ static/style.css | 19 + templates/index.html | 32 + templates/photo.html | 34 + templates/train.html | 21 + 15 files changed, 19713 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 DrawData.py create mode 100644 Model.py create mode 100644 config.py create mode 100644 requirements.txt create mode 100644 server.py create mode 100644 static/drawing.js create mode 100644 static/edgeDetection.js create mode 100755 static/jsfeat-min.js create mode 100644 static/paper-full.js create mode 100644 static/style.css create mode 100644 templates/index.html create mode 100644 templates/photo.html create mode 100644 templates/train.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a49497 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ + +__pycache__/ +bin/ +lib/ + + +pyvenv.cfg + +.prettierrc + +get-docker.sh + +share/man/man1/isympy.1 + +lib64 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c8b51bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +# syntax=docker/dockerfile:1 + +FROM python:3 +WORKDIR /tinqta +COPY . . +RUN pip install -r requirements.txt +CMD ["python", "server.py"] +EXPOSE 5000 diff --git a/DrawData.py b/DrawData.py new file mode 100644 index 0000000..67c5d77 --- /dev/null +++ b/DrawData.py @@ -0,0 +1,305 @@ +import torch +import os +import os.path as osp +from torch_geometric.data import Data, InMemoryDataset +from torch_geometric.nn import GAE, GCNConv +from config import config +import random + +#Loads the dataset and handles delivery +class GraphDataset(InMemoryDataset): + def __init__(self, name, level, transform=None, pre_transform=None, pre_filter=None): + self.base_name = name + self.name = name+"-"+level + self.level = level + #self.maxVal = 0 + + root = osp.join(osp.dirname(osp.realpath(__file__)), 'data', self.name) + + + super().__init__(root, transform, pre_transform, pre_filter) + self.data, self.slices = torch.load(self.processed_paths[0]) + + print(self.name) + + @property + def raw_file_names(self): + return [self.name + '.pt'] + + + @property + def processed_file_names(self): + return ['data.pt'] + + @property + def num_nodes(self): + return self.get(0).num_nodes + + def download(self): + print("Nothing to download...") + + def process(self): + # Read data into huge `Data` list. + complete_data = torch.load(osp.join(osp.dirname(osp.realpath(__file__)), 'baseData', self.name +'.pt')) + + print("handling data at level ", self.level) + + data_list = complete_data[self.level] + print("Loading Dataset of length: ", len(data_list)) + + if self.pre_filter is not None: + data_list = [data for data in data_list if self.pre_filter(data)] + + if self.pre_transform is not None: + data_list = [self.pre_transform(data) for data in data_list] + + data, slices = self.collate(data_list) + torch.save((data, slices), self.processed_paths[0]) + +class GraphHandler: + def __init__(self): + self.clear() + + def clear(self): + self.raw_data = [] + self.outer_ids = [] + + def init_raw(self,raw_data): + #print(raw_data) + self.raw_data = [] + self.outer_ids = [] + self.add_raw(raw_data) + + def add_raw(self, raw_data): + self.raw_data += raw_data['list'] + if('outer_ids' in raw_data): + self.outer_ids += raw_data['outer_ids'] + + + def get_path_name(self, name, type_name): + return osp.join(osp.dirname(osp.realpath(__file__)), 'baseData', name +'-'+ type_name +'.pt') + + + + def add_line_latentspace(self,lineTrainer): + name = lineTrainer.name + for line in self.raw_data: + x, edge_index = self.create_line_graph(line['points']) + z = lineTrainer.encodeLineVector(x, edge_index) + if 'latent_vector' not in line.keys(): + line['latent_vector'] = {} + if name not in line['latent_vector']: + line['latent_vector'][name] = z + print("added latent space vectors to Graph Handler") + + print("known lines:", len(self.raw_data)) + + #def add_line_drawdata(self, data, lineTrainer): + # name = lineTrainer.name + # for line in data: + # x, edge_index = self.create_line_graph(line['points']) + # z = lineTrainer.encodeLineVector(x, edge_index) + # if 'latent_vector' not in line.keys(): + # line['latent_vector'] = {} + # line['latent_vector'][name] = z + # self.raw_data.append(line) + + # print("added latent space vectors to Graph Handler", len(data), "now:", len(self.raw_data)) + + + + def save_line_training_data(self, name): + data_list = [] + for line in self.raw_data: + x, edge_index = self.create_line_graph(line['points']) + data = Data(x=x, edge_index=edge_index, scale=line['scale'], rotation=line['rotation']) + data_list.append(data) + + print(data_list) + torch.save({ 'line': data_list }, self.get_path_name(name, 'line')) + + + + def create_line_graph(self, points): #graph per stroke + connections = [] + hidden_states = [] + for i in range(1,len(points)): + connections.append([i-1,i]) + connections.append([i,i-1]) + + connections.append([0,i]) + connections.append([i,0]) + + if config['double_ended'] : + for i in range(0,len(points)-1): + connections.append([len(points)-1,i]) + connections.append([i,len(points)-1]) + + edge_index = torch.tensor(connections, dtype=torch.long).t().contiguous() + + for point in points: + hidden_states.append([point['x']-points[0]['x'], point['y']-points[0]['y']]) + + x = torch.tensor(hidden_states, dtype=torch.float) + + return x, edge_index + + + + def create_pattern_graph(self, main_id, reference_node_id, ids, latent_name): + + hidden_states = [] + if not reference_node_id in ids: + ids = torch.cat([ torch.tensor([reference_node_id]), ids]) + + #fully connect der nähesten k nodes + connections = torch.combinations(torch.arange(0,len(ids), dtype=torch.int64)) + + #reference_node_id = ids[0] #die referenz node könnte auch durchiteriert werden. referenziert aktuell nur den 0,0 punkt + + for i in ids: + hid = self.assemble_node_hidden_state(i, reference_node_id, self.raw_data[i]['latent_vector'][latent_name]) + hidden_states.append(hid) + + if main_id is not None: + ground_truth = self.assemble_node_hidden_state(main_id, reference_node_id, self.raw_data[main_id]['latent_vector'][latent_name]) + else: + ground_truth = None + print("fail", main_id, reference_node_id) + + edge_index = torch.tensor(connections, dtype=torch.long).t().contiguous() + x = torch.stack(hidden_states, dim=0) #vllt nochmal checken ob der jetzt "richtig rum" ist + + return x, edge_index, ground_truth + + + def sample_graph(self,ref_id, latent_name, max_dist=config['max_dist']): + dists = self.get_distance_matrix() + max_dist = config['max_dist'] + dists = dists * (dists < max_dist) + sorted_dists, indices = torch.sort(dists) + + current = sorted_dists[ref_id] + current_ids = indices[ref_id] + not_zero = current!=0 + current = current[not_zero] + ids = current_ids[not_zero] + + x, edge_index, _ = self.create_pattern_graph(None, ref_id, ids, latent_name) + + return x, edge_index + + def sample_complete_graph(self, latent_name): + samples = [] + for i in range(len(self.raw_data)): + x, edge_idx = self.sample_graph(i, latent_name) + samples.append({ + "x":x, + "edge_index":edge_idx, + "ref_id": i + }) + return samples + + + def save_pattern_training_data(self, latent_name, name): + data_list = [] + + + max_dist = config['max_dist'] + + for current_dist in range(50, max_dist+1, 50): #area of lines to take into context. dropout of lines maybe better? + dists = self.get_distance_matrix() + print("processing data at max distance ", current_dist) + + dists = dists * (dists < current_dist) + sorted_dists, indices = torch.sort(dists) + + + #for ref_id in range(len(sorted_dists)): + inner_ids = [i for i in range(len(sorted_dists)) if i not in self.outer_ids] + for ref_id in inner_ids: + + current = sorted_dists[ref_id] + current_ids = indices[ref_id] + + #remove all nodes that are too far away (have 0) + not_zero = current!=0 + current = current[not_zero] + current_ids = current_ids[not_zero] + if current_ids.size()[0] == 0: + continue + + for l in range(1): #range(len(current_ids)): + + #get ground truth node and remove it from other ids + #GT node could be different ones than only the closest? + + + + pred_id = current_ids[l] + current_drop = torch.cat([current[0:l], current[l+1:]]) # current[1:] + current_ids_drop = torch.cat([current_ids[0:l], current_ids[l+1:]]) #current_ids[1:] + + + #remove edge nodes and only take inner ids + inner = torch.tensor([True if i in inner_ids else False for i in current_ids_drop]) + if inner.size()[0] == 0: + continue + current_drop = current_drop[inner] + current_ids_drop = current_ids_drop[inner] + + x, edge_index, ground_truth = self.create_pattern_graph(pred_id, ref_id, current_ids_drop, latent_name) + data = Data(x=x, edge_index=edge_index, y=ground_truth) + data_list.append(data) + + print("saving dataset of length ", len(data_list)) + torch.save({ 'pattern': data_list }, self.get_path_name(name, 'pattern')) + + + + def getAbsolutePosition(reference_id): + return self.raw_data[reference_id]['points'][0]['x'], self.raw_data[reference_id]['points'][0]['y'] + + def assemble_node_hidden_state(self, current_id, reference_id, current_latent_vector): + lat_vec = current_latent_vector + posX = self.raw_data[current_id]['points'][0]['x'] - self.raw_data[reference_id]['points'][0]['x'] #delta zur main node position + posY = self.raw_data[current_id]['points'][0]['y'] - self.raw_data[reference_id]['points'][0]['y'] + # versuch das relativ anzugeben im bezug zur ... maxdist? + posX = posX / config['max_dist'] + posY = posY / config['max_dist'] + + rot = self.raw_data[current_id]['rotation'] + scale = self.raw_data[current_id]['scale'] + #print(lat_vec.size(), posX, posY, rot, scale) + + return torch.cat( (torch.tensor( [posX, posY, rot, scale], dtype=torch.float), lat_vec), 0) + + + + def decompose_node_hidden_state(z): + info = { + "posX" : z[0].item(), + "posY" : z[1].item(), + "rot" : z[2].item(), + "scale" : z[3].item(), + "latVec" : z[4:] + } + + return info + + + def get_distance_matrix(self): + + dist_list = [] + for line1 in self.raw_data: + dist_list.append( [ line1['points'][0]['x'], line1['points'][0]['y'] ] ) + + dist_tensor = torch.tensor(dist_list).float() + return torch.cdist(dist_tensor, dist_tensor, p=2) + + + + + + + diff --git a/Model.py b/Model.py new file mode 100644 index 0000000..d572126 --- /dev/null +++ b/Model.py @@ -0,0 +1,619 @@ + +import os.path as osp +from functools import reduce +import math +import torch +from torch import Tensor +from torch.optim.lr_scheduler import LambdaLR, StepLR, MultiStepLR, ExponentialLR, ReduceLROnPlateau, LinearLR +from torch.nn import AvgPool1d, MaxPool1d +import random +import torch_geometric.transforms as T +from torch_geometric.datasets import Planetoid +from torch_geometric.utils import dropout_node +from torch_geometric.nn import GAE, VGAE, GCNConv, global_mean_pool, global_max_pool, Linear, TransformerConv, SAGEConv, GCN +from DrawData import GraphDataset +from config import config +from torch_geometric.loader import DataLoader +from torch_geometric.utils.num_nodes import maybe_num_nodes +from torch_geometric.utils import subgraph + + +class GCNEncoder(torch.nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + + num_features = 2 + + self.conv1 = GCNConv(num_features, 2 * num_features) + self.conv2 = GCNConv(2 * num_features, 4 * num_features) + self.lin = Linear(-1, config['latent_size']) + + self.mu = Linear(-1, config['latent_size']) + self.logvar = Linear(-1, config['latent_size']) + + + def pool_pairs(self, x): + + pool = MaxPool1d(2, stride=2) + x = pool(x.permute(1,0)).permute(1,0) + + edge_index = torch.cat( ( torch.arange(0,x.size()[0]-1).unsqueeze(1), torch.arange(1,x.size()[0]).unsqueeze(1) ), 1) + edge_index2 = torch.cat( ( torch.arange(1,x.size()[0]).unsqueeze(1), torch.arange(0,x.size()[0]-1).unsqueeze(1) ), 1) + + edge_index = torch.cat( (edge_index, edge_index2), 0).t().contiguous() + + #print(edge_index, edge_index.size()) + + return x, edge_index + + def forward(self, x, edge_index): + #encode + x = self.conv1(x, edge_index).relu() + x, edge_index = self.pool_pairs(x) + + x = self.conv2(x, edge_index).relu() + x, edge_index = self.pool_pairs(x) + + #reshape to linear + x = x.flatten() + + return self.mu(x), self.logvar(x) + +# def forward(self, x, edge_index): +# #encode +# x = self.conv1(x, edge_index).relu() +# x, edge_index = self.pool_pairs(x) +# +# x = self.conv2(x, edge_index).relu() +# x, edge_index = self.pool_pairs(x) +# +# #reshape to linear +# x = x.flatten() +# +# return self.lin(x) + +class GCNDecoder(torch.nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + + self.lin1 = Linear(-1, int(config['nrPoints']*2 /4) ) + self.lin2 = Linear(-1, int(config['nrPoints']*2 /2) ) + self.lin3 = Linear(-1, int(config['nrPoints']*2) ) + + #mehr decoding layer machen das auf jeden fall besser! + #aber halt auch echt nicht beliebig + + + def forward(self, x): + x = self.lin1(x).relu() + x = self.lin2(x).relu() + x = self.lin3(x) + + #reshape + return x.unflatten(0, ( config['nrPoints'], 2 ) ) + + + + +class PatternEncoder(torch.nn.Module): + + def __init__(self, in_channels, hidden_channels, num_layers, out_channels): + super().__init__() + + extended_lat_size = int( config['latent_size'] + 4 ) # +4 fuer posx, posy, scale, rot + + self.conv = GCNConv( extended_lat_size , extended_lat_size * 2 ) + self.conv2 = GCNConv( extended_lat_size * 2 , extended_lat_size ) + self.lin1 = Linear(-1, extended_lat_size ) + self.lin2 = Linear(-1, extended_lat_size*8 ) + self.lin22 = Linear(-1, extended_lat_size*8 ) + self.lin23 = Linear(-1, extended_lat_size*8 ) + self.lin3 = Linear(-1, extended_lat_size ) + + def dropout_node_min(self, edge_index, batch_vector=None, p = 0.5, num_nodes = None, min_node=3, training = True): + + + num_nodes = maybe_num_nodes(edge_index, num_nodes) + + if not training or p == 0.0: + node_mask = edge_index.new_ones(num_nodes, dtype=torch.bool) + edge_mask = edge_index.new_ones(edge_index.size(1), dtype=torch.bool) + return edge_index, edge_mask, node_mask + + + if batch_vector is None: + batch_vector = torch.zeros(num_nodes) + + count = torch.bincount(batch_vector) + probs = [] + for i in range(count.size(0)): + prob = torch.cat( (torch.rand( count[i]-min_node, device=edge_index.device), torch.ones(min_node)), 0) + r=torch.randperm(prob.size(0)) + prob=prob[r] + probs.append(prob) + + prob = torch.cat(probs, 0) + node_mask = prob > p + edge_index, _, edge_mask = subgraph(node_mask, edge_index, + num_nodes=num_nodes, + return_edge_mask=True) + return edge_index, edge_mask, node_mask + + + def forward(self, x, edge_index, batch_vector=None): + #encode + #x = self.conv1(x, edge_index).relu() + + #dropout + #edge_index, edge_mask, node_mask = self.dropout_node_min(edge_index, batch_vector, p=config['node_dropout'], training=self.training) + + x = self.conv(x, edge_index).relu() + x = self.conv2(x, edge_index).relu() + + #second part of dropout + #x = x[node_mask] + #if batch_vector is not None: + # batch_vector = batch_vector[node_mask] + + x = global_mean_pool(x, batch_vector) + x = self.lin1(x).relu() + x = self.lin2(x).relu() + x = self.lin22(x).relu() + x = self.lin23(x).relu() + x = self.lin3(x) + x = torch.squeeze(x) + x = x.flatten() #to come back to the bached format from pygeometric + return x + + + + +#class GCNEncoder(torch.nn.Module): +# def __init__(self, in_channels, out_channels): +# super().__init__() +# self.conv1 = GCNConv(in_channels, 2 * out_channels) +# self.conv2 = GCNConv(2 * out_channels, out_channels) +# +# def forward(self, x, edge_index): +# x = self.conv1(x, edge_index).relu() +# return self.conv2(x, edge_index) + + +class VariationalGCNEncoder(torch.nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.conv1 = GCNConv(in_channels, 2 * out_channels) + self.conv_mu = GCNConv(2 * out_channels, out_channels) + self.conv_logstd = GCNConv(2 * out_channels, out_channels) + + def forward(self, x, edge_index): + x = self.conv1(x, edge_index).relu() + return self.conv_mu(x, edge_index), self.conv_logstd(x, edge_index) + + +class LinearEncoder(torch.nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.conv = GCNConv(in_channels, out_channels) + + def forward(self, x, edge_index): + return self.conv(x, edge_index) + + +class VariationalLinearEncoder(torch.nn.Module): + def __init__(self, in_channels, out_channels): + super().__init__() + self.conv_mu = GCNConv(in_channels, out_channels) + self.conv_logstd = GCNConv(in_channels, out_channels) + + def forward(self, x, edge_index): + return self.conv_mu(x, edge_index), self.conv_logstd(x, edge_index) + +def train(train_data): + model.train() + optimizer.zero_grad() + z = model.encode(train_data.x, train_data.edge_index) + loss = model.recon_loss(z, train_data.pos_edge_label_index) + if args.variational: + loss = loss + (1 / train_data.num_nodes) * model.kl_loss() + loss.backward() + optimizer.step() + return float(loss) + + +@torch.no_grad() +def test(data): + model.eval() + z = model.encode(data.x, data.edge_index) + return model.test(z, data.pos_edge_label_index, data.neg_edge_label_index) + + +class LineTrainer(): + + def __init__(self, name): + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + transform = T.Compose([ + # T.NormalizeFeatures(), + T.ToDevice(device), + # T.RandomLinkSplit(num_val=0.05, num_test=0.1, is_undirected=True, + # split_labels=True, add_negative_train_samples=False), + ]) + + self.model_path = osp.join(osp.dirname(osp.realpath(__file__)), 'lineModels', name) + self.dataset = GraphDataset(name, level="line", transform=transform) + self.in_channels, self.hidden_channels, self.out_channels = self.dataset.num_features, self.dataset.num_features*2, config['latent_size'] + #out_channels hab ich mal verdoppelt + self.name = name + + self.encoder = GCNEncoder(self.in_channels, self.out_channels) + #self.encoder = GCN(in_channels=self.in_channels, hidden_channels=self.hidden_channels, num_layers=1, out_channels=self.out_channels) + + + self.decoder = GCNDecoder(self.in_channels, self.out_channels) + self.model = GAE(self.encoder, self.decoder) + #model = GAE(LinearEncoder(in_channels, out_channels)) + #model = VGAE(VariationalGCNEncoder(in_channels, out_channels)) + #model = VGAE(VariationalLinearEncoder(in_channels, out_channels)) + + self.epochs = 10000000 #100000 + + self.model = self.model.to(device) + if osp.exists(self.model_path): + print("MODEL EXISTS. LOADING...") + self.model.load_state_dict(torch.load(self.model_path, map_location=torch.device('cpu'))) + ##self.optimizer = torch.optim.Adam(self.model.parameters(), lr=0.001) + ##self.scheduler = ReduceLROnPlateau(self.optimizer, patience=60, cooldown=20) + self.optimizer = torch.optim.Adam(self.model.parameters(), lr=0.0005) + + #lr war mal 0.1. 0.001 war jetzt besser + + def loss_function(self, out, target, mu, logvar, epoch): + + criterionMSE = torch.nn.MSELoss() + lossMSE = criterionMSE(out, target) + + beta_norm = 0.01 #(1000 * config['latent_size']) / (config["nrPoints"]*2) + kl_annealing = 0.001 #0.001 + kl_weight = max(0, min( (epoch -200) * kl_annealing, 1 ) ) + + + mse_annealing = kl_annealing + mse_weight = 1 #1 - max(0.5, min( (epoch -1200) * mse_annealing, 1 ) ) + 0.5 + + + kld_loss = torch.mean(-0.5 * (1 + logvar - mu ** 2 - logvar.exp())) + loss = (mse_weight * lossMSE) + (kl_weight * beta_norm * kld_loss) + + return loss, (mse_weight * lossMSE), (kl_weight * beta_norm * kld_loss), kl_weight, mse_weight + + + def trainModel(self): + self.model.train() + criterionMSE = torch.nn.MSELoss() + criterionKLD = torch.nn.KLDivLoss(reduction='batchmean') + + loss_list = [] + + for epoch in range(self.epochs): + + # Initializing variable for storing + # loss + running_loss = 0 + + # Iterating over the training dataset + for train_data in self.dataset: + + # Loading image(s) and + # reshaping it into a 1-d vector + + + # Generating output + mu, logvar = self.model.encode(train_data.x, train_data.edge_index) + z = self.reparameterize(mu, logvar) + out = self.model.decode(z) + + # Calculating loss + loss, l1, l2, w, m = self.loss_function(out, train_data.x, mu, logvar, epoch) + + # Updating weights according + # to the calculated loss + self.optimizer.zero_grad() + loss.backward() + torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0) #gradient clipping to prevent loss becoming NaN + self.optimizer.step() + + + # Incrementing loss + running_loss += loss.item() + + + # Averaging out loss over entire batch + running_loss /= self.dataset.len() + + #if epoch > 1400 and running_loss<200: + #self.scheduler.step(running_loss) + + loss_list.append(running_loss) + if len(loss_list) > 30: + loss_list = loss_list[1:] + + print("Epoch:", epoch, "Loss:", running_loss, l1, l2, m, w) + + if math.isnan(running_loss): + die() + + diff = 0 + for d in range(1,len(loss_list)): + diff = diff + abs(loss_list[d] - loss_list[d-1]) + + print("total", diff) + if running_loss < 0.1: + break + + if epoch % 100 == 0: + torch.save(self.model.state_dict(), self.model_path) + print("saving...") + + + torch.save(self.model.state_dict(), self.model_path) + + + def reparameterize(self, mu, logvar): + """ + Reparameterization trick to sample from N(mu, var) from + N(0,1). + :param mu: (Tensor) Mean of the latent Gaussian [B x D] + :param logvar: (Tensor) Standard deviation of the latent Gaussian [B x D] + :return: (Tensor) [B x D] + """ + std = torch.exp(0.5 * logvar) + eps = torch.randn_like(std) + return eps * std + mu + + def extractOriginLineVectors(self): + self.model.eval() + vectors = [] + originpoints = [] + for i in range(self.dataset.len()): + mu, logvar = self.model.encode(self.dataset[i].x, self.dataset[i].edge_index) + z = self.reparameterize(mu, logvar) + vectors.append(z) + originpoints.append(self.dataset[i].x) + return vectors, originpoints + + def encodeLineVector(self, x, edge_index): + self.model.eval() + + mu, logvar = self.model.encode(x, edge_index) + z = self.reparameterize(mu, logvar) + + return z + + + #slightly changes existing lines to keep their scaling and rotation information. random latent vectors would + #not have this information currently + def generate(self, nr, faktor='random'): + self.model.eval() + + lines = [] + scales = [] + rotations = [] + + for i in range(nr): + if faktor is 'random': + lines.append(self.model.decode(self.randomLatentVector())) + scales.append(1) + rotations.append(random.random()) + else: + datanum = random.randint(0,self.dataset.len()-1) + + + + mu1, logvar1 = self.model.encode(self.dataset[datanum].x, self.dataset[datanum].edge_index) + z1 = self.reparameterize(mu1, logvar1) + + datanum2 = random.randint(0,self.dataset.len()-1) + mu2, logvar2 = self.model.encode(self.dataset[datanum2].x, self.dataset[datanum2].edge_index) + z2 = self.reparameterize(mu2, logvar2) + + #jitter = 0.1 + + #z = torch.multiply(torch.rand(z1.size()), jitter) + #ToDo beim randomnessfaktor könnte man gut aus einer gaussverteilung ziehen. Könnte man hier nicht generell aus der gaussverteilung ziehen? + z = torch.add( torch.multiply(self.randomLatentVector(), random.random()*faktor), z1) + + #z = z1 + + #z = torch.add( torch.divide(torch.subtract(z2,z1), random.randint(10,20)), z1) + lines.append(self.model.decode(z)) + scales.append(self.dataset[datanum].scale.item()) + rotations.append(self.dataset[datanum].rotation.item()) + + #for i in range(nr): + # z = self.randomLatentVector() + # lines.append(self.model.decode(z)) + + + + return lines, scales, rotations + + + def generateFlower(self, nr): + self.model.eval() + + lines = [] + + + + #num1 = random.randint(0,self.dataset.len()-1) + #num2 = random.randint(0,self.dataset.len()-1) + #z1 = self.encodeLineVector(self.dataset[num1].x, self.dataset[num1].edge_index) + #z2 = self.encodeLineVector(self.dataset[num2].x, self.dataset[num2].edge_index) + + z1 = self.randomLatentVector() + z2 = self.randomLatentVector() + + z_dir = torch.subtract(z2,z1) + z_step = torch.divide(z_dir, nr) + + for i in range(nr): + z_fin = torch.add(z1, torch.multiply(z_step, i)) + lines.append(self.model.decode(z_fin)) + + return lines + + def randomLatentVector(self): + + z = torch.randn(config['latent_size']) + + #z = z.to(current_device) + return z + + def decode_latent_vector(self, z): + self.model.eval() + + + return self.model.decode(z) + +class PatternTrainer(): + + def __init__(self, name): + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + transform = T.Compose([ + # T.NormalizeFeatures(), + T.ToDevice(device), + # T.RandomLinkSplit(num_val=0.05, num_test=0.1, is_undirected=True, + # split_labels=True, add_negative_train_samples=False), + ]) + + self.model_path = osp.join(osp.dirname(osp.realpath(__file__)), 'patternModels', name) + self.dataset = GraphDataset(name, level="pattern", transform=transform) + self.loader = DataLoader(self.dataset, batch_size=config['batch_size']) + + self.in_channels = self.out_channels = self.dataset.num_features + self.hidden_channels = self.dataset.num_features*2 + + + #out_channels hab ich mal verdoppelt + self.name = name + + self.model = PatternEncoder(in_channels=self.in_channels, hidden_channels=self.hidden_channels, num_layers=1, out_channels=self.out_channels) + + #model = GAE(LinearEncoder(in_channels, out_channels)) + #model = VGAE(VariationalGCNEncoder(in_channels, out_channels)) + #model = VGAE(VariationalLinearEncoder(in_channels, out_channels)) + + self.epochs = 100000 #100000 + + self.model = self.model.to(device) + if osp.exists(self.model_path): + print("MODEL EXISTS. LOADING...") + self.model.load_state_dict(torch.load(self.model_path, map_location=torch.device('cpu'))) + self.optimizer = torch.optim.Adam(self.model.parameters(), lr=0.001) + self.scheduler = ReduceLROnPlateau(self.optimizer) + #self.scheduler = ExponentialLR(self.optimizer, gamma=0.9) + #self.scheduler = LinearLR(self.optimizer, total_iters=200, verbose=True) + + + + #for train_data in self.dataset: + # print(train_data) + + + + + def trainModel(self): + self.model.train() + criterion = torch.nn.MSELoss(reduction='sum') + + loss_list = [] + avg_epoch_loss = 0 + + for epoch in range(self.epochs): + + running_loss = 0 + + #for train_data in self.dataset: + for train_data in self.loader: + + out = self.model.forward(train_data.x, train_data.edge_index, train_data.batch) + #print(train_data.x[2,:]) + #print(train_data.x.size()) + #print(out[1:14]) + #print(train_data.y.size()) + + + #loss = criterion(out, train_data.y) + loss = self.loss_function(out, train_data.y) + + self.optimizer.zero_grad() + loss.backward() + self.optimizer.step() + + running_loss += loss.item() + + + + + running_loss /= len(self.loader.sampler) + self.scheduler.step(running_loss) + + + print("Epoch:", epoch, "Loss:", running_loss) + avg_epoch_loss += running_loss + + + if epoch % 100 == 0: + torch.save(self.model.state_dict(), self.model_path) + print("saving...", "Epoch:", epoch, "Loss:", avg_epoch_loss/100, "current loss:", running_loss) + avg_epoch_loss = 0 + + torch.save(self.model.state_dict(), self.model_path) + + def loss_function(self, out, ground_truth): + res = torch.unflatten(out, 0, (-1, 4+config['latent_size'])) + test = torch.unflatten(ground_truth, 0, (-1, 4+config['latent_size'])) + + resPos = res[:, 0:4] + resVec = res[:, 4:] + testPos = test[:, 0:4] + testVec = test[:, 4:] + + posLoss = torch.nn.MSELoss(reduction='mean')(resPos, testPos) + vecLoss = torch.nn.MSELoss(reduction='mean')(resVec, testVec) + + return posLoss*50 + vecLoss + + def getDatasetSample(self): + self.model.eval() + datanum = random.randint(0,self.dataset.len()-1) + return self.dataset[datanum].x + + def generate(self): + #wir haben hier noch nie probiert mit einem richtigen random z was zu generieren, oder? + self.model.eval() + + datanum = random.randint(0,self.dataset.len()-1) + print(datanum) + z = self.model.forward(self.dataset[datanum].x, self.dataset[datanum].edge_index) + print(z) + print(self.dataset[datanum].y) + return z, self.dataset[datanum].y, self.dataset[datanum].x + + def predict(self, x, edge_index): + self.model.eval() + return self.model.forward(x, edge_index) + + + + + + + + + +#for epoch in range(1, epochs + 1): +# for train_data, val_data, test_data in dataset: +# loss = train(train_data) +# auc, ap = test(test_data) +# print(f'Epoch: {epoch:03d}, AUC: {auc:.4f}, AP: {ap:.4f}') diff --git a/config.py b/config.py new file mode 100644 index 0000000..fe30326 --- /dev/null +++ b/config.py @@ -0,0 +1,11 @@ +config = { + 'nrPoints': 60, + 'double_ended': False, + 'patternReferencePoint': 'start', + 'latent_size': 2, + 'stroke_normalizing_size': 100, + 'batch_size': 500, + 'max_dist' : 100, + 'node_dropout': 0 + } + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4983cdd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +flask +python-socketio +flask-socketio +torch +torch-geometric diff --git a/server.py b/server.py new file mode 100644 index 0000000..87e553b --- /dev/null +++ b/server.py @@ -0,0 +1,224 @@ +from flask import Flask, render_template +from flask_socketio import SocketIO, send, emit +from DrawData import GraphHandler +from Model import LineTrainer, PatternTrainer +import json +import os +import os.path as osp +from config import config +import random +import numpy as np + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'secret!' +socketio = SocketIO(app) + + +gh = GraphHandler() + + +#path = osp.join(osp.dirname(osp.realpath(__file__)), 'data', 'dataset-test') +#dataset = MyOwnDataset("testdata", path) + + +def tensor2Points(x): + points = [] + + for i in range(x.shape[0]): + points.append({ + 'x': x[i][0].item(), + 'y': x[i][1].item() + }) + return points + +def prediction2obj(pred, lineTrainer, ref_id=None): + line_obj = {} + tensor = lineTrainer.decode_latent_vector(pred["latVec"]) + line_obj['points'] = tensor2Points(tensor) + line_obj['scale'] = pred["scale"] + line_obj['rotation'] = pred["rot"] + line_obj['position'] = { + "x": pred['posX'], + "y": pred['posY'] + } + if ref_id: + line_obj['reference_id'] = ref_id + return line_obj + +@socketio.event +def connect(): + print("User connected") + emit('init', config) + +#@socketio.on('new line') +#def new_line(points): +# print("new line received", points) +# bd.createData(points) + + +@socketio.on('raw data') +def raw_data(data): + gh.init_raw(data) + print('raw data recieved') + +@socketio.on('new dataset') +def new_dataset(data): + #print(data) + + if len(data['list']) == 0: + print("ERROR: no drawing data transmitted!") + else: + gh.save_line_training_data(data['name']) + + lineTrainer = LineTrainer(data['name']) + lineTrainer.trainModel() + +@socketio.on('generate') +def generate(data): + print(data) + print("RAW", gh.raw_data) + + trainer = LineTrainer(data['name']) + + tensors, scales, rotations = trainer.generate(data['nr'], 0.5) + pointlist = [] + + for tensor in tensors: + pointlist.append(tensor2Points(tensor)) + + emit('result', {'list': pointlist, 'scales':scales, 'rotations':rotations}) + +@socketio.on('compare') +def compare(data): + trainer = LineTrainer(data['name']) + + pointlist = [] + originlist = [] + tensors, originpoints = trainer.extractOriginLineVectors() + for z in tensors: + print(z) + tensor = trainer.decode_latent_vector(z) + pointlist.append(tensor2Points(tensor)) + + for ori in originpoints: + originlist.append(tensor2Points(ori)) + + emit('result', {'list': pointlist, "origins":originlist}) + + + +@socketio.on('train pattern') +def new_pattern(data): + print("hallo", data) + lineTrainer = LineTrainer(data['name']) + gh.add_line_latentspace(lineTrainer) + gh.save_pattern_training_data(data['name'], data['name']) + + #todo training hier ist broken ||||| ist das so? + pt = PatternTrainer(data['name']) + pt.trainModel() + + + + + +@socketio.on('generate pattern') +def generate_pattern(data): + + pt = PatternTrainer(data['name']) + lineTrainer = LineTrainer(data['name']) + + z,y,x = pt.generate() + pred = GraphHandler.decompose_node_hidden_state(z) + ground_truth = GraphHandler.decompose_node_hidden_state(y) + base_list = [] + for i in range(x.size()[0]): + n = GraphHandler.decompose_node_hidden_state(x[i]) + base_list.append( prediction2obj(n, lineTrainer) ) + + info = {} + info["prediction"] = prediction2obj(pred, lineTrainer) + info["ground_truth"] = prediction2obj(ground_truth, lineTrainer) + info["base_list"] = base_list + + emit('prediction', info) + +@socketio.on('extend pattern') +def extend_pattern(data): + + #prediction durch alle linien laufen lassen, clustern und durchschnitt bilden oder in die richtung "wandern" lassen + #für die ähnlihckeit distanz im vectorspace nehmen? + #später dropout? + + + print("LINES:", len(data["list"])) + + pt = PatternTrainer(data['name']) + lineTrainer = LineTrainer(data['name']) + info = {} + + if(len(data["list"]) <=0 ): + gh.clear() + + #starten mit einem einzelnen set aus dem datensatz + x = pt.getDatasetSample() + + base_list = [] + for i in range(x.size()[0]): + n = GraphHandler.decompose_node_hidden_state(x[i]) + base_list.append( prediction2obj(n, lineTrainer) ) + + + #info["prediction"] = prediction2obj(pred, lineTrainer) + #info["ground_truth"] = prediction2obj(ground_truth, lineTrainer) + info["base_list"] = base_list + + emit('extention', info) + else: + #prediction durch alle linien laufen lassen, clustern und durchschnitt bilden oder in die richtung "wandern" lassen + + #bisherige linien in den graphhandler laden + gh.add_raw(data) + + #latentspace auf den gleichen datensatz setzen + gh.add_line_latentspace(lineTrainer) + + #alle linien abgehen, als referenz nehmen und prediction einsammeln + samples = gh.sample_complete_graph(data['name']) + predictions = [] + for sample in samples: + x = sample["x"] + edge_index = sample["edge_index"] + ref_id = sample["ref_id"] + z = pt.predict(x, edge_index) + pred = GraphHandler.decompose_node_hidden_state(z) + predictions.append( prediction2obj(pred, lineTrainer, ref_id) ) + + + info["prediction"] = predictions + emit('extention', info) + + +@socketio.on('train') +def train(data): + trainer.trainModel(data['name']) + +###### ROUTES + +#@app.route("/") +#def hello_world(): +# return render_template('index.html') + +@app.route("/train") +def website_train(): + return render_template('train.html') + +#@app.route("/photo") +#def website_photo(): +# return render_template('photo.html') + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 5000)) + socketio.run(app, host='0.0.0.0', port=port, allow_unsafe_werkzeug=True) + + diff --git a/static/drawing.js b/static/drawing.js new file mode 100644 index 0000000..a7b78df --- /dev/null +++ b/static/drawing.js @@ -0,0 +1,422 @@ + +var config + +let linelist = [] +let originalLines = [] + +var socket = io() +socket.on("connect", function () { + socket.emit("my event", {data: "I'm connected!"}) +}) + +socket.on("init", function (data) { + console.log(data) + config = data +}) + +socket.on("flower-result", function (data) { + project.activeLayer.removeChildren(); + console.log(data) + let x = 50 + let y = 50 + let animate = false + for (let [idx,points] of Object.entries(data)) { + //let line = drawLine(points, Color.random(), true) + let line = drawLine(points, "black", true) + line.strokeWidth = 5 + line.opacity = 0.3 + line.position = view.bounds.center + if(animate){ + line.dashArray = [line.length, line.length] + line.dashOffset = line.length*10 + idx*10 + line.tweenTo({dashOffset: 0}, 40000) + } + line.rotate(idx*10) + line.scale(2) + } +}) + +socket.on("result", function (data) { + console.log(data) + let x = 50 + let y = 50 + for (let [idx, points] of data.list.entries()) { + //let line = drawLine(points, Color.random(), true) + let line = drawLine(points, "black", true) + line.strokeWidth = 5 + line.opacity = 0.8 + line.position = Point.random().multiply(view.bounds.size) + + if(data.origins){ + let o = drawLine(data.origins[idx], "red", true) + o.position = line.position + } + + + if(data.scales){ + line.scale(data.scales[idx]) + } + if(data.rotations){ + line.rotate(data.rotations[idx] *360 ) + } + + line.dashArray = [line.length, line.length] + line.dashOffset = line.length + line.tweenTo({dashOffset: 0}, 400) + + } +}) + +socket.on("prediction", function (data) { + project.activeLayer.removeChildren() + clearArea() + drawPredictionEnsemble(data) + console.log("known lines: ", linelist.length) +}) + +socket.on("extention", function (data) { + + drawPredictionEnsemble(data) + +}) + +paper.install(window) +window.onload = function () { + document.getElementById("load").addEventListener("change", load) + + let trainBtn = document.getElementById("train") + trainBtn.addEventListener("click", () => { + socket.emit("new dataset", { + name: document.getElementById("trainname").value, + list: linelist, + }) + }) + let trainPatternBtn = document.getElementById("train-pattern") + trainPatternBtn.addEventListener("click", () => { + socket.emit("train pattern", { + name: document.getElementById("trainname").value, + }) + }) + + let genPatternBtn = document.getElementById("generate-pattern") + genPatternBtn.addEventListener("click", () => { + socket.emit("generate pattern", { + name: document.getElementById("trainname").value, + }) + console.log("emitting") + }) + + let extPatternBtn = document.getElementById("extend-pattern") + extPatternBtn.addEventListener("click", () => { + + socket.emit("extend pattern", { + name: document.getElementById("trainname").value, + list: linelist, + }) + console.log("emitting") + }) + + let generateBtn = document.getElementById("compare") + generateBtn.addEventListener("click", () => + socket.emit("compare", { + name: document.getElementById("trainname").value + }) + ) + + let compareBtn = document.getElementById("generate") + compareBtn.addEventListener("click", () => + socket.emit("generate", { + name: document.getElementById("trainname").value, + nr: 10, + }) + ) + + let flowerBtn = document.getElementById("flower") + flowerBtn.addEventListener("click", () => + socket.emit("flower", { + name: document.getElementById("trainname").value, + nr: 20 + }) + ) + //socket.emit('train'); + + paper.setup("paperCanvas") + let tool = new Tool() + tool.minDistance = 6 + + let rect = new Path.Rectangle([10, 10], [100, 100]) + rect.fillColor = "black" + + var path + + tool.onMouseDown = function (event) { + path = new Path() + path.strokeColor = "black" + path.add(event.point) + } + + tool.onMouseDrag = function (event) { + path.add(event.point) + } + + tool.onMouseUp = function (event) { + path.simplify() + + //socket.emit('new line', points); + + processLine(path) + + /*for(let i = 0; i<3; i++){ + let copy = path.clone() + copy.scale(Math.random()*0.5 -0.25 + 1) + copy.rotate(Math.random()*60 -30) + points = path2points(path) + let group = drawLine(points, 'blue') + pointlist.push(points) + }*/ + } +} + +function save() { + console.log("save") + let saveArr = [] + for (let o of originalLines) { + saveArr.push(o.exportJSON()) + } + download(JSON.stringify(saveArr), "json.txt", "text/plain") +} + +function undo(){ + linelist.pop() + let l = originalLines.pop() + l.remove() +} + +function load(event) { + //clearArea() + + console.log("LOAD FILE") + var reader = new FileReader() + reader.onload = function (event) { + let loadLines = JSON.parse(event.target.result) + console.log(loadLines) + for (let line of loadLines) { + let p = new Path() + p.importJSON(line) + console.log(p) + processLine(p) + } + + let outer = findOuterLines() + outer.forEach((i) => (originalLines[i].strokeColor = "green")) + + socket.emit("raw data", { + list: linelist, + outer_ids: outer, + }) + } + reader.readAsText(event.target.files[0]) +} + +function clearArea() { + originalLines = [] + pointlist = [] +} + +function download(content, fileName, contentType) { + var a = document.createElement("a") + var file = new Blob([content], {type: contentType}) + a.href = URL.createObjectURL(file) + a.download = fileName + a.click() +} + +function normalizePointpath(points) { + let p1 = new Point(points[1]) + let p2 = new Point(points[0]) + let dir = p1.subtract(p2) +} + +function processLine(path) { + let [segmentedPath, scale, angle] = createSegments(path) + path.scale(scale, path.firstSegment.point) + path.rotate(angle*360, path.firstSegment.point) + console.log(scale, angle) + + let points = segments2points(segmentedPath) + //let group = drawLine(points, "red") + //pointlist.push(points) + linelist.push({ + points: points, + scale: scale, + rotation: angle, + }) + originalLines.push(path) + + + +} + +function createSegments(path) { + //scale up to normalized size + largeDir = Math.max(path.bounds.width, path.bounds.height) + let baseSize = config["stroke_normalizing_size"] + path.scale(baseSize/largeDir, path.firstSegment.point) + let scale = largeDir/baseSize + + let currAngle = path.lastSegment.point.subtract( + path.firstSegment.point + ).angle + 180 + + let angle = currAngle/360 + path.rotate(-currAngle, path.firstSegment.point) + + + let segmentedPath = new Path() + + let dist = path.length / (config.nrPoints - 1) + for (let i = 0; i < config.nrPoints - 1; i++) { + let p = path.getPointAt(dist * i).round() + segmentedPath.addSegment(p) + } + segmentedPath.addSegment(path.lastSegment.point.round()) + + return [segmentedPath, scale, angle] +} + +function segments2points(path) { + return path.segments.map((seg) => { + return {x: seg.point.x, y: seg.point.y} + }) +} + + + +function drawLine(baseLine, color, smoothing) { + let points + if (Array.isArray(baseLine)) { + points = baseLine + } else { + points = baseLine.points + } + + let path = new Path({segments: points}) + path.strokeColor = color + path.pivot = path.firstSegment.point + + console.log("base", baseLine.position) + + if (!Array.isArray(baseLine)) { + path.position = new Point(baseLine.position.x * config["max_dist"], baseLine.position.y * config["max_dist"] ) + console.log("pos", path.position) + path.scale(baseLine.scale) + path.rotate(baseLine.rotation * 360) + } else { + console.error("no normapization info", baseLine) + } + + if(baseLine.reference_id){ + console.log("moving", originalLines[baseLine.reference_id].firstSegment.point) + path.translate(originalLines[baseLine.reference_id].firstSegment.point) + }else{ + path.translate(view.center) + } + + processLine(path) + + if (smoothing) { + path.simplify() + + return path + } else { + let group = new Group() + + for (let p of points) { + let c = new Path.Circle([p.x, p.y], 2) + c.fillColor = color + group.addChild(c) + } + + group.addChild(path) + return group + } +} + +function findOuterLines() { + let rect = new Path.Rectangle(view.bounds) + let positions = [] + + let ids = [] + for (let i = 0; i < rect.length; i += 10) { + let pt = rect.getPointAt(i) + let dist = view.bounds.height * 3 + let found_id = -1 + for (let [idx, line] of originalLines.entries()) { + if (pt.getDistance(line.position) < dist) { + dist = pt.getDistance(line.position) + found_id = idx + } + } + ids.push(found_id) + } + let unique = [...new Set(ids)] + return unique +} + +function drawPredictionEnsemble(data) { + console.log(data) + + + //pointZero = baseList[0]["points"][0] + + if(data["base_list"]){ + let baseList = data["base_list"] + for (baseLine of baseList) { + console.log(baseLine) + let line = drawLine(baseLine, "black", true) + } + } + + if(data.ground_truth){ + let gt = drawLine(data.ground_truth, "blue", true) + } + + if(data.prediction){ + if(Array.isArray(data.prediction)){ + for(let p of data.prediction){ + let l=drawLine(p, "red", true) + console.log(l) + } + }else{ + drawLine(data.prediction, "red", true) + } + } + + /* + let gt = drawLine(data.ground_truth.points, 'blue', true) + gt.pivot = gt.firstSegment.point + gt.position = Point.random().multiply(view.bounds.size) + gt.scale(1/data.ground_truth.scale) + gt.rotation = data.ground_truth.rotation + gt.strokeWidth = 3 + + + let line = drawLine(data.prediction.points, 'black', true) + line.pivot = line.firstSegment.point + line.position = gt.position + line.scale(1/data.prediction.scale) + line.rotation = data.prediction.rotation + */ +} + +function downloadSVG(){ + var svg = project.exportSVG({ asString: true, bounds: 'content' }); + var svgBlob = new Blob([svg], {type:"image/svg+xml;charset=utf-8"}); + var svgUrl = URL.createObjectURL(svgBlob); + var downloadLink = document.createElement("a"); + downloadLink.href = svgUrl; + downloadLink.download = "export.svg"; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} diff --git a/static/edgeDetection.js b/static/edgeDetection.js new file mode 100644 index 0000000..32e6445 --- /dev/null +++ b/static/edgeDetection.js @@ -0,0 +1,538 @@ +let edgeLines = [] +let patternLines = [] +let samplePoints = [] + +let mlStrokes = [] +let mlScales = [] +let mlRotations = [] +let mlName = "" +var socket = io(); +var raster +let segmenter + +let segmentBG = false +let edgeDetails = 5 +let edgemin = 20 +let edgemax = 50 +//import removeBackground from './node_modules/@imgly/background-removal/dist/browser.mjs'; + + +//import imglyRemoveBackground from "@imgly/background-removal" +//console.log(window.removeBackground ) + + + paper.install(window); + window.onload = async function () { + + paper.setup("paperCanvas"); + paper.view.onFrame = tick //tick + + // lets do some fun + var video = document.getElementById('webcam'); + let vidw = video.width + let vidh = video.height + //var video = document.getE kralementById('plotimg'); + var canvas = document.getElementById('canvas'); + canvas.width = vidw + canvas.height = vidh + var ctx = canvas.getContext('2d'); + + + raster = new Raster([vidw,vidh]); + + + let models = [ + + //"jojo", + //"sun", + //"boxes", + //"gigswirl", + //"trianglestripe", + //"boxgroup" + "test5", + //"pigtail", + + ] + mlName = _.sample(models) + socket.emit('generate', {nr: 200, name:mlName}); + + + // Move the raster to the center of the view + //raster.position = view.center; + //vectorizeRaster(raster) + //generateImage() + + socket.on('result', function(data) { + console.log(data) + mlStrokes = data.list + mlScales = data.scales + mlRotations = data.rotations + console.log(mlStrokes.length) + + }); + + + + view.onMouseDown = function(event) { + + /* + var canvasBG = document.getElementById('background-canvas'); + canvasBG.width = vidw + canvasBG.height = vidh + var ctxBG = canvasBG.getContext('2d'); + ctxBG.drawImage(video, 0, 0, vidw, vidh); + + const segmentation = segmenter.segmentPeople(canvasBG, {multiSegmentation: true, segmentBodyParts: false}).then(()=>{ + console.log("segmentation") + + const foregroundColor = {r: 0, g: 0, b: 0, a: 0}; + const backgroundColor = {r: 0, g: 0, b: 0, a: 255}; + const backgroundDarkeningMask = bodySegmentation.toBinaryMask( + segmentation, foregroundColor, backgroundColor).then(() => { + const opacity = 0.7; + const maskBlurAmount = 3; + const flipHorizontal = false; + const canvasBG = document.getElementById('background-canvas'); + // Draw the mask onto the image on a canvas. With opacity set to 0.7 and + // maskBlurAmount set to 3, this will darken the background and blur the + // darkened background's edge. + bodySegmentation.drawMask( + canvasBG, video, backgroundDarkeningMask, opacity, maskBlurAmount, flipHorizontal); + + }) + + + + }) + */ + + console.log("start") + + + let ctx = canvas.getContext('2d'); + + raster.setImageData(ctx.getImageData(0, 0, vidw, vidh), [0,0]) + raster.position = view.center; + + let canvasTMP = document.createElement('canvas'); + var context = canvasTMP.getContext('2d'); + context.canvas.width = vidw; + context.canvas.height = vidh; + context.drawImage(video, 0, 0, vidw, vidh); + + const colorThief = new ColorThief(); + let res = colorThief.getPalette(canvasTMP, 10); + console.log(res) + + + //remove background + const bodypix = ml5.bodyPix(modelReady); + + function modelReady() { + // segment the image given + console.log("video", video) + bodypix.segment(video, gotResults); + + } + + function gotResults(error, result) { + if (error) { + console.log(error); + return; + } + // log the result + console.log("MASK", result.backgroundMask); + + vectorizeRaster(raster) + //raster.setImageData(context.getImageData(0, 0, vidw, vidh), [0,0]) + + res = res.map( (elem, idx) => (new Color(res[idx][0]/255, res[idx][1]/255, res[idx][2]/255) ) ) + res = res.sort(function(a,b){ + if( a.brightness < b.brightness){ + return -1 + }else{ + return 1 + } + }) + + compressColors(raster, res, result.backgroundMask) + + + samplePoints = _.shuffle(samplePoints) + + while(samplePoints.length > 0 && mlStrokes.length > 0){ + drawML() + } + + + } + paper.view.onFrame = undefined + + } + + + if (navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia({ video: true }) + .then(function (stream) { + video.srcObject = stream; + video.play(); + + }) + .catch(function (err0r) { + console.log("Something went wrong!"); + }); + } + + + + var options = function(){ + this.blur_radius = 2; + this.low_threshold = 20; + this.high_threshold = 50; + } + + function colorDistance(c1, c2){ + let c = c2.subtract(c1) + return Math.abs( Math.sqrt( Math.pow(c.red, 2) + Math.pow(c.blue, 2) + Math.pow(c.green, 2) ) ) + } + + function compressColors(raster, palette, backgroundMask){ + + let mask = new ImageData(backgroundMask,vidw,vidh); + raster.setImageData(mask, [0,0]) + console.log(raster.getPixel(0,0)) + + for(let i = 0; i 10){ + used = drawStroke(points, 40, used) + remaining = points.length-used.length + console.log("remaining: ", remaining) + } + + } + + + function vectorizeRaster(raster){ + let viewRadius = 3 + let k = 10 + let maxDist = 3.7 + let whiteColor = new Color('white') + let points = [] + for(let i = 0; i 100){ + used = drawStroke(points, maxDist, used) + remaining = points.length-used.length + console.log("remaining: ", remaining) + } + } + + function drawStroke(points, maxDist, used){ + + + let ids = points.map(p => p.ID) + let sampleID = _.sample(_.without(ids, ...used)) //_.sample(points) + let testPoint = points[sampleID] + + + //used = [testPoint.ID] + used.push(testPoint.ID) + let path = new Path({ + strokeColor : Color.random(), + strokeWidth : 1 + }) + path.add(testPoint) + + + for(let i = 0; i<500; i++){ + let sorted = _.sortBy(points, [function(p) { return path.lastSegment.point.getDistance(p) }]); + sorted = sorted.map(p => p.ID) + sorted = _.without(sorted, ...used) + if(path.lastSegment.point.getDistance(points[sorted[0]]) > maxDist ){ + console.log("break at ", i) + break + }else{ + path.add(points[sorted[0]]) + used.push(sorted[0]) + } + } + path.reverse() + for(let i = 0; i<500; i++){ + let sorted = _.sortBy(points, [function(p) { return path.lastSegment.point.getDistance(p) }]); + sorted = sorted.map(p => p.ID) + sorted = _.without(sorted, ...used) + if(path.lastSegment.point.getDistance(points[sorted[0]]) > maxDist ){ + console.log("break at ", i) + break + }else{ + path.add(points[sorted[0]]) + used.push(sorted[0]) + } + } + + path.simplify(10) + //path.smooth() + edgeLines.push(path) + + return used + } + + + function findStartPoints(points){ + let startIDs = [] + for(let testPoint of points){ + let sorted = _.sortBy(points, [function(p) { return testPoint.getDistance(p) }]); + + let dir = sorted[1].subtract(testPoint) + let dir2 = sorted[2].subtract(testPoint) + + let angle = Math.abs(dir.getAngle(dir2)-180) + if(angle < 15){ + testPoint.circle.fillColor = 'yellow' + } + } + + } + + + + + + function tick() { + + + + + + if(ctx){ + ctx = canvas.getContext('2d'); + ctx.drawImage(video, 0, 0, vidw, vidh); + var imageData = ctx.getImageData(0, 0, vidw, vidh); + + + var img_u8 = new jsfeat.matrix_t(vidw, vidh, jsfeat.U8_t | jsfeat.C1_t); + jsfeat.imgproc.grayscale(imageData.data, vidw, vidh, img_u8); + + //jsfeat.imgproc.equalize_histogram(img_u8, img_u8) + + + var r = edgeDetails; //5 bei zu viel gedöns? + var kernel_size = (r+1) << 1; + + + jsfeat.imgproc.gaussian_blur(img_u8, img_u8, kernel_size, 0); + + + + jsfeat.imgproc.canny(img_u8, img_u8, edgemin, edgemax); + + + + // render result back to canvas + var data_u32 = new Uint32Array(imageData.data.buffer); + var alpha = (0xff << 24); + var i = img_u8.cols*img_u8.rows, pix = 0; + while(--i >= 0) { + pix = img_u8.data[i]; + data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix; + } + + + ctx.putImageData(imageData, 0, 0); + console.log("tick") + + + } + + + } + + + + function drawML(){ + let points = mlStrokes.pop() + let scale = mlScales.pop() + let rot = mlRotations.pop() + let c = new Path( {segments: points} ) + c.smooth() + c.strokeColor = 'black' + c.scale(scale) + //c.scale(0.4) + c.scale(0.35) + //if(mlName != "trianglestripe"){ + // c.scale(0.85) + //}else{ + // c.scale(1.1) + //} + c.rotate(rot) + c.position = samplePoints.pop() + while( (doesIntersect(c, patternLines) || doesIntersect(c, edgeLines) ) && samplePoints.length > 0 && mlStrokes.length > 0 ){ + c.position = samplePoints.pop() + } + patternLines.push(c) + console.log("remaining sample points:", samplePoints.length, mlStrokes.length) + } + + function doesIntersect(item, arr){ + for(let i of arr){ + if(item.intersects(i)){ + return true + } + } + return false + } + + + } + +function downloadSVG(){ + raster.remove() + var svg = project.exportSVG({ asString: true, bounds: 'content' }); + var svgBlob = new Blob([svg], {type:"image/svg+xml;charset=utf-8"}); + var svgUrl = URL.createObjectURL(svgBlob); + var downloadLink = document.createElement("a"); + downloadLink.href = svgUrl; + downloadLink.download = "stamp.svg"; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} diff --git a/static/jsfeat-min.js b/static/jsfeat-min.js new file mode 100755 index 0000000..f1dc9da --- /dev/null +++ b/static/jsfeat-min.js @@ -0,0 +1 @@ +var jsfeat=jsfeat||{REVISION:"ALPHA"};(function(r){var o=1.192092896e-7;var l=1e-37;var m=256,i=512,h=1024,x=2048,w=4096;var A=1,n=2,b=3,p=4;var z=new Int32Array([-1,1,4,-1,4,-1,-1,-1,8,-1,-1,-1,-1,-1,-1,-1,8]);var y=(function(){return function(B){return(B&65280)}})();var k=(function(){return function(B){return(B&255)}})();var c=(function(){return function(B){return z[(B&65280)>>8]}})();var a=0;var f=1;var e=2;var u=3;var d=1;var s=1;var g=2;var v=(function(){function B(D,C){this.size=((D+7)|0)&-8;if(typeof C==="undefined"){this.buffer=new ArrayBuffer(this.size)}else{this.buffer=C;this.size=C.length}this.u8=new Uint8Array(this.buffer);this.i32=new Int32Array(this.buffer);this.f32=new Float32Array(this.buffer);this.f64=new Float64Array(this.buffer)}return B})();var q=(function(){function B(F,D,E,C){this.type=y(E)|0;this.channel=k(E)|0;this.cols=F|0;this.rows=D|0;if(typeof C==="undefined"){this.allocate()}else{this.buffer=C;this.data=this.type&m?this.buffer.u8:(this.type&i?this.buffer.i32:(this.type&h?this.buffer.f32:this.buffer.f64))}}B.prototype.allocate=function(){delete this.data;delete this.buffer;this.buffer=new v((this.cols*c(this.type)*this.channel)*this.rows);this.data=this.type&m?this.buffer.u8:(this.type&i?this.buffer.i32:(this.type&h?this.buffer.f32:this.buffer.f64))};B.prototype.copy_to=function(D){var C=D.data,G=this.data;var E=0,F=(this.cols*this.rows*this.channel)|0;for(;Ethis.buffer.size){this.cols=F;this.rows=D;this.channel=C;this.allocate()}else{this.cols=F;this.rows=D;this.channel=C}};return B})();var t=(function(){function B(C){this.levels=C|0;this.data=new Array(C);this.pyrdown=jsfeat.imgproc.pyrdown}B.prototype.allocate=function(C,E,F){var D=this.levels;while(--D>=0){this.data[D]=new q(C>>D,E>>D,F)}};B.prototype.build=function(F,E){if(typeof E==="undefined"){E=true}var H=2,D=F,C=this.data[0];if(!E){var G=F.cols*F.rows;while(--G>=0){C.data[G]=F.data[G]}}C=this.data[1];this.pyrdown(D,C);for(;Hh.size){h.resize(g)}return h},put_buffer:function(g){c=c.next=g;d++}}})();b.cache=a;a.allocate(30,640*4)})(jsfeat);(function(b){var a=(function(){var c=new Int32Array(48*2);return{get_gaussian_kernel:function(p,m,e,l){var f=0,j=0,o=0,n=0,d=0;var g=0;var h=jsfeat.cache.get_buffer(p<<2);var k=h.f32;if((p&1)==1&&p<=7&&m<=0){switch(p>>1){case 0:k[0]=1;g=1;break;case 1:k[0]=0.25,k[1]=0.5,k[2]=0.25;g=0.25+0.5+0.25;break;case 2:k[0]=0.0625,k[1]=0.25,k[2]=0.375,k[3]=0.25,k[4]=0.0625;g=0.0625+0.25+0.375+0.25+0.0625;break;case 3:k[0]=0.03125,k[1]=0.109375,k[2]=0.21875,k[3]=0.28125,k[4]=0.21875,k[5]=0.109375,k[6]=0.03125;g=0.03125+0.109375+0.21875+0.28125+0.21875+0.109375+0.03125;break}}else{n=m>0?m:((p-1)*0.5-1)*0.3+0.8;d=-0.5/(n*n);for(;f=0){j=k[C<<1];G=k[(C<<1)+1];C--;for(;;){z=(G-j)+1;if(z<=D){for(e=j+1;e<=G;e++){for(y=e;y>j&&u(o[y],o[y-1]);y--){v=o[y];o[y]=o[y-1];o[y-1]=v}}break}else{f=0;x=j;h=G;l=j+(z>>1);if(z>40){E=z>>3;I=j,H=j+E,F=j+(E<<1);r=o[I],q=o[H],p=o[F];j=u(r,q)?(u(q,p)?H:(u(r,p)?F:I)):(u(p,q)?H:(u(r,p)?I:F));I=l-E,H=l,F=l+E;r=o[I],q=o[H],p=o[F];l=u(r,q)?(u(q,p)?H:(u(r,p)?F:I)):(u(p,q)?H:(u(r,p)?I:F));I=G-(E<<1),H=G-E,F=G;r=o[I],q=o[H],p=o[F];G=u(r,q)?(u(q,p)?H:(u(r,p)?F:I)):(u(p,q)?H:(u(r,p)?I:F))}I=j,H=l,F=G;r=o[I],q=o[H],p=o[F];l=u(r,q)?(u(q,p)?H:(u(r,p)?F:I)):(u(p,q)?H:(u(r,p)?I:F));if(l!=x){v=o[l];o[l]=o[x];o[x]=v;l=x}j=w=x+1;G=g=h;r=o[l];for(;;){while(j<=G&&!u(r,o[j])){if(!u(o[j],r)){if(j>w){v=o[w];o[w]=o[j];o[j]=v}f=1;w++}j++}while(j<=G&&!u(o[G],r)){if(!u(r,o[G])){if(GG){break}v=o[j];o[j]=o[G];o[G]=v;f=1;j++;G--}if(f==0){j=x,G=h;for(e=j+1;e<=G;e++){for(y=e;y>j&&u(o[y],o[y-1]);y--){v=o[y];o[y]=o[y-1];o[y-1]=v}}break}z=Math.min((w-x),(j-w));A=(j-z)|0;for(B=0;B1){if(A>1){if(z>A){++C;k[C<<1]=x;k[(C<<1)+1]=x+z-1;j=h-A+1,G=h}else{++C;k[C<<1]=h-A+1;k[(C<<1)+1]=h;j=x,G=x+z-1}}else{j=x,G=x+z-1}}else{if(A>1){j=h-A+1,G=h}else{break}}}}}},median:function(k,d,i){var e;var f=0,j=0,g=0,h=(d+i)>>1;for(;;){if(i<=d){return k[h]}if(i==(d+1)){if(k[d]>k[i]){e=k[d];k[d]=k[i];k[i]=e}return k[h]}f=((d+i)>>1);if(k[f]>k[i]){e=k[f];k[f]=k[i];k[i]=e}if(k[d]>k[i]){e=k[d];k[d]=k[i];k[i]=e}if(k[f]>k[d]){e=k[f];k[f]=k[d];k[d]=e}j=(d+1);e=k[f];k[f]=k[j];k[j]=e;g=i;for(;;){do{++j}while(k[d]>k[j]);do{--g}while(k[g]>k[d]);if(g=h){i=(g-1)}}}return 0}}})();b.math=a})(jsfeat);(function(b){var a=(function(){return{identity:function(j,g){if(typeof g==="undefined"){g=1}var i=j.data;var f=j.rows,h=j.cols,e=(h+1)|0;var c=f*h;var d=c;while(--c>=0){i[c]=0}c=d;d=0;while(dg){g/=h;return h*Math.sqrt(1+g*g)}if(g>0){h/=g;return g*Math.sqrt(1+h*h)}return 0};var c=function(H,o,q,r,h,I){var C=jsfeat.EPSILON;var N=0,M=0,L=0,J=0,K=0,D=0,R=0,G=0;var u=0,v=I*I*30;var E=0,U=0,F=0,x=0,z=0,B=0,Q=0,T=0,w=0;var P=jsfeat.cache.get_buffer(I<<2);var S=jsfeat.cache.get_buffer(I<<2);var O=P.i32;var g=S.i32;if(r){for(;N0){for(J=0,E=Math.abs(H[L]),N=1;N1){for(;u0){for(J=0,E=Math.abs(H[D]),N=1;N>16)&32767)&256)!=0?y:-y;D[X*l+U]=B}for(A=0;A<2;A++){for(V=0;VMath.abs(w[n*v+q])){n=o}}if(Math.abs(w[n*v+q])=0;q--){y=r[q];for(n=q+1;n=0;p--){k=q[p];o=(p+1);s=(o*u);for(;o=0){p.data[z]=u.data[z]}}else{if(p){jsfeat.matmath.transpose(p,u)}}if(l&&(o&jsfeat.SVD_V_T)){z=v*v;while(--z>=0){l.data[z]=y.data[z]}}else{if(l){jsfeat.matmath.transpose(l,y)}}}else{if(p&&(o&jsfeat.SVD_U_T)){z=v*v;while(--z>=0){p.data[z]=y.data[z]}}else{if(p){jsfeat.matmath.transpose(p,y)}}if(l&&(o&jsfeat.SVD_V_T)){z=w*w;while(--z>=0){l.data[z]=u.data[z]}}else{if(l){jsfeat.matmath.transpose(l,u)}}}jsfeat.cache.put_buffer(q);jsfeat.cache.put_buffer(h);jsfeat.cache.put_buffer(C)},svd_solve:function(v,l,s){var E=0,C=0,z=0;var w=0,u=0;var o=v.rows,p=v.cols;var h=0,I=0,x=0;var r=v.type|jsfeat.C1_t;var F=jsfeat.cache.get_buffer((o*o)<<3);var m=jsfeat.cache.get_buffer(p<<3);var H=jsfeat.cache.get_buffer((p*p)<<3);var t=new jsfeat.matrix_t(o,o,r,F.data);var G=new jsfeat.matrix_t(1,p,r,m.data);var D=new jsfeat.matrix_t(p,p,r,H.data);var n=s.data,y=t.data,q=G.data,g=D.data;this.svd_decompose(v,G,t,D,0);x=jsfeat.EPSILON*q[0]*p;for(;Ex){for(z=0,h=0,w=0;zw){l+=g[s+y]*x[v]/p[y]}}r[h]=l}}jsfeat.cache.put_buffer(D);jsfeat.cache.put_buffer(m);jsfeat.cache.put_buffer(G)},eigenVV:function(j,p,r){var k=j.cols,m=k*k;var g=j.type|jsfeat.C1_t;var o=jsfeat.cache.get_buffer((k*k)<<3);var h=jsfeat.cache.get_buffer(k<<3);var l=new jsfeat.matrix_t(k,k,g,o.data);var q=new jsfeat.matrix_t(1,k,g,h.data);while(--m>=0){l.data[m]=j.data[m]}c(l.data,k,q.data,p?p.data:null,k,k);if(r){while(--k>=0){r.data[k]=q.data[k]}}jsfeat.cache.put_buffer(o);jsfeat.cache.put_buffer(h)}}})();b.linalg=a})(jsfeat);(function(a){var c=(function(){var m=function(p){return p*p};var e=function(z,A,x,w,u){var t=0;var y=0,s=0,q=0,C=0;var v=0,r=0,p=0,B=0;var E=0,D=0;for(;t=0){M[L]=0}for(L=0;L=0||-h>=i*(-f)?i:Math.round(h/f))|0};return e})();var d=(function(){var e=function(l,q,r,p,t,m,g){var v=1000;var s=[];var n=0,k=0,u=0,h=0,o=false;for(;u=0){y.data[l]=1}}jsfeat.cache.put_buffer(B);jsfeat.cache.put_buffer(h);jsfeat.cache.put_buffer(t);return true}for(;zMath.max(F,v-1)){o.copy_to(j);F=p;if(y){s.copy_to(y)}A=E.update_iters((l-p)/l,A);q=true}}jsfeat.cache.put_buffer(B);jsfeat.cache.put_buffer(h);jsfeat.cache.put_buffer(t);return q},lmeds:function(H,n,z,i,l,j,B,g){if(typeof g==="undefined"){g=1000}if(l=0){B.data[l]=1}}jsfeat.cache.put_buffer(E);jsfeat.cache.put_buffer(h);jsfeat.cache.put_buffer(u);return true}for(;C=w}jsfeat.cache.put_buffer(E);jsfeat.cache.put_buffer(h);jsfeat.cache.put_buffer(u);return r}}})();a.ransac_params_t=b;a.motion_model=c;a.motion_estimator=d})(jsfeat);(function(b){var a=(function(){var c=function(q,S,O,p){var r=0;var y=q.channel,v=q.cols,J=q.rows;var P=q.data,m=S.data;var I=v/O,H=J/p;var n=(I*H*65536)|0;var x=0,u=0,C=0,A=0,t=0,s=0,G=0,F=0,D=0,B=0;var Q=0,N=0,K=0,o=0,M=0,E=0;var l=jsfeat.cache.get_buffer((O*y)<<2);var g=jsfeat.cache.get_buffer((O*y)<<2);var R=jsfeat.cache.get_buffer((v*2*3)<<2);var L=l.i32;var j=g.i32;var z=R.i32;for(;xD){z[F++]=(x*y)|0;z[F++]=((t-1)*y)|0;z[F++]=((t-D)*256)|0;r++}for(C=t;C0.001){r++;z[F++]=(x*y)|0;z[F++]=(s*y)|0;z[F++]=((B-s)*256)|0}}for(x=0;xC){q++;y[E++]=((s-1)*x)|0;y[E++]=(v*x)|0;y[E++]=(s-C)*Q}for(B=s;B0.001){q++;y[E++]=(r*x)|0;y[E++]=(v*x)|0;y[E++]=(A-r)*Q}}for(v=0;v>8,255);m[u+y+1]=Math.min(G>>8,255);m[u+y+2]=Math.min(E>>8,255);m[u+y+3]=Math.min(C>>8,255)}for(;y>8,255)}A+=s;u+=s}for(z=0;z>8,255);m[u+s]=Math.min(G>>8,255);m[u+q]=Math.min(E>>8,255);m[u+p]=Math.min(C>>8,255)}for(;y>8,255)}}};var d=function(D,F,m,s,B,t,g,n){var z=0,y=0,x=0,A=0,u=0,l=0,G=0,E=0,C=0,v=t[0],r=0;var q=s<<1,p=s*3,o=s<<2;for(;z>14;t[u+1]=(n[m+o]*s+n[m+o+1]*B+n[m+o+2]*C+8192)>>14;t[u+2]=(n[m+l]*s+n[m+l+1]*B+n[m+l+2]*C+8192)>>14;t[u+3]=(n[m+k]*s+n[m+k+1]*B+n[m+k+2]*C+8192)>>14}for(;q>14}}},resample:function(l,m,i,k){var j=l.rows,g=l.cols;if(j>k&&g>i){m.resize(i,k,l.channel);if(l.type&jsfeat.U8_t&&m.type&jsfeat.U8_t&&j*g/(k*i)<256){c(l,m,i,k)}else{f(l,m,i,k)}}},box_blur_gray:function(r,J,n,l){if(typeof l==="undefined"){l=0}var z=r.cols,E=r.rows,s=E<<1,v=z<<1;var D=0,u=0,t=0,m=0;var B=((n<<1)+1)|0;var p=(n+1)|0,H=(p+1)|0;var I=l&jsfeat.BOX_BLUR_NOSCALE?1:(1/(B*B));var C=jsfeat.cache.get_buffer((z*E)<<2);var j=0,G=0,o=0,q=0,k=0;var F=C.i32;var g=r.data;var A=0;J.resize(z,E,r.channel);for(t=0;t>1;var t=g.cols,p=g.rows;var u=g.type,n=u&jsfeat.U8_t;s.resize(t,p,g.channel);var m=g.data,j=s.data;var k,i,q=(r+Math.max(p,t))|0;var l=jsfeat.cache.get_buffer(q<<2);var o=jsfeat.cache.get_buffer(r<<2);if(n){k=l.i32;i=o.i32}else{if(u&jsfeat.S32_t){k=l.i32;i=o.f32}else{k=l.f32;i=o.f32}}jsfeat.math.get_gaussian_kernel(r,v,i,u);if(n){e(k,m,j,t,p,i,r,x)}else{d(k,m,j,t,p,i,r,x)}jsfeat.cache.put_buffer(l);jsfeat.cache.put_buffer(o)},hough_transform:function(D,q,C,m){var v=D.data;var x=D.cols;var u=D.rows;var p=x;min_theta=0;max_theta=Math.PI;numangle=Math.round((max_theta-min_theta)/C);numrho=Math.round(((x+u)*2+1)/q);irho=1/q;var A=new Int32Array((numangle+2)*(numrho+2));var g=new Float32Array(numangle);var k=new Float32Array(numangle);var w=0;var B=min_theta;for(;wm&&A[o]>A[o-1]&&A[o]>=A[o+1]&&A[o]>A[o-numrho-2]&&A[o]>=A[o+numrho+2]){_sort_buf.push(o)}}}_sort_buf.sort(function(j,i){return A[j]>A[i]||(A[j]==A[i]&&j>1,l=t>>1;var B=p-(s<<1),u=l-(r<<1);var o=0,n=0,g=s+r*q,m=0,v=0,i=0;A.resize(p,l,k.channel);var z=k.data,j=A.data;for(n=0;n>2;j[i+1]=(z[m+2]+z[m+3]+z[m+q+2]+z[m+q+3]+2)>>2}for(;o>2}g+=q<<1;v+=p}},scharr_derivatives:function(j,G){var p=j.cols,s=j.rows;var H=p<<1,o=0,m=0,u=0,E,D,C,B,A,z;var v=0,t=0,r=0,i=0;var n,l;G.resize(p,s,2);var F=j.data,g=G.data;var k=jsfeat.cache.get_buffer((p+2)<<2);var q=jsfeat.cache.get_buffer((p+2)<<2);if(j.type&jsfeat.U8_t||j.type&jsfeat.S32_t){n=k.i32;l=q.i32}else{n=k.f32;l=q.f32}for(;m0?m-1:1)*p)|0;r=((m0?m-1:1)*p)|0;r=((m0;--n){h=n+w*r,x=h-r;for(q=w;q>0;--q,h-=r,x-=r){u[h]+=u[x]+u[x+1]}}}},equalize_histogram:function(j,r){var s=j.cols,q=j.rows,o=j.data;r.resize(s,q,j.channel);var l=r.data,t=s*q;var p=0,n=0,k,g;var m=jsfeat.cache.get_buffer(256<<2);k=m.i32;for(;p<256;++p){k[p]=0}for(p=0;pk){K=E;E=k;k=K}K=(3*(C+2))|0;while(--K>=0){Q[K]=0}K=((L+2)*(C+2))|0;while(--K>=0){T[K]=0}for(;H>31))-(z>>31))+((v^(v>>31))-(v>>31))}for(K=1;K<=L;++K,q+=A){if(K==L){H=M+C;while(--H>=M){Q[H]=0}}else{for(H=0;H>31))-(z>>31))+((v^(v>>31))-(v>>31))}}R=(q-A)|0;T[I-1]=0;J=0;for(H=0;HE){z=G[R];v=G[R+1];D=z^v;z=((z^(z>>31))-(z>>31))|0;v=((v^(v>>31))-(v>>31))|0;g=z*13573;U=g+((z+z)<<15);v<<=15;if(vQ[O+H-1]&&N>=Q[O+H+1]){if(N>k&&!J&&T[I+H-B]!=2){T[I+H]=2;J=1;r[F++]=I+H}else{T[I+H]=1}continue}}else{if(v>U){if(N>Q[P+H]&&N>=Q[M+H]){if(N>k&&!J&&T[I+H-B]!=2){T[I+H]=2;J=1;r[F++]=I+H}else{T[I+H]=1}continue}}else{D=D<0?-1:1;if(N>Q[P+H-D]&&N>Q[M+H+D]){if(N>k&&!J&&T[I+H-B]!=2){T[I+H]=2;J=1;r[F++]=I+H}else{T[I+H]=1}continue}}}}T[I+H]=0;J=0}T[I+C]=0;I+=B;H=P;P=O;O=M;M=H}H=I-B-1;for(K=0;K0){I=r[--F];I-=B+1;if(T[I]==1){T[I]=2,r[F++]=I}I+=1;if(T[I]==1){T[I]=2,r[F++]=I}I+=1;if(T[I]==1){T[I]=2,r[F++]=I}I+=B;if(T[I]==1){T[I]=2,r[F++]=I}I-=2;if(T[I]==1){T[I]=2,r[F++]=I}I+=B;if(T[I]==1){T[I]=2,r[F++]=I}I+=1;if(T[I]==1){T[I]=2,r[F++]=I}I+=1;if(T[I]==1){T[I]=2,r[F++]=I}}I=B+1;P=0;for(K=0;K0&&p>0&&u<(l-1)&&k<(v-1)){R=Math.max(C-u,0);Q=Math.max(p-k,0);G=(l*k+u)|0;N=H[G]+R*(H[G+1]-H[G]);M=H[G+l]+R*(H[G+l+1]-H[G+l]);q[g]=N+Q*(M-N)}else{q[g]=r}}}},warp_affine:function(k,K,p,J){if(typeof J==="undefined"){J=0}var u=k.cols,z=k.rows,j=K.cols,v=K.rows;var E=k.data,i=K.data;var o=0,n=0,I=0,q=0,A=0,m=0,w=0,G=0,D=0,h=0,g=0;var l=p.data;var t=l[0],s=l[1],r=l[2],H=l[3],F=l[4],C=l[5];for(var B=0;n=0&&A>=0&&q<(u-1)&&A<(z-1)){G=m-q;D=w-A;I=u*A+q;h=E[I]+G*(E[I+1]-E[I]);g=E[I+u]+G*(E[I+u+1]-E[I+u]);i[B]=h+D*(g-h)}else{i[B]=J}}}},skindetector:function(o,p){var n,m,h,k;var l=o.width*o.height;while(l--){k=l*4;n=o.data[k];m=o.data[k+1];h=o.data[k+2];if((n>95)&&(m>40)&&(h>20)&&(n>m)&&(n>h)&&(n-Math.min(m,h)>15)&&(Math.abs(n-m)>15)){p[l]=255}else{p[l]=0}}}}})();b.imgproc=a})(jsfeat);(function(a){var b=(function(){var h=new Int32Array([0,3,1,3,2,2,3,1,3,0,3,-1,2,-2,1,-3,0,-3,-1,-3,-2,-2,-3,-1,-3,0,-3,1,-2,2,-1,3]);var f=new Uint8Array(512);var e=new Int32Array(25);var i=new Int32Array(25);var d=function(l,n,o){var j=0;var m=h;for(;j=u){continue}s=Math.max(s,r[o+6]);s=Math.max(s,r[o+7]);s=Math.max(s,r[o+8]);u=Math.min(u,Math.max(s,r[o]));u=Math.min(u,Math.max(s,r[o+9]))}return -u-1};var c=20;return{set_threshold:function(j){c=Math.min(Math.max(j,0),255);for(var k=-255;k<=255;++k){f[(k+255)]=(k<-c?1:(k>c?2:0))}return c},detect:function(L,H,D){if(typeof D==="undefined"){D=3}var A=8,t=25;var u=L.data,X=L.cols,ar=L.rows;var ap=0,an=0,al=0,E=0,W=0,aq=0;var B=jsfeat.cache.get_buffer(3*X);var O=jsfeat.cache.get_buffer(((X+1)*3)<<2);var I=B.u8;var F=O.i32;var M=e;var J=i;var y=Math.max(3,D);var Z=Math.min((ar-2),(ar-D));var z=Math.max(3,D);var aa=Math.min((X-3),(X-D));var ah=0,P=0,C;var Q=g;var G=f;var p=c;var Y=0,ao=0,au=0,aw=0,U=0,V=0,av=0,R=0,at=0;var T=0,S=0,o=0;d(M,X,16);var am=M[0];var ak=M[1];var aj=M[2];var ai=M[3];var ag=M[4];var af=M[5];var ae=M[6];var ad=M[7];var ac=M[8];var ab=M[9];var s=M[10];var r=M[11];var q=M[12];var n=M[13];var m=M[14];var l=M[15];for(ap=0;apA){++aw;F[U+aw]=an;I[V+an]=Q(u,av,M,J,p);break}}else{ah=0}}}if(au&2){E=(Y+p);ah=0;for(al=0;alE){++ah;if(ah>A){++aw;F[U+aw]=an;I[V+an]=Q(u,av,M,J,p);break}}else{ah=0}}}}}F[U+X]=aw;if(ap==y){continue}aq=(ap-4+3)%3;R=(aq*X)|0;U=(aq*(X+1))|0;aq=(ap-5+3)%3;at=(aq*X)|0;aw=F[U+X];for(al=0;alI[R+T]&&o>I[R+S]&&o>I[at+S]&&o>I[at+an]&&o>I[at+T]&&o>I[V+S]&&o>I[V+an]&&o>I[V+T])){C=H[P];C.x=an,C.y=(ap-1),C.score=o;P++}}}jsfeat.cache.put_buffer(B);jsfeat.cache.put_buffer(O);return P}}})();a.fast_corners=b;b.set_threshold(20)})(jsfeat);(function(b){var a=(function(){var d=function(e,l,q,i,r,g,p,n,k,j){var m=0,o=0,f=(n*q+p)|0,s=f;for(m=n;m=0){j[o]=0}d(q,j,p,B,H,f,t,s,E,C);k=(s*p+t)|0;for(n=s;nF&&i>j[m-1]&&i>j[m+1]&&i>j[m-p]&&i>j[m+p]&&i>j[m-p-1]&&i>j[m+p-1]&&i>j[m-p+1]&&i>j[m+p+1])){r=c(q,m,i,H,f,G,g);if(r>D){v=A[u];v.x=o,v.y=n,v.score=r;++u;++o,++m}}}}jsfeat.cache.put_buffer(e);return u}}})();b.yape06=a})(jsfeat);(function(a){var b=(function(){var d=function(l,m,k){var j=0;var h,n;h=k;for(n=0;n=0;h--,j++){n=(Math.sqrt((k*k-h*h))+0.5)|0;m[j]=(h+l*n)}for(;-h=0;n--,j++){h=(-Math.sqrt((k*k-n*n))-0.5)|0;m[j]=(h+l*n)}for(;n>h;n--,j++){h=(-Math.sqrt((k*k-n*n))-0.5)|0;m[j]=(h+l*n)}for(h++;h<=0;h++,j++){n=(-Math.sqrt((k*k-h*h))-0.5)|0;m[j]=(h+l*n)}for(;h<-n;h++,j++){n=(-Math.sqrt((k*k-h*h))-0.5)|0;m[j]=(h+l*n)}for(n++;n<0;n++,j++){h=(Math.sqrt((k*k-n*n))+0.5)|0;m[j]=(h+l*n)}m[j]=m[0];m[j+1]=m[1];return j};var g=function(h,j,i){var k=0;if(h[j+1]!=0){k++}if(h[j-1]!=0){k++}if(h[j+i]!=0){k++}if(h[j+i+1]!=0){k++}if(h[j+i-1]!=0){k++}if(h[j-i]!=0){k++}if(h[j-i+1]!=0){k++}if(h[j-i-1]!=0){k++}return k};var c=function(l,m,i,k,j){var h,n;if(i>0){m-=k*j;for(n=-j;n<=j;++n){for(h=-j;h<=j;++h){if(l[m+h]>i){return false}}m+=k}}else{m-=k*j;for(n=-j;n<=j;++n){for(h=-j;h<=j;++h){if(l[m+h]=u)){w=s[r+i[o]];if((w<=p)){if((w>=u)){m[r]=0;return}else{o++;v=s[r+i[o]];if((v>p)){o++;t=s[r+i[o]];if((t>p)){h=3}else{if((tp)){h=7}else{if((tp)){o++;t=s[r+i[o]];if((t>p)){h=3}else{if((tp)){h=7}else{if((tp)){m[r]=0;return}o++;v=s[r+i[o]];if((v>p)){m[r]=0;return}o++;t=s[r+i[o]];if((t>p)){m[r]=0;return}h=1}}else{w=s[r+i[o]];if((wp)){v=t;o++;t=s[r+i[o]];if((tp)){m[r]=0;return}if((t>p)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((t>p)){m[r]=0;return}k-=j+v;h=8;break}if((v<=p)){m[r]=0;return}if((t<=p)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((t>p)){k-=j+v;h=3;break}if((tp)){m[r]=0;return}k-=j+v;h=1;break}if((j>p)){if((v=u)){m[r]=0;return}if((t>=u)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((tp)){k-=j+v;h=7;break}m[r]=0;return;case 2:if((j>p)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((jp)){m[r]=0;return}k-=j+v;h=4;break}if((t>p)){k-=j+v;h=7;break}if((tp)){if((tp)){k-=j+v;h=3;break}if((tp)){m[r]=0;return}if((jp)){m[r]=0;return}k-=j+v;h=1;break}if((t>=u)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((tp)){k-=j+v;h=7;break}m[r]=0;return;case 5:if((jp)){v=t;o++;t=s[r+i[o]];if((tp)){k-=j+v;h=3;break}if((tp)){m[r]=0;return}if((jp)){k-=j+v;h=3;break}if((tp)){m[r]=0;return}if((jp)){k-=j+v;h=7;break}m[r]=0;return;case 8:if((j>p)){if((tp)){m[r]=0;return}k-=j+v;h=1;break}m[r]=0;return;case 9:if((jp)){m[r]=0;return}v=t;o++;t=s[r+i[o]];if((t>p)){m[r]=0;return}k-=j+v;h=8;break}if((j>p)){v=t;o++;t=s[r+i[o]];if((t>k,j>>k,h)}},detect:function(k,J,G){if(typeof G==="undefined"){G=4}var A=this.level_tables[0];var i=A.radius|0,q=(i-1)|0;var m=A.dirs;var n=A.dirs_count|0;var v=n>>1;var O=k.data,u=k.cols|0,K=k.rows|0,N=u>>1;var H=A.scores;var s=0,r=0,j=0,l=0,o=0,p=0,z=0,I=0;var F=this.tau|0;var D=0,E;var C=Math.max(i+1,G)|0;var B=Math.max(i+1,G)|0;var M=Math.min(u-i-2,u-G)|0;var L=Math.min(K-i-2,K-G)|0;j=(B*u+C)|0;for(r=B;r=3&&c(H,l,I,N,i)){E=J[D];E.x=s,E.y=r,E.score=z;++D;s+=q,l+=q}}}}return D}}})();a.yape=b})(jsfeat);(function(b){var a=(function(){var d=new Int32Array([8,-3,9,5,4,2,7,-12,-11,9,-8,2,7,-12,12,-13,2,-13,2,12,1,-7,1,6,-2,-10,-2,-4,-13,-13,-11,-8,-13,-3,-12,-9,10,4,11,9,-13,-8,-8,-9,-11,7,-9,12,7,7,12,6,-4,-5,-3,0,-13,2,-12,-3,-9,0,-7,5,12,-6,12,-1,-3,6,-2,12,-6,-13,-4,-8,11,-13,12,-8,4,7,5,1,5,-3,10,-3,3,-7,6,12,-8,-7,-6,-2,-2,11,-1,-10,-13,12,-8,10,-7,3,-5,-3,-4,2,-3,7,-10,-12,-6,11,5,-12,6,-7,5,-6,7,-1,1,0,4,-5,9,11,11,-13,4,7,4,12,2,-1,4,4,-4,-12,-2,7,-8,-5,-7,-10,4,11,9,12,0,-8,1,-13,-13,-2,-8,2,-3,-2,-2,3,-6,9,-4,-9,8,12,10,7,0,9,1,3,7,-5,11,-10,-13,-6,-11,0,10,7,12,1,-6,-3,-6,12,10,-9,12,-4,-13,8,-8,-12,-13,0,-8,-4,3,3,7,8,5,7,10,-7,-1,7,1,-12,3,-10,5,6,2,-4,3,-10,-13,0,-13,5,-13,-7,-12,12,-13,3,-11,8,-7,12,-4,7,6,-10,12,8,-9,-1,-7,-6,-2,-5,0,12,-12,5,-7,5,3,-10,8,-13,-7,-7,-4,5,-3,-2,-1,-7,2,9,5,-11,-11,-13,-5,-13,-1,6,0,-1,5,-3,5,2,-4,-13,-4,12,-9,-6,-9,6,-12,-10,-8,-4,10,2,12,-3,7,12,12,12,-7,-13,-6,5,-4,9,-3,4,7,-1,12,2,-7,6,-5,1,-13,11,-12,5,-3,7,-2,-6,7,-8,12,-7,-13,-7,-11,-12,1,-3,12,12,2,-6,3,0,-4,3,-2,-13,-1,-13,1,9,7,1,8,-6,1,-1,3,12,9,1,12,6,-1,-9,-1,3,-13,-13,-10,5,7,7,10,12,12,-5,12,9,6,3,7,11,5,-13,6,10,2,-12,2,3,3,8,4,-6,2,6,12,-13,9,-12,10,3,-8,4,-7,9,-11,12,-4,-6,1,12,2,-8,6,-9,7,-4,2,3,3,-2,6,3,11,0,3,-3,8,-8,7,8,9,3,-11,-5,-6,-4,-10,11,-5,10,-5,-8,-3,12,-10,5,-9,0,8,-1,12,-6,4,-6,6,-11,-10,12,-8,7,4,-2,6,7,-2,0,-2,12,-5,-8,-5,2,7,-6,10,12,-9,-13,-8,-8,-5,-13,-5,-2,8,-8,9,-13,-9,-11,-9,0,1,-8,1,-2,7,-4,9,1,-2,1,-1,-4,11,-6,12,-11,-12,-9,-6,4,3,7,7,12,5,5,10,8,0,-4,2,8,-9,12,-5,-13,0,7,2,12,-1,2,1,7,5,11,7,-9,3,5,6,-8,-13,-4,-8,9,-5,9,-3,-3,-4,-7,-3,-12,6,5,8,0,-7,6,-6,12,-13,6,-5,-2,1,-10,3,10,4,1,8,-4,-2,-2,2,-13,2,-12,12,12,-2,-13,0,-6,4,1,9,3,-6,-10,-3,-5,-3,-13,-1,1,7,5,12,-11,4,-2,5,-7,-13,9,-9,-5,7,1,8,6,7,-8,7,6,-7,-4,-7,1,-8,11,-7,-8,-13,6,-12,-8,2,4,3,9,10,-5,12,3,-6,-5,-6,7,8,-3,9,-8,2,-12,2,8,-11,-2,-10,3,-12,-13,-7,-9,-11,0,-10,-5,5,-3,11,8,-2,-13,-1,12,-1,-8,0,9,-13,-11,-12,-5,-10,-2,-10,11,-3,9,-2,-13,2,-3,3,2,-9,-13,-4,0,-4,6,-3,-10,-4,12,-2,-7,-6,-11,-4,9,6,-3,6,11,-13,11,-5,5,11,11,12,6,7,-5,12,-2,-1,12,0,7,-4,-8,-3,-2,-7,1,-6,7,-13,-12,-8,-13,-7,-2,-6,-8,-8,5,-6,-9,-5,-1,-4,5,-13,7,-8,10,1,5,5,-13,1,0,10,-13,9,12,10,-1,5,-8,10,-9,-1,11,1,-13,-9,-3,-6,2,-1,-10,1,12,-13,1,-8,-10,8,-11,10,-6,2,-13,3,-6,7,-13,12,-9,-10,-10,-5,-7,-10,-8,-8,-13,4,-6,8,5,3,12,8,-13,-4,2,-3,-3,5,-13,10,-12,4,-13,5,-1,-9,9,-4,3,0,3,3,-9,-12,1,-6,1,3,2,4,-8,-10,-10,-10,9,8,-13,12,12,-8,-12,-6,-5,2,2,3,7,10,6,11,-8,6,8,8,-12,-7,10,-6,5,-3,-9,-3,9,-1,-13,-1,5,-3,-7,-3,4,-8,-2,-8,3,4,2,12,12,2,-5,3,11,6,-9,11,-13,3,-1,7,12,11,-1,12,4,-3,0,-3,6,4,-11,4,12,2,-4,2,1,-10,-6,-8,1,-13,7,-11,1,-13,12,-11,-13,6,0,11,-13,0,-1,1,4,-13,3,-9,-2,-9,8,-6,-3,-13,-6,-8,-2,5,-9,8,10,2,7,3,-9,-1,-6,-1,-1,9,5,11,-2,11,-3,12,-8,3,0,3,5,-1,4,0,10,3,-6,4,5,-13,0,-10,5,5,8,12,11,8,9,9,-6,7,-4,8,-12,-10,4,-10,9,7,3,12,4,9,-7,10,-2,7,0,12,-2,-1,-6,0,-11]);var c=new jsfeat.matrix_t(3,3,jsfeat.F32_t|jsfeat.C1_t);var f=new jsfeat.matrix_t(32,32,jsfeat.U8_t|jsfeat.C1_t);var e=function(l,n,k,i,h,j){var m=Math.cos(k);var g=Math.sin(k);c.data[0]=m,c.data[1]=-g,c.data[2]=(-m+g)*j*0.5+i,c.data[3]=g,c.data[4]=m,c.data[5]=(-g-m)*j*0.5+h;jsfeat.imgproc.warp_affine(l,n,c,128)};return{describe:function(j,u,g,B){var r=32;var x=0,A=0,q=0,p=0,z=0;var o=0,m=0,D=0;var C=j.data,n=j.cols,y=j.rows;var t=f.data;var v=16*32+16;var k=0;if(!(B.type&jsfeat.U8_t)){B.type=jsfeat.U8_t;B.cols=r;B.rows=g;B.channel=1;B.allocate()}else{B.resize(r,g,1)}var l=B.data;var s=0;for(x=0;x=0;--ai){am=(1/(1<>ai;aH=aB>>ai;ab=ay<<1;g=r[ai].data;F=S[ai].data;A=(ay-N)|0;O=(aH-N)|0;c(r[ai],V);for(ak=0;ak=A)|(o<=d)|(o>=O);if(J!=0){if(ai==0){K[ak]=0}continue}U=aF-p;T=aD-o;aK=(((1-U)*(1-T)*ad)+0.5)|0;aJ=((U*(1-T)*ad)+0.5)|0;ar=(((1-U)*T*ad)+0.5)|0;aq=(ad-aK-aJ-ar);ao=0,an=0,ag=0;for(H=0;H>(Z));v=(aA[aM]*aK+aA[aM+2]*aJ+aA[aM+ab]*ar+aA[aM+ab+2]*aq);v=(((v)+m)>>(C));B=(aA[aM+1]*aK+aA[aM+3]*aJ+aA[aM+ab+1]*ar+aA[aM+ab+3]*aq);B=(((B)+m)>>(C));w[at]=al;ac[aI++]=v;ac[aI++]=B;ao+=v*v;an+=v*B;ag+=B*B}}ao*=X;an*=X;ag*=X;aj=ao*ag-an*an;aG=(ag+ao-Math.sqrt((ao-ag)*(ao-ag)+4*an*an))/aa;if(aG=A)|(aC<=d)|(aC>=O);if(J!=0){if(ai==0){K[ak]=0}break}U=af-aE;T=ae-aC;aK=(((1-U)*(1-T)*ad)+0.5)|0;aJ=((U*(1-T)*ad)+0.5)|0;ar=(((1-U)*T*ad)+0.5)|0;aq=(ad-aK-aJ-ar);aw=0,av=0;for(H=0;H>(Z));al=(al-w[at]);aw+=al*ac[aI++];av+=al*ac[aI++]}}aw*=X;av*=X;Y=((an*av-ag*aw)*aj);W=((an*aw-ao*av)*aj);af+=Y;ae+=W;aL[Q]=af+e;aL[P]=ae+e;if(Y*Y+W*W<=f){break}if(l>0&&Math.abs(Y+E)<0.01&&Math.abs(W+z)<0.01){aL[Q]-=Y*0.5;aL[P]-=W*0.5;break}E=Y;z=W}}}jsfeat.cache.put_buffer(az);jsfeat.cache.put_buffer(s);jsfeat.cache.put_buffer(t)}}})();b.optical_flow_lk=a})(jsfeat);(function(b){var a=(function(){var c=function(e,d){var f=(e.width*0.25+0.5)|0;return d.x<=e.x+f&&d.x>=e.x-f&&d.y<=e.y+f&&d.y>=e.y-f&&d.width<=(e.width*1.5+0.5)|0&&(d.width*1.5+0.5)|0>=e.width};return{edges_density:0.07,detect_single_scale:function(E,ad,af,q,d,f,D,B){var z=(B.size[0]*D)|0,N=(B.size[1]*D)|0,V=(0.5*D+1.5)|0,U=V;var Z,X,W,Q,O,T=(d-z)|0,R=(f-N)|0;var H=(d+1)|0,w,p,r,S;var e=1/(z*N);var t,o,l,u,s,ae,A,g=true,L,h,n,G,m;var M,K,J,I,v,C;var ac=0,ab=z,aa=N*H,Y=aa+z;var F=((z*N)*255*this.edges_density)|0;var P=[];for(O=0;O0?Math.sqrt(r):1;t=B.complexClassifiers;s=t.length;g=true;for(Z=0;Zr[s].rank){r[s].parent=t}else{r[t].parent=s;if(r[t].rank==r[s].rank){r[s].rank++}t=s}var A,d=v;while(r[d].parent!=-1){A=d;d=r[d].parent;r[A].parent=t}d=y;while(r[d].parent!=-1){A=d;d=r[d].parent;r[A].parent=t}}}}}var w=[];var o=0;for(y=0;y=0){r[e].rank=~o++}v=~r[e].rank}w[y]=v}var m=[];for(y=0;y=l){h.push({x:(m[y].x*2+q)/(2*q),y:(m[y].y*2+q)/(2*q),width:(m[y].width*2+q)/(2*q),height:(m[y].height*2+q)/(2*q),neighbors:m[y].neighbors,confidence:m[y].confidence})}}var p=[];q=h.length;for(y=0;y=u.x-f&&z.y>=u.y-f&&z.x+z.width<=u.x+u.width+f&&z.y+z.height<=u.y+u.height+f&&(u.neighbors>Math.max(3,z.neighbors)||z.neighbors<3)){x=false;break}}if(x){p.push(z)}}return p}}})();b.haar=a})(jsfeat);(function(a){var b=(function(){var c=function(f,e){var g=(f.width*0.25+0.5)|0;return e.x<=f.x+g&&e.x>=f.x-g&&e.y<=f.y+g&&e.y>=f.y-g&&e.width<=(f.width*1.5+0.5)|0&&(e.width*1.5+0.5)|0>=f.width};var d=new jsfeat.pyramid_t(1);return{interval:4,scale:1.1486,next:5,scale_to:1,prepare_cascade:function(g){var m=g.stage_classifier.length;for(var h=0;h>1;h=g.rows>>1;if(p||n!=j.cols||h!=j.rows){d.data[l<<2]=new jsfeat.matrix_t(n,h,r);j=d.data[l<<2]}jsfeat.imgproc.pyrdown(g,j)}for(l=this.next*2;l>1;h=g.rows>>1;j=d.data[(l<<2)+1];if(p||n!=j.cols||h!=j.rows){d.data[(l<<2)+1]=new jsfeat.matrix_t(n,h,r);j=d.data[(l<<2)+1]}jsfeat.imgproc.pyrdown(g,j,1,0);j=d.data[(l<<2)+2];if(p||n!=j.cols||h!=j.rows){d.data[(l<<2)+2]=new jsfeat.matrix_t(n,h,r);j=d.data[(l<<2)+2]}jsfeat.imgproc.pyrdown(g,j,0,1);j=d.data[(l<<2)+3];if(p||n!=j.cols||h!=j.rows){d.data[(l<<2)+3]=new jsfeat.matrix_t(n,h,r);j=d.data[(l<<2)+3]}jsfeat.imgproc.pyrdown(g,j,1,1)}return d},detect:function(G,L){var h=this.interval;var N=this.scale;var m=this.next;var l=this.scale_to;var ab=0,aa=0,Z=0,W=0,S=0,R=0,U=0,B=0,J=0,I=0,V=0,ae=0,M=0,ad=0,w=0,Y=0,g=0;var E=0,X,Q,D,H,F,O=true,o=true;var z=1,v=1;var s=[0,1,0,1];var r=[0,0,1,1];var K=[];var C=G.data,ac=1,u=2,t=4;var A=[],e=[0,0,0];var P=[0,0,0];var T=[0,0,0];for(ab=0;ab>2);g=C[w+(m<<3)].rows-(L.height>>2);P[0]=C[w].cols*ac;P[1]=C[w+(m<<2)].cols*ac;P[2]=C[w+(m<<3)].cols*ac;T[0]=(C[w].cols*t)-(Y*t);T[1]=(C[w+(m<<2)].cols*u)-(Y*u);T[2]=(C[w+(m<<3)].cols*ac)-(Y*ac);B=L.stage_classifier.length;for(aa=0;aa=0){V=A[H.pz[ad]][e[H.pz[ad]]+H.px[ad]];if(V=0){W=A[H.nz[ad]][e[H.nz[ad]]+H.nx[ad]];if(W>M){if(ae<=W){o=false;break}M=W}}}E+=(o)?X[(Z<<1)+1]:X[Z<<1]}}if(Es[t].rank){s[t].parent=u}else{s[u].parent=t;if(s[u].rank==s[t].rank){s[t].rank++}u=t}var B,e=w;while(s[e].parent!=-1){B=e;e=s[e].parent;s[B].parent=u}e=z;while(s[e].parent!=-1){B=e;e=s[e].parent;s[B].parent=u}}}}}var x=[];var p=0;for(z=0;z=0){s[f].rank=~p++}w=~s[f].rank}x[z]=w}var o=[];for(z=0;z=m){k.push({x:(o[z].x*2+r)/(2*r),y:(o[z].y*2+r)/(2*r),width:(o[z].width*2+r)/(2*r),height:(o[z].height*2+r)/(2*r),neighbors:o[z].neighbors,confidence:o[z].confidence})}}var q=[];r=k.length;for(z=0;z=v.x-g&&A.y>=v.y-g&&A.x+A.width<=v.x+v.width+g&&A.y+A.height<=v.y+v.height+g&&(v.neighbors>Math.max(3,A.neighbors)||A.neighbors<3)){y=false;break}}if(y){q.push(A)}}return q}}})();a.bbf=b})(jsfeat);(function(a){if(typeof module==="undefined"||typeof module.exports==="undefined"){window.jsfeat=a}else{module.exports=a}})(jsfeat); \ No newline at end of file diff --git a/static/paper-full.js b/static/paper-full.js new file mode 100644 index 0000000..09cd459 --- /dev/null +++ b/static/paper-full.js @@ -0,0 +1,17459 @@ +/*! + * Paper.js v0.12.15 - The Swiss Army Knife of Vector Graphics Scripting. + * http://paperjs.org/ + * + * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey + * http://juerglehni.com/ & https://puckey.studio/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + * + * Date: Wed Mar 17 10:49:48 2021 +0100 + * + *** + * + * Straps.js - Class inheritance library with support for bean-style accessors + * + * Copyright (c) 2006 - 2020 Jürg Lehni + * http://juerglehni.com/ + * + * Distributed under the MIT license. + * + *** + * + * Acorn.js + * https://marijnhaverbeke.nl/acorn/ + * + * Acorn is a tiny, fast JavaScript parser written in JavaScript, + * created by Marijn Haverbeke and released under an MIT license. + * + */ + +var paper = function(self, undefined) { + +self = self || require('./node/self.js'); +var window = self.window, + document = self.document; + +var Base = new function() { + var hidden = /^(statics|enumerable|beans|preserve)$/, + array = [], + slice = array.slice, + create = Object.create, + describe = Object.getOwnPropertyDescriptor, + define = Object.defineProperty, + + forEach = array.forEach || function(iter, bind) { + for (var i = 0, l = this.length; i < l; i++) { + iter.call(bind, this[i], i, this); + } + }, + + forIn = function(iter, bind) { + for (var i in this) { + if (this.hasOwnProperty(i)) + iter.call(bind, this[i], i, this); + } + }, + + set = Object.assign || function(dst) { + for (var i = 1, l = arguments.length; i < l; i++) { + var src = arguments[i]; + for (var key in src) { + if (src.hasOwnProperty(key)) + dst[key] = src[key]; + } + } + return dst; + }, + + each = function(obj, iter, bind) { + if (obj) { + var desc = describe(obj, 'length'); + (desc && typeof desc.value === 'number' ? forEach : forIn) + .call(obj, iter, bind = bind || obj); + } + return bind; + }; + + function inject(dest, src, enumerable, beans, preserve) { + var beansNames = {}; + + function field(name, val) { + val = val || (val = describe(src, name)) + && (val.get ? val : val.value); + if (typeof val === 'string' && val[0] === '#') + val = dest[val.substring(1)] || val; + var isFunc = typeof val === 'function', + res = val, + prev = preserve || isFunc && !val.base + ? (val && val.get ? name in dest : dest[name]) + : null, + bean; + if (!preserve || !prev) { + if (isFunc && prev) + val.base = prev; + if (isFunc && beans !== false + && (bean = name.match(/^([gs]et|is)(([A-Z])(.*))$/))) + beansNames[bean[3].toLowerCase() + bean[4]] = bean[2]; + if (!res || isFunc || !res.get || typeof res.get !== 'function' + || !Base.isPlainObject(res)) { + res = { value: res, writable: true }; + } + if ((describe(dest, name) + || { configurable: true }).configurable) { + res.configurable = true; + res.enumerable = enumerable != null ? enumerable : !bean; + } + define(dest, name, res); + } + } + if (src) { + for (var name in src) { + if (src.hasOwnProperty(name) && !hidden.test(name)) + field(name); + } + for (var name in beansNames) { + var part = beansNames[name], + set = dest['set' + part], + get = dest['get' + part] || set && dest['is' + part]; + if (get && (beans === true || get.length === 0)) + field(name, { get: get, set: set }); + } + } + return dest; + } + + function Base() { + for (var i = 0, l = arguments.length; i < l; i++) { + var src = arguments[i]; + if (src) + set(this, src); + } + return this; + } + + return inject(Base, { + inject: function(src) { + if (src) { + var statics = src.statics === true ? src : src.statics, + beans = src.beans, + preserve = src.preserve; + if (statics !== src) + inject(this.prototype, src, src.enumerable, beans, preserve); + inject(this, statics, null, beans, preserve); + } + for (var i = 1, l = arguments.length; i < l; i++) + this.inject(arguments[i]); + return this; + }, + + extend: function() { + var base = this, + ctor, + proto; + for (var i = 0, obj, l = arguments.length; + i < l && !(ctor && proto); i++) { + obj = arguments[i]; + ctor = ctor || obj.initialize; + proto = proto || obj.prototype; + } + ctor = ctor || function() { + base.apply(this, arguments); + }; + proto = ctor.prototype = proto || create(this.prototype); + define(proto, 'constructor', + { value: ctor, writable: true, configurable: true }); + inject(ctor, this); + if (arguments.length) + this.inject.apply(ctor, arguments); + ctor.base = base; + return ctor; + } + }).inject({ + enumerable: false, + + initialize: Base, + + set: Base, + + inject: function() { + for (var i = 0, l = arguments.length; i < l; i++) { + var src = arguments[i]; + if (src) { + inject(this, src, src.enumerable, src.beans, src.preserve); + } + } + return this; + }, + + extend: function() { + var res = create(this); + return res.inject.apply(res, arguments); + }, + + each: function(iter, bind) { + return each(this, iter, bind); + }, + + clone: function() { + return new this.constructor(this); + }, + + statics: { + set: set, + each: each, + create: create, + define: define, + describe: describe, + + clone: function(obj) { + return set(new obj.constructor(), obj); + }, + + isPlainObject: function(obj) { + var ctor = obj != null && obj.constructor; + return ctor && (ctor === Object || ctor === Base + || ctor.name === 'Object'); + }, + + pick: function(a, b) { + return a !== undefined ? a : b; + }, + + slice: function(list, begin, end) { + return slice.call(list, begin, end); + } + } + }); +}; + +if (typeof module !== 'undefined') + module.exports = Base; + +Base.inject({ + enumerable: false, + + toString: function() { + return this._id != null + ? (this._class || 'Object') + (this._name + ? " '" + this._name + "'" + : ' @' + this._id) + : '{ ' + Base.each(this, function(value, key) { + if (!/^_/.test(key)) { + var type = typeof value; + this.push(key + ': ' + (type === 'number' + ? Formatter.instance.number(value) + : type === 'string' ? "'" + value + "'" : value)); + } + }, []).join(', ') + ' }'; + }, + + getClassName: function() { + return this._class || ''; + }, + + importJSON: function(json) { + return Base.importJSON(json, this); + }, + + exportJSON: function(options) { + return Base.exportJSON(this, options); + }, + + toJSON: function() { + return Base.serialize(this); + }, + + set: function(props, exclude) { + if (props) + Base.filter(this, props, exclude, this._prioritize); + return this; + } +}, { + +beans: false, +statics: { + exports: {}, + + extend: function extend() { + var res = extend.base.apply(this, arguments), + name = res.prototype._class; + if (name && !Base.exports[name]) + Base.exports[name] = res; + return res; + }, + + equals: function(obj1, obj2) { + if (obj1 === obj2) + return true; + if (obj1 && obj1.equals) + return obj1.equals(obj2); + if (obj2 && obj2.equals) + return obj2.equals(obj1); + if (obj1 && obj2 + && typeof obj1 === 'object' && typeof obj2 === 'object') { + if (Array.isArray(obj1) && Array.isArray(obj2)) { + var length = obj1.length; + if (length !== obj2.length) + return false; + while (length--) { + if (!Base.equals(obj1[length], obj2[length])) + return false; + } + } else { + var keys = Object.keys(obj1), + length = keys.length; + if (length !== Object.keys(obj2).length) + return false; + while (length--) { + var key = keys[length]; + if (!(obj2.hasOwnProperty(key) + && Base.equals(obj1[key], obj2[key]))) + return false; + } + } + return true; + } + return false; + }, + + read: function(list, start, options, amount) { + if (this === Base) { + var value = this.peek(list, start); + list.__index++; + return value; + } + var proto = this.prototype, + readIndex = proto._readIndex, + begin = start || readIndex && list.__index || 0, + length = list.length, + obj = list[begin]; + amount = amount || length - begin; + if (obj instanceof this + || options && options.readNull && obj == null && amount <= 1) { + if (readIndex) + list.__index = begin + 1; + return obj && options && options.clone ? obj.clone() : obj; + } + obj = Base.create(proto); + if (readIndex) + obj.__read = true; + obj = obj.initialize.apply(obj, begin > 0 || begin + amount < length + ? Base.slice(list, begin, begin + amount) + : list) || obj; + if (readIndex) { + list.__index = begin + obj.__read; + var filtered = obj.__filtered; + if (filtered) { + list.__filtered = filtered; + obj.__filtered = undefined; + } + obj.__read = undefined; + } + return obj; + }, + + peek: function(list, start) { + return list[list.__index = start || list.__index || 0]; + }, + + remain: function(list) { + return list.length - (list.__index || 0); + }, + + readList: function(list, start, options, amount) { + var res = [], + entry, + begin = start || 0, + end = amount ? begin + amount : list.length; + for (var i = begin; i < end; i++) { + res.push(Array.isArray(entry = list[i]) + ? this.read(entry, 0, options) + : this.read(list, i, options, 1)); + } + return res; + }, + + readNamed: function(list, name, start, options, amount) { + var value = this.getNamed(list, name), + hasValue = value !== undefined; + if (hasValue) { + var filtered = list.__filtered; + if (!filtered) { + var source = this.getSource(list); + filtered = list.__filtered = Base.create(source); + filtered.__unfiltered = source; + } + filtered[name] = undefined; + } + return this.read(hasValue ? [value] : list, start, options, amount); + }, + + readSupported: function(list, dest) { + var source = this.getSource(list), + that = this, + read = false; + if (source) { + Object.keys(source).forEach(function(key) { + if (key in dest) { + var value = that.readNamed(list, key); + if (value !== undefined) { + dest[key] = value; + } + read = true; + } + }); + } + return read; + }, + + getSource: function(list) { + var source = list.__source; + if (source === undefined) { + var arg = list.length === 1 && list[0]; + source = list.__source = arg && Base.isPlainObject(arg) + ? arg : null; + } + return source; + }, + + getNamed: function(list, name) { + var source = this.getSource(list); + if (source) { + return name ? source[name] : list.__filtered || source; + } + }, + + hasNamed: function(list, name) { + return !!this.getNamed(list, name); + }, + + filter: function(dest, source, exclude, prioritize) { + var processed; + + function handleKey(key) { + if (!(exclude && key in exclude) && + !(processed && key in processed)) { + var value = source[key]; + if (value !== undefined) + dest[key] = value; + } + } + + if (prioritize) { + var keys = {}; + for (var i = 0, key, l = prioritize.length; i < l; i++) { + if ((key = prioritize[i]) in source) { + handleKey(key); + keys[key] = true; + } + } + processed = keys; + } + + Object.keys(source.__unfiltered || source).forEach(handleKey); + return dest; + }, + + isPlainValue: function(obj, asString) { + return Base.isPlainObject(obj) || Array.isArray(obj) + || asString && typeof obj === 'string'; + }, + + serialize: function(obj, options, compact, dictionary) { + options = options || {}; + + var isRoot = !dictionary, + res; + if (isRoot) { + options.formatter = new Formatter(options.precision); + dictionary = { + length: 0, + definitions: {}, + references: {}, + add: function(item, create) { + var id = '#' + item._id, + ref = this.references[id]; + if (!ref) { + this.length++; + var res = create.call(item), + name = item._class; + if (name && res[0] !== name) + res.unshift(name); + this.definitions[id] = res; + ref = this.references[id] = [id]; + } + return ref; + } + }; + } + if (obj && obj._serialize) { + res = obj._serialize(options, dictionary); + var name = obj._class; + if (name && !obj._compactSerialize && (isRoot || !compact) + && res[0] !== name) { + res.unshift(name); + } + } else if (Array.isArray(obj)) { + res = []; + for (var i = 0, l = obj.length; i < l; i++) + res[i] = Base.serialize(obj[i], options, compact, dictionary); + } else if (Base.isPlainObject(obj)) { + res = {}; + var keys = Object.keys(obj); + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + res[key] = Base.serialize(obj[key], options, compact, + dictionary); + } + } else if (typeof obj === 'number') { + res = options.formatter.number(obj, options.precision); + } else { + res = obj; + } + return isRoot && dictionary.length > 0 + ? [['dictionary', dictionary.definitions], res] + : res; + }, + + deserialize: function(json, create, _data, _setDictionary, _isRoot) { + var res = json, + isFirst = !_data, + hasDictionary = isFirst && json && json.length + && json[0][0] === 'dictionary'; + _data = _data || {}; + if (Array.isArray(json)) { + var type = json[0], + isDictionary = type === 'dictionary'; + if (json.length == 1 && /^#/.test(type)) { + return _data.dictionary[type]; + } + type = Base.exports[type]; + res = []; + for (var i = type ? 1 : 0, l = json.length; i < l; i++) { + res.push(Base.deserialize(json[i], create, _data, + isDictionary, hasDictionary)); + } + if (type) { + var args = res; + if (create) { + res = create(type, args, isFirst || _isRoot); + } else { + res = new type(args); + } + } + } else if (Base.isPlainObject(json)) { + res = {}; + if (_setDictionary) + _data.dictionary = res; + for (var key in json) + res[key] = Base.deserialize(json[key], create, _data); + } + return hasDictionary ? res[1] : res; + }, + + exportJSON: function(obj, options) { + var json = Base.serialize(obj, options); + return options && options.asString == false + ? json + : JSON.stringify(json); + }, + + importJSON: function(json, target) { + return Base.deserialize( + typeof json === 'string' ? JSON.parse(json) : json, + function(ctor, args, isRoot) { + var useTarget = isRoot && target + && target.constructor === ctor, + obj = useTarget ? target + : Base.create(ctor.prototype); + if (args.length === 1 && obj instanceof Item + && (useTarget || !(obj instanceof Layer))) { + var arg = args[0]; + if (Base.isPlainObject(arg)) { + arg.insert = false; + if (useTarget) { + args = args.concat([{ insert: true }]); + } + } + } + (useTarget ? obj.set : ctor).apply(obj, args); + if (useTarget) + target = null; + return obj; + }); + }, + + push: function(list, items) { + var itemsLength = items.length; + if (itemsLength < 4096) { + list.push.apply(list, items); + } else { + var startLength = list.length; + list.length += itemsLength; + for (var i = 0; i < itemsLength; i++) { + list[startLength + i] = items[i]; + } + } + return list; + }, + + splice: function(list, items, index, remove) { + var amount = items && items.length, + append = index === undefined; + index = append ? list.length : index; + if (index > list.length) + index = list.length; + for (var i = 0; i < amount; i++) + items[i]._index = index + i; + if (append) { + Base.push(list, items); + return []; + } else { + var args = [index, remove]; + if (items) + Base.push(args, items); + var removed = list.splice.apply(list, args); + for (var i = 0, l = removed.length; i < l; i++) + removed[i]._index = undefined; + for (var i = index + amount, l = list.length; i < l; i++) + list[i]._index = i; + return removed; + } + }, + + capitalize: function(str) { + return str.replace(/\b[a-z]/g, function(match) { + return match.toUpperCase(); + }); + }, + + camelize: function(str) { + return str.replace(/-(.)/g, function(match, chr) { + return chr.toUpperCase(); + }); + }, + + hyphenate: function(str) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + } +}}); + +var Emitter = { + on: function(type, func) { + if (typeof type !== 'string') { + Base.each(type, function(value, key) { + this.on(key, value); + }, this); + } else { + var types = this._eventTypes, + entry = types && types[type], + handlers = this._callbacks = this._callbacks || {}; + handlers = handlers[type] = handlers[type] || []; + if (handlers.indexOf(func) === -1) { + handlers.push(func); + if (entry && entry.install && handlers.length === 1) + entry.install.call(this, type); + } + } + return this; + }, + + off: function(type, func) { + if (typeof type !== 'string') { + Base.each(type, function(value, key) { + this.off(key, value); + }, this); + return; + } + var types = this._eventTypes, + entry = types && types[type], + handlers = this._callbacks && this._callbacks[type], + index; + if (handlers) { + if (!func || (index = handlers.indexOf(func)) !== -1 + && handlers.length === 1) { + if (entry && entry.uninstall) + entry.uninstall.call(this, type); + delete this._callbacks[type]; + } else if (index !== -1) { + handlers.splice(index, 1); + } + } + return this; + }, + + once: function(type, func) { + return this.on(type, function handler() { + func.apply(this, arguments); + this.off(type, handler); + }); + }, + + emit: function(type, event) { + var handlers = this._callbacks && this._callbacks[type]; + if (!handlers) + return false; + var args = Base.slice(arguments, 1), + setTarget = event && event.target && !event.currentTarget; + handlers = handlers.slice(); + if (setTarget) + event.currentTarget = this; + for (var i = 0, l = handlers.length; i < l; i++) { + if (handlers[i].apply(this, args) == false) { + if (event && event.stop) + event.stop(); + break; + } + } + if (setTarget) + delete event.currentTarget; + return true; + }, + + responds: function(type) { + return !!(this._callbacks && this._callbacks[type]); + }, + + attach: '#on', + detach: '#off', + fire: '#emit', + + _installEvents: function(install) { + var types = this._eventTypes, + handlers = this._callbacks, + key = install ? 'install' : 'uninstall'; + if (types) { + for (var type in handlers) { + if (handlers[type].length > 0) { + var entry = types[type], + func = entry && entry[key]; + if (func) + func.call(this, type); + } + } + } + }, + + statics: { + inject: function inject(src) { + var events = src._events; + if (events) { + var types = {}; + Base.each(events, function(entry, key) { + var isString = typeof entry === 'string', + name = isString ? entry : key, + part = Base.capitalize(name), + type = name.substring(2).toLowerCase(); + types[type] = isString ? {} : entry; + name = '_' + name; + src['get' + part] = function() { + return this[name]; + }; + src['set' + part] = function(func) { + var prev = this[name]; + if (prev) + this.off(type, prev); + if (func) + this.on(type, func); + this[name] = func; + }; + }); + src._eventTypes = types; + } + return inject.base.apply(this, arguments); + } + } +}; + +var PaperScope = Base.extend({ + _class: 'PaperScope', + + initialize: function PaperScope() { + paper = this; + this.settings = new Base({ + applyMatrix: true, + insertItems: true, + handleSize: 4, + hitTolerance: 0 + }); + this.project = null; + this.projects = []; + this.tools = []; + this._id = PaperScope._id++; + PaperScope._scopes[this._id] = this; + var proto = PaperScope.prototype; + if (!this.support) { + var ctx = CanvasProvider.getContext(1, 1) || {}; + proto.support = { + nativeDash: 'setLineDash' in ctx || 'mozDash' in ctx, + nativeBlendModes: BlendMode.nativeModes + }; + CanvasProvider.release(ctx); + } + if (!this.agent) { + var user = self.navigator.userAgent.toLowerCase(), + os = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0], + platform = os === 'darwin' ? 'mac' : os, + agent = proto.agent = proto.browser = { platform: platform }; + if (platform) + agent[platform] = true; + user.replace( + /(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\/?\s*([.\d]+)(?:.*version\/([.\d]+))?(?:.*rv\:v?([.\d]+))?/g, + function(match, n, v1, v2, rv) { + if (!agent.chrome) { + var v = n === 'opera' ? v2 : + /^(node|trident)$/.test(n) ? rv : v1; + agent.version = v; + agent.versionNumber = parseFloat(v); + n = { trident: 'msie', jsdom: 'node' }[n] || n; + agent.name = n; + agent[n] = true; + } + } + ); + if (agent.chrome) + delete agent.webkit; + if (agent.atom) + delete agent.chrome; + } + }, + + version: "0.12.15", + + getView: function() { + var project = this.project; + return project && project._view; + }, + + getPaper: function() { + return this; + }, + + execute: function(code, options) { + var exports = paper.PaperScript.execute(code, this, options); + View.updateFocus(); + return exports; + }, + + install: function(scope) { + var that = this; + Base.each(['project', 'view', 'tool'], function(key) { + Base.define(scope, key, { + configurable: true, + get: function() { + return that[key]; + } + }); + }); + for (var key in this) + if (!/^_/.test(key) && this[key]) + scope[key] = this[key]; + }, + + setup: function(element) { + paper = this; + this.project = new Project(element); + return this; + }, + + createCanvas: function(width, height) { + return CanvasProvider.getCanvas(width, height); + }, + + activate: function() { + paper = this; + }, + + clear: function() { + var projects = this.projects, + tools = this.tools; + for (var i = projects.length - 1; i >= 0; i--) + projects[i].remove(); + for (var i = tools.length - 1; i >= 0; i--) + tools[i].remove(); + }, + + remove: function() { + this.clear(); + delete PaperScope._scopes[this._id]; + }, + + statics: new function() { + function handleAttribute(name) { + name += 'Attribute'; + return function(el, attr) { + return el[name](attr) || el[name]('data-paper-' + attr); + }; + } + + return { + _scopes: {}, + _id: 0, + + get: function(id) { + return this._scopes[id] || null; + }, + + getAttribute: handleAttribute('get'), + hasAttribute: handleAttribute('has') + }; + } +}); + +var PaperScopeItem = Base.extend(Emitter, { + + initialize: function(activate) { + this._scope = paper; + this._index = this._scope[this._list].push(this) - 1; + if (activate || !this._scope[this._reference]) + this.activate(); + }, + + activate: function() { + if (!this._scope) + return false; + var prev = this._scope[this._reference]; + if (prev && prev !== this) + prev.emit('deactivate'); + this._scope[this._reference] = this; + this.emit('activate', prev); + return true; + }, + + isActive: function() { + return this._scope[this._reference] === this; + }, + + remove: function() { + if (this._index == null) + return false; + Base.splice(this._scope[this._list], null, this._index, 1); + if (this._scope[this._reference] == this) + this._scope[this._reference] = null; + this._scope = null; + return true; + }, + + getView: function() { + return this._scope.getView(); + } +}); + +var CollisionDetection = { + findItemBoundsCollisions: function(items1, items2, tolerance) { + function getBounds(items) { + var bounds = new Array(items.length); + for (var i = 0; i < items.length; i++) { + var rect = items[i].getBounds(); + bounds[i] = [rect.left, rect.top, rect.right, rect.bottom]; + } + return bounds; + } + + var bounds1 = getBounds(items1), + bounds2 = !items2 || items2 === items1 + ? bounds1 + : getBounds(items2); + return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0); + }, + + findCurveBoundsCollisions: function(curves1, curves2, tolerance, bothAxis) { + function getBounds(curves) { + var min = Math.min, + max = Math.max, + bounds = new Array(curves.length); + for (var i = 0; i < curves.length; i++) { + var v = curves[i]; + bounds[i] = [ + min(v[0], v[2], v[4], v[6]), + min(v[1], v[3], v[5], v[7]), + max(v[0], v[2], v[4], v[6]), + max(v[1], v[3], v[5], v[7]) + ]; + } + return bounds; + } + + var bounds1 = getBounds(curves1), + bounds2 = !curves2 || curves2 === curves1 + ? bounds1 + : getBounds(curves2); + if (bothAxis) { + var hor = this.findBoundsCollisions( + bounds1, bounds2, tolerance || 0, false, true), + ver = this.findBoundsCollisions( + bounds1, bounds2, tolerance || 0, true, true), + list = []; + for (var i = 0, l = hor.length; i < l; i++) { + list[i] = { hor: hor[i], ver: ver[i] }; + } + return list; + } + return this.findBoundsCollisions(bounds1, bounds2, tolerance || 0); + }, + + findBoundsCollisions: function(boundsA, boundsB, tolerance, + sweepVertical, onlySweepAxisCollisions) { + var self = !boundsB || boundsA === boundsB, + allBounds = self ? boundsA : boundsA.concat(boundsB), + lengthA = boundsA.length, + lengthAll = allBounds.length; + + function binarySearch(indices, coord, value) { + var lo = 0, + hi = indices.length; + while (lo < hi) { + var mid = (hi + lo) >>> 1; + if (allBounds[indices[mid]][coord] < value) { + lo = mid + 1; + } else { + hi = mid; + } + } + return lo - 1; + } + + var pri0 = sweepVertical ? 1 : 0, + pri1 = pri0 + 2, + sec0 = sweepVertical ? 0 : 1, + sec1 = sec0 + 2; + var allIndicesByPri0 = new Array(lengthAll); + for (var i = 0; i < lengthAll; i++) { + allIndicesByPri0[i] = i; + } + allIndicesByPri0.sort(function(i1, i2) { + return allBounds[i1][pri0] - allBounds[i2][pri0]; + }); + var activeIndicesByPri1 = [], + allCollisions = new Array(lengthA); + for (var i = 0; i < lengthAll; i++) { + var curIndex = allIndicesByPri0[i], + curBounds = allBounds[curIndex], + origIndex = self ? curIndex : curIndex - lengthA, + isCurrentA = curIndex < lengthA, + isCurrentB = self || !isCurrentA, + curCollisions = isCurrentA ? [] : null; + if (activeIndicesByPri1.length) { + var pruneCount = binarySearch(activeIndicesByPri1, pri1, + curBounds[pri0] - tolerance) + 1; + activeIndicesByPri1.splice(0, pruneCount); + if (self && onlySweepAxisCollisions) { + curCollisions = curCollisions.concat(activeIndicesByPri1); + for (var j = 0; j < activeIndicesByPri1.length; j++) { + var activeIndex = activeIndicesByPri1[j]; + allCollisions[activeIndex].push(origIndex); + } + } else { + var curSec1 = curBounds[sec1], + curSec0 = curBounds[sec0]; + for (var j = 0; j < activeIndicesByPri1.length; j++) { + var activeIndex = activeIndicesByPri1[j], + activeBounds = allBounds[activeIndex], + isActiveA = activeIndex < lengthA, + isActiveB = self || activeIndex >= lengthA; + + if ( + onlySweepAxisCollisions || + ( + isCurrentA && isActiveB || + isCurrentB && isActiveA + ) && ( + curSec1 >= activeBounds[sec0] - tolerance && + curSec0 <= activeBounds[sec1] + tolerance + ) + ) { + if (isCurrentA && isActiveB) { + curCollisions.push( + self ? activeIndex : activeIndex - lengthA); + } + if (isCurrentB && isActiveA) { + allCollisions[activeIndex].push(origIndex); + } + } + } + } + } + if (isCurrentA) { + if (boundsA === boundsB) { + curCollisions.push(curIndex); + } + allCollisions[curIndex] = curCollisions; + } + if (activeIndicesByPri1.length) { + var curPri1 = curBounds[pri1], + index = binarySearch(activeIndicesByPri1, pri1, curPri1); + activeIndicesByPri1.splice(index + 1, 0, curIndex); + } else { + activeIndicesByPri1.push(curIndex); + } + } + for (var i = 0; i < allCollisions.length; i++) { + var collisions = allCollisions[i]; + if (collisions) { + collisions.sort(function(i1, i2) { return i1 - i2; }); + } + } + return allCollisions; + } +}; + +var Formatter = Base.extend({ + initialize: function(precision) { + this.precision = Base.pick(precision, 5); + this.multiplier = Math.pow(10, this.precision); + }, + + number: function(val) { + return this.precision < 16 + ? Math.round(val * this.multiplier) / this.multiplier : val; + }, + + pair: function(val1, val2, separator) { + return this.number(val1) + (separator || ',') + this.number(val2); + }, + + point: function(val, separator) { + return this.number(val.x) + (separator || ',') + this.number(val.y); + }, + + size: function(val, separator) { + return this.number(val.width) + (separator || ',') + + this.number(val.height); + }, + + rectangle: function(val, separator) { + return this.point(val, separator) + (separator || ',') + + this.size(val, separator); + } +}); + +Formatter.instance = new Formatter(); + +var Numerical = new function() { + + var abscissas = [ + [ 0.5773502691896257645091488], + [0,0.7745966692414833770358531], + [ 0.3399810435848562648026658,0.8611363115940525752239465], + [0,0.5384693101056830910363144,0.9061798459386639927976269], + [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016], + [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897], + [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609], + [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762], + [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640], + [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380], + [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491], + [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294], + [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973], + [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657], + [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542] + ]; + + var weights = [ + [1], + [0.8888888888888888888888889,0.5555555555555555555555556], + [0.6521451548625461426269361,0.3478548451374538573730639], + [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640], + [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961], + [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114], + [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314], + [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922], + [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688], + [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537], + [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160], + [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216], + [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329], + [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284], + [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806] + ]; + + var abs = Math.abs, + sqrt = Math.sqrt, + pow = Math.pow, + log2 = Math.log2 || function(x) { + return Math.log(x) * Math.LOG2E; + }, + EPSILON = 1e-12, + MACHINE_EPSILON = 1.12e-16; + + function clamp(value, min, max) { + return value < min ? min : value > max ? max : value; + } + + function getDiscriminant(a, b, c) { + function split(v) { + var x = v * 134217729, + y = v - x, + hi = y + x, + lo = v - hi; + return [hi, lo]; + } + + var D = b * b - a * c, + E = b * b + a * c; + if (abs(D) * 3 < E) { + var ad = split(a), + bd = split(b), + cd = split(c), + p = b * b, + dp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1], + q = a * c, + dq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0]) + + ad[1] * cd[1]; + D = (p - q) + (dp - dq); + } + return D; + } + + function getNormalizationFactor() { + var norm = Math.max.apply(Math, arguments); + return norm && (norm < 1e-8 || norm > 1e8) + ? pow(2, -Math.round(log2(norm))) + : 0; + } + + return { + EPSILON: EPSILON, + MACHINE_EPSILON: MACHINE_EPSILON, + CURVETIME_EPSILON: 1e-8, + GEOMETRIC_EPSILON: 1e-7, + TRIGONOMETRIC_EPSILON: 1e-8, + KAPPA: 4 * (sqrt(2) - 1) / 3, + + isZero: function(val) { + return val >= -EPSILON && val <= EPSILON; + }, + + isMachineZero: function(val) { + return val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON; + }, + + clamp: clamp, + + integrate: function(f, a, b, n) { + var x = abscissas[n - 2], + w = weights[n - 2], + A = (b - a) * 0.5, + B = A + a, + i = 0, + m = (n + 1) >> 1, + sum = n & 1 ? w[i++] * f(B) : 0; + while (i < m) { + var Ax = A * x[i]; + sum += w[i++] * (f(B + Ax) + f(B - Ax)); + } + return A * sum; + }, + + findRoot: function(f, df, x, a, b, n, tolerance) { + for (var i = 0; i < n; i++) { + var fx = f(x), + dx = fx / df(x), + nx = x - dx; + if (abs(dx) < tolerance) { + x = nx; + break; + } + if (fx > 0) { + b = x; + x = nx <= a ? (a + b) * 0.5 : nx; + } else { + a = x; + x = nx >= b ? (a + b) * 0.5 : nx; + } + } + return clamp(x, a, b); + }, + + solveQuadratic: function(a, b, c, roots, min, max) { + var x1, x2 = Infinity; + if (abs(a) < EPSILON) { + if (abs(b) < EPSILON) + return abs(c) < EPSILON ? -1 : 0; + x1 = -c / b; + } else { + b *= -0.5; + var D = getDiscriminant(a, b, c); + if (D && abs(D) < MACHINE_EPSILON) { + var f = getNormalizationFactor(abs(a), abs(b), abs(c)); + if (f) { + a *= f; + b *= f; + c *= f; + D = getDiscriminant(a, b, c); + } + } + if (D >= -MACHINE_EPSILON) { + var Q = D < 0 ? 0 : sqrt(D), + R = b + (b < 0 ? -Q : Q); + if (R === 0) { + x1 = c / a; + x2 = -x1; + } else { + x1 = R / a; + x2 = c / R; + } + } + } + var count = 0, + boundless = min == null, + minB = min - EPSILON, + maxB = max + EPSILON; + if (isFinite(x1) && (boundless || x1 > minB && x1 < maxB)) + roots[count++] = boundless ? x1 : clamp(x1, min, max); + if (x2 !== x1 + && isFinite(x2) && (boundless || x2 > minB && x2 < maxB)) + roots[count++] = boundless ? x2 : clamp(x2, min, max); + return count; + }, + + solveCubic: function(a, b, c, d, roots, min, max) { + var f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)), + x, b1, c2, qd, q; + if (f) { + a *= f; + b *= f; + c *= f; + d *= f; + } + + function evaluate(x0) { + x = x0; + var tmp = a * x; + b1 = tmp + b; + c2 = b1 * x + c; + qd = (tmp + b1) * x + c2; + q = c2 * x + d; + } + + if (abs(a) < EPSILON) { + a = b; + b1 = c; + c2 = d; + x = Infinity; + } else if (abs(d) < EPSILON) { + b1 = b; + c2 = c; + x = 0; + } else { + evaluate(-(b / a) / 3); + var t = q / a, + r = pow(abs(t), 1/3), + s = t < 0 ? -1 : 1, + td = -qd / a, + rd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r, + x0 = x - s * rd; + if (x0 !== x) { + do { + evaluate(x0); + x0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON); + } while (s * x0 > s * x); + if (abs(a) * x * x > abs(d / x)) { + c2 = -d / x; + b1 = (c2 - c) / x; + } + } + } + var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max), + boundless = min == null; + if (isFinite(x) && (count === 0 + || count > 0 && x !== roots[0] && x !== roots[1]) + && (boundless || x > min - EPSILON && x < max + EPSILON)) + roots[count++] = boundless ? x : clamp(x, min, max); + return count; + } + }; +}; + +var UID = { + _id: 1, + _pools: {}, + + get: function(name) { + if (name) { + var pool = this._pools[name]; + if (!pool) + pool = this._pools[name] = { _id: 1 }; + return pool._id++; + } else { + return this._id++; + } + } +}; + +var Point = Base.extend({ + _class: 'Point', + _readIndex: true, + + initialize: function Point(arg0, arg1) { + var type = typeof arg0, + reading = this.__read, + read = 0; + if (type === 'number') { + var hasY = typeof arg1 === 'number'; + this._set(arg0, hasY ? arg1 : arg0); + if (reading) + read = hasY ? 2 : 1; + } else if (type === 'undefined' || arg0 === null) { + this._set(0, 0); + if (reading) + read = arg0 === null ? 1 : 0; + } else { + var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0; + read = 1; + if (Array.isArray(obj)) { + this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0])); + } else if ('x' in obj) { + this._set(obj.x || 0, obj.y || 0); + } else if ('width' in obj) { + this._set(obj.width || 0, obj.height || 0); + } else if ('angle' in obj) { + this._set(obj.length || 0, 0); + this.setAngle(obj.angle || 0); + } else { + this._set(0, 0); + read = 0; + } + } + if (reading) + this.__read = read; + return this; + }, + + set: '#initialize', + + _set: function(x, y) { + this.x = x; + this.y = y; + return this; + }, + + equals: function(point) { + return this === point || point + && (this.x === point.x && this.y === point.y + || Array.isArray(point) + && this.x === point[0] && this.y === point[1]) + || false; + }, + + clone: function() { + return new Point(this.x, this.y); + }, + + toString: function() { + var f = Formatter.instance; + return '{ x: ' + f.number(this.x) + ', y: ' + f.number(this.y) + ' }'; + }, + + _serialize: function(options) { + var f = options.formatter; + return [f.number(this.x), f.number(this.y)]; + }, + + getLength: function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + }, + + setLength: function(length) { + if (this.isZero()) { + var angle = this._angle || 0; + this._set( + Math.cos(angle) * length, + Math.sin(angle) * length + ); + } else { + var scale = length / this.getLength(); + if (Numerical.isZero(scale)) + this.getAngle(); + this._set( + this.x * scale, + this.y * scale + ); + } + }, + getAngle: function() { + return this.getAngleInRadians.apply(this, arguments) * 180 / Math.PI; + }, + + setAngle: function(angle) { + this.setAngleInRadians.call(this, angle * Math.PI / 180); + }, + + getAngleInDegrees: '#getAngle', + setAngleInDegrees: '#setAngle', + + getAngleInRadians: function() { + if (!arguments.length) { + return this.isZero() + ? this._angle || 0 + : this._angle = Math.atan2(this.y, this.x); + } else { + var point = Point.read(arguments), + div = this.getLength() * point.getLength(); + if (Numerical.isZero(div)) { + return NaN; + } else { + var a = this.dot(point) / div; + return Math.acos(a < -1 ? -1 : a > 1 ? 1 : a); + } + } + }, + + setAngleInRadians: function(angle) { + this._angle = angle; + if (!this.isZero()) { + var length = this.getLength(); + this._set( + Math.cos(angle) * length, + Math.sin(angle) * length + ); + } + }, + + getQuadrant: function() { + return this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3; + } +}, { + beans: false, + + getDirectedAngle: function() { + var point = Point.read(arguments); + return Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI; + }, + + getDistance: function() { + var args = arguments, + point = Point.read(args), + x = point.x - this.x, + y = point.y - this.y, + d = x * x + y * y, + squared = Base.read(args); + return squared ? d : Math.sqrt(d); + }, + + normalize: function(length) { + if (length === undefined) + length = 1; + var current = this.getLength(), + scale = current !== 0 ? length / current : 0, + point = new Point(this.x * scale, this.y * scale); + if (scale >= 0) + point._angle = this._angle; + return point; + }, + + rotate: function(angle, center) { + if (angle === 0) + return this.clone(); + angle = angle * Math.PI / 180; + var point = center ? this.subtract(center) : this, + sin = Math.sin(angle), + cos = Math.cos(angle); + point = new Point( + point.x * cos - point.y * sin, + point.x * sin + point.y * cos + ); + return center ? point.add(center) : point; + }, + + transform: function(matrix) { + return matrix ? matrix._transformPoint(this) : this; + }, + + add: function() { + var point = Point.read(arguments); + return new Point(this.x + point.x, this.y + point.y); + }, + + subtract: function() { + var point = Point.read(arguments); + return new Point(this.x - point.x, this.y - point.y); + }, + + multiply: function() { + var point = Point.read(arguments); + return new Point(this.x * point.x, this.y * point.y); + }, + + divide: function() { + var point = Point.read(arguments); + return new Point(this.x / point.x, this.y / point.y); + }, + + modulo: function() { + var point = Point.read(arguments); + return new Point(this.x % point.x, this.y % point.y); + }, + + negate: function() { + return new Point(-this.x, -this.y); + }, + + isInside: function() { + return Rectangle.read(arguments).contains(this); + }, + + isClose: function() { + var args = arguments, + point = Point.read(args), + tolerance = Base.read(args); + return this.getDistance(point) <= tolerance; + }, + + isCollinear: function() { + var point = Point.read(arguments); + return Point.isCollinear(this.x, this.y, point.x, point.y); + }, + + isColinear: '#isCollinear', + + isOrthogonal: function() { + var point = Point.read(arguments); + return Point.isOrthogonal(this.x, this.y, point.x, point.y); + }, + + isZero: function() { + var isZero = Numerical.isZero; + return isZero(this.x) && isZero(this.y); + }, + + isNaN: function() { + return isNaN(this.x) || isNaN(this.y); + }, + + isInQuadrant: function(q) { + return this.x * (q > 1 && q < 4 ? -1 : 1) >= 0 + && this.y * (q > 2 ? -1 : 1) >= 0; + }, + + dot: function() { + var point = Point.read(arguments); + return this.x * point.x + this.y * point.y; + }, + + cross: function() { + var point = Point.read(arguments); + return this.x * point.y - this.y * point.x; + }, + + project: function() { + var point = Point.read(arguments), + scale = point.isZero() ? 0 : this.dot(point) / point.dot(point); + return new Point( + point.x * scale, + point.y * scale + ); + }, + + statics: { + min: function() { + var args = arguments, + point1 = Point.read(args), + point2 = Point.read(args); + return new Point( + Math.min(point1.x, point2.x), + Math.min(point1.y, point2.y) + ); + }, + + max: function() { + var args = arguments, + point1 = Point.read(args), + point2 = Point.read(args); + return new Point( + Math.max(point1.x, point2.x), + Math.max(point1.y, point2.y) + ); + }, + + random: function() { + return new Point(Math.random(), Math.random()); + }, + + isCollinear: function(x1, y1, x2, y2) { + return Math.abs(x1 * y2 - y1 * x2) + <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) + * 1e-8; + }, + + isOrthogonal: function(x1, y1, x2, y2) { + return Math.abs(x1 * x2 + y1 * y2) + <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) + * 1e-8; + } + } +}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) { + var op = Math[key]; + this[key] = function() { + return new Point(op(this.x), op(this.y)); + }; +}, {})); + +var LinkedPoint = Point.extend({ + initialize: function Point(x, y, owner, setter) { + this._x = x; + this._y = y; + this._owner = owner; + this._setter = setter; + }, + + _set: function(x, y, _dontNotify) { + this._x = x; + this._y = y; + if (!_dontNotify) + this._owner[this._setter](this); + return this; + }, + + getX: function() { + return this._x; + }, + + setX: function(x) { + this._x = x; + this._owner[this._setter](this); + }, + + getY: function() { + return this._y; + }, + + setY: function(y) { + this._y = y; + this._owner[this._setter](this); + }, + + isSelected: function() { + return !!(this._owner._selection & this._getSelection()); + }, + + setSelected: function(selected) { + this._owner._changeSelection(this._getSelection(), selected); + }, + + _getSelection: function() { + return this._setter === 'setPosition' ? 4 : 0; + } +}); + +var Size = Base.extend({ + _class: 'Size', + _readIndex: true, + + initialize: function Size(arg0, arg1) { + var type = typeof arg0, + reading = this.__read, + read = 0; + if (type === 'number') { + var hasHeight = typeof arg1 === 'number'; + this._set(arg0, hasHeight ? arg1 : arg0); + if (reading) + read = hasHeight ? 2 : 1; + } else if (type === 'undefined' || arg0 === null) { + this._set(0, 0); + if (reading) + read = arg0 === null ? 1 : 0; + } else { + var obj = type === 'string' ? arg0.split(/[\s,]+/) || [] : arg0; + read = 1; + if (Array.isArray(obj)) { + this._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0])); + } else if ('width' in obj) { + this._set(obj.width || 0, obj.height || 0); + } else if ('x' in obj) { + this._set(obj.x || 0, obj.y || 0); + } else { + this._set(0, 0); + read = 0; + } + } + if (reading) + this.__read = read; + return this; + }, + + set: '#initialize', + + _set: function(width, height) { + this.width = width; + this.height = height; + return this; + }, + + equals: function(size) { + return size === this || size && (this.width === size.width + && this.height === size.height + || Array.isArray(size) && this.width === size[0] + && this.height === size[1]) || false; + }, + + clone: function() { + return new Size(this.width, this.height); + }, + + toString: function() { + var f = Formatter.instance; + return '{ width: ' + f.number(this.width) + + ', height: ' + f.number(this.height) + ' }'; + }, + + _serialize: function(options) { + var f = options.formatter; + return [f.number(this.width), + f.number(this.height)]; + }, + + add: function() { + var size = Size.read(arguments); + return new Size(this.width + size.width, this.height + size.height); + }, + + subtract: function() { + var size = Size.read(arguments); + return new Size(this.width - size.width, this.height - size.height); + }, + + multiply: function() { + var size = Size.read(arguments); + return new Size(this.width * size.width, this.height * size.height); + }, + + divide: function() { + var size = Size.read(arguments); + return new Size(this.width / size.width, this.height / size.height); + }, + + modulo: function() { + var size = Size.read(arguments); + return new Size(this.width % size.width, this.height % size.height); + }, + + negate: function() { + return new Size(-this.width, -this.height); + }, + + isZero: function() { + var isZero = Numerical.isZero; + return isZero(this.width) && isZero(this.height); + }, + + isNaN: function() { + return isNaN(this.width) || isNaN(this.height); + }, + + statics: { + min: function(size1, size2) { + return new Size( + Math.min(size1.width, size2.width), + Math.min(size1.height, size2.height)); + }, + + max: function(size1, size2) { + return new Size( + Math.max(size1.width, size2.width), + Math.max(size1.height, size2.height)); + }, + + random: function() { + return new Size(Math.random(), Math.random()); + } + } +}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) { + var op = Math[key]; + this[key] = function() { + return new Size(op(this.width), op(this.height)); + }; +}, {})); + +var LinkedSize = Size.extend({ + initialize: function Size(width, height, owner, setter) { + this._width = width; + this._height = height; + this._owner = owner; + this._setter = setter; + }, + + _set: function(width, height, _dontNotify) { + this._width = width; + this._height = height; + if (!_dontNotify) + this._owner[this._setter](this); + return this; + }, + + getWidth: function() { + return this._width; + }, + + setWidth: function(width) { + this._width = width; + this._owner[this._setter](this); + }, + + getHeight: function() { + return this._height; + }, + + setHeight: function(height) { + this._height = height; + this._owner[this._setter](this); + } +}); + +var Rectangle = Base.extend({ + _class: 'Rectangle', + _readIndex: true, + beans: true, + + initialize: function Rectangle(arg0, arg1, arg2, arg3) { + var args = arguments, + type = typeof arg0, + read; + if (type === 'number') { + this._set(arg0, arg1, arg2, arg3); + read = 4; + } else if (type === 'undefined' || arg0 === null) { + this._set(0, 0, 0, 0); + read = arg0 === null ? 1 : 0; + } else if (args.length === 1) { + if (Array.isArray(arg0)) { + this._set.apply(this, arg0); + read = 1; + } else if (arg0.x !== undefined || arg0.width !== undefined) { + this._set(arg0.x || 0, arg0.y || 0, + arg0.width || 0, arg0.height || 0); + read = 1; + } else if (arg0.from === undefined && arg0.to === undefined) { + this._set(0, 0, 0, 0); + if (Base.readSupported(args, this)) { + read = 1; + } + } + } + if (read === undefined) { + var frm = Point.readNamed(args, 'from'), + next = Base.peek(args), + x = frm.x, + y = frm.y, + width, + height; + if (next && next.x !== undefined || Base.hasNamed(args, 'to')) { + var to = Point.readNamed(args, 'to'); + width = to.x - x; + height = to.y - y; + if (width < 0) { + x = to.x; + width = -width; + } + if (height < 0) { + y = to.y; + height = -height; + } + } else { + var size = Size.read(args); + width = size.width; + height = size.height; + } + this._set(x, y, width, height); + read = args.__index; + } + var filtered = args.__filtered; + if (filtered) + this.__filtered = filtered; + if (this.__read) + this.__read = read; + return this; + }, + + set: '#initialize', + + _set: function(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + return this; + }, + + clone: function() { + return new Rectangle(this.x, this.y, this.width, this.height); + }, + + equals: function(rect) { + var rt = Base.isPlainValue(rect) + ? Rectangle.read(arguments) + : rect; + return rt === this + || rt && this.x === rt.x && this.y === rt.y + && this.width === rt.width && this.height === rt.height + || false; + }, + + toString: function() { + var f = Formatter.instance; + return '{ x: ' + f.number(this.x) + + ', y: ' + f.number(this.y) + + ', width: ' + f.number(this.width) + + ', height: ' + f.number(this.height) + + ' }'; + }, + + _serialize: function(options) { + var f = options.formatter; + return [f.number(this.x), + f.number(this.y), + f.number(this.width), + f.number(this.height)]; + }, + + getPoint: function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this.x, this.y, this, 'setPoint'); + }, + + setPoint: function() { + var point = Point.read(arguments); + this.x = point.x; + this.y = point.y; + }, + + getSize: function(_dontLink) { + var ctor = _dontLink ? Size : LinkedSize; + return new ctor(this.width, this.height, this, 'setSize'); + }, + + _fw: 1, + _fh: 1, + + setSize: function() { + var size = Size.read(arguments), + sx = this._sx, + sy = this._sy, + w = size.width, + h = size.height; + if (sx) { + this.x += (this.width - w) * sx; + } + if (sy) { + this.y += (this.height - h) * sy; + } + this.width = w; + this.height = h; + this._fw = this._fh = 1; + }, + + getLeft: function() { + return this.x; + }, + + setLeft: function(left) { + if (!this._fw) { + var amount = left - this.x; + this.width -= this._sx === 0.5 ? amount * 2 : amount; + } + this.x = left; + this._sx = this._fw = 0; + }, + + getTop: function() { + return this.y; + }, + + setTop: function(top) { + if (!this._fh) { + var amount = top - this.y; + this.height -= this._sy === 0.5 ? amount * 2 : amount; + } + this.y = top; + this._sy = this._fh = 0; + }, + + getRight: function() { + return this.x + this.width; + }, + + setRight: function(right) { + if (!this._fw) { + var amount = right - this.x; + this.width = this._sx === 0.5 ? amount * 2 : amount; + } + this.x = right - this.width; + this._sx = 1; + this._fw = 0; + }, + + getBottom: function() { + return this.y + this.height; + }, + + setBottom: function(bottom) { + if (!this._fh) { + var amount = bottom - this.y; + this.height = this._sy === 0.5 ? amount * 2 : amount; + } + this.y = bottom - this.height; + this._sy = 1; + this._fh = 0; + }, + + getCenterX: function() { + return this.x + this.width / 2; + }, + + setCenterX: function(x) { + if (this._fw || this._sx === 0.5) { + this.x = x - this.width / 2; + } else { + if (this._sx) { + this.x += (x - this.x) * 2 * this._sx; + } + this.width = (x - this.x) * 2; + } + this._sx = 0.5; + this._fw = 0; + }, + + getCenterY: function() { + return this.y + this.height / 2; + }, + + setCenterY: function(y) { + if (this._fh || this._sy === 0.5) { + this.y = y - this.height / 2; + } else { + if (this._sy) { + this.y += (y - this.y) * 2 * this._sy; + } + this.height = (y - this.y) * 2; + } + this._sy = 0.5; + this._fh = 0; + }, + + getCenter: function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this.getCenterX(), this.getCenterY(), this, 'setCenter'); + }, + + setCenter: function() { + var point = Point.read(arguments); + this.setCenterX(point.x); + this.setCenterY(point.y); + return this; + }, + + getArea: function() { + return this.width * this.height; + }, + + isEmpty: function() { + return this.width === 0 || this.height === 0; + }, + + contains: function(arg) { + return arg && arg.width !== undefined + || (Array.isArray(arg) ? arg : arguments).length === 4 + ? this._containsRectangle(Rectangle.read(arguments)) + : this._containsPoint(Point.read(arguments)); + }, + + _containsPoint: function(point) { + var x = point.x, + y = point.y; + return x >= this.x && y >= this.y + && x <= this.x + this.width + && y <= this.y + this.height; + }, + + _containsRectangle: function(rect) { + var x = rect.x, + y = rect.y; + return x >= this.x && y >= this.y + && x + rect.width <= this.x + this.width + && y + rect.height <= this.y + this.height; + }, + + intersects: function() { + var rect = Rectangle.read(arguments), + epsilon = Base.read(arguments) || 0; + return rect.x + rect.width > this.x - epsilon + && rect.y + rect.height > this.y - epsilon + && rect.x < this.x + this.width + epsilon + && rect.y < this.y + this.height + epsilon; + }, + + intersect: function() { + var rect = Rectangle.read(arguments), + x1 = Math.max(this.x, rect.x), + y1 = Math.max(this.y, rect.y), + x2 = Math.min(this.x + this.width, rect.x + rect.width), + y2 = Math.min(this.y + this.height, rect.y + rect.height); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + }, + + unite: function() { + var rect = Rectangle.read(arguments), + x1 = Math.min(this.x, rect.x), + y1 = Math.min(this.y, rect.y), + x2 = Math.max(this.x + this.width, rect.x + rect.width), + y2 = Math.max(this.y + this.height, rect.y + rect.height); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + }, + + include: function() { + var point = Point.read(arguments); + var x1 = Math.min(this.x, point.x), + y1 = Math.min(this.y, point.y), + x2 = Math.max(this.x + this.width, point.x), + y2 = Math.max(this.y + this.height, point.y); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + }, + + expand: function() { + var amount = Size.read(arguments), + hor = amount.width, + ver = amount.height; + return new Rectangle(this.x - hor / 2, this.y - ver / 2, + this.width + hor, this.height + ver); + }, + + scale: function(hor, ver) { + return this.expand(this.width * hor - this.width, + this.height * (ver === undefined ? hor : ver) - this.height); + } +}, Base.each([ + ['Top', 'Left'], ['Top', 'Right'], + ['Bottom', 'Left'], ['Bottom', 'Right'], + ['Left', 'Center'], ['Top', 'Center'], + ['Right', 'Center'], ['Bottom', 'Center'] + ], + function(parts, index) { + var part = parts.join(''), + xFirst = /^[RL]/.test(part); + if (index >= 4) + parts[1] += xFirst ? 'Y' : 'X'; + var x = parts[xFirst ? 0 : 1], + y = parts[xFirst ? 1 : 0], + getX = 'get' + x, + getY = 'get' + y, + setX = 'set' + x, + setY = 'set' + y, + get = 'get' + part, + set = 'set' + part; + this[get] = function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + return new ctor(this[getX](), this[getY](), this, set); + }; + this[set] = function() { + var point = Point.read(arguments); + this[setX](point.x); + this[setY](point.y); + }; + }, { + beans: true + } +)); + +var LinkedRectangle = Rectangle.extend({ + initialize: function Rectangle(x, y, width, height, owner, setter) { + this._set(x, y, width, height, true); + this._owner = owner; + this._setter = setter; + }, + + _set: function(x, y, width, height, _dontNotify) { + this._x = x; + this._y = y; + this._width = width; + this._height = height; + if (!_dontNotify) + this._owner[this._setter](this); + return this; + } +}, +new function() { + var proto = Rectangle.prototype; + + return Base.each(['x', 'y', 'width', 'height'], function(key) { + var part = Base.capitalize(key), + internal = '_' + key; + this['get' + part] = function() { + return this[internal]; + }; + + this['set' + part] = function(value) { + this[internal] = value; + if (!this._dontNotify) + this._owner[this._setter](this); + }; + }, Base.each(['Point', 'Size', 'Center', + 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY', + 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', + 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], + function(key) { + var name = 'set' + key; + this[name] = function() { + this._dontNotify = true; + proto[name].apply(this, arguments); + this._dontNotify = false; + this._owner[this._setter](this); + }; + }, { + isSelected: function() { + return !!(this._owner._selection & 2); + }, + + setSelected: function(selected) { + var owner = this._owner; + if (owner._changeSelection) { + owner._changeSelection(2, selected); + } + } + }) + ); +}); + +var Matrix = Base.extend({ + _class: 'Matrix', + + initialize: function Matrix(arg, _dontNotify) { + var args = arguments, + count = args.length, + ok = true; + if (count >= 6) { + this._set.apply(this, args); + } else if (count === 1 || count === 2) { + if (arg instanceof Matrix) { + this._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty, + _dontNotify); + } else if (Array.isArray(arg)) { + this._set.apply(this, + _dontNotify ? arg.concat([_dontNotify]) : arg); + } else { + ok = false; + } + } else if (!count) { + this.reset(); + } else { + ok = false; + } + if (!ok) { + throw new Error('Unsupported matrix parameters'); + } + return this; + }, + + set: '#initialize', + + _set: function(a, b, c, d, tx, ty, _dontNotify) { + this._a = a; + this._b = b; + this._c = c; + this._d = d; + this._tx = tx; + this._ty = ty; + if (!_dontNotify) + this._changed(); + return this; + }, + + _serialize: function(options, dictionary) { + return Base.serialize(this.getValues(), options, true, dictionary); + }, + + _changed: function() { + var owner = this._owner; + if (owner) { + if (owner._applyMatrix) { + owner.transform(null, true); + } else { + owner._changed(25); + } + } + }, + + clone: function() { + return new Matrix(this._a, this._b, this._c, this._d, + this._tx, this._ty); + }, + + equals: function(mx) { + return mx === this || mx && this._a === mx._a && this._b === mx._b + && this._c === mx._c && this._d === mx._d + && this._tx === mx._tx && this._ty === mx._ty; + }, + + toString: function() { + var f = Formatter.instance; + return '[[' + [f.number(this._a), f.number(this._c), + f.number(this._tx)].join(', ') + '], [' + + [f.number(this._b), f.number(this._d), + f.number(this._ty)].join(', ') + ']]'; + }, + + reset: function(_dontNotify) { + this._a = this._d = 1; + this._b = this._c = this._tx = this._ty = 0; + if (!_dontNotify) + this._changed(); + return this; + }, + + apply: function(recursively, _setApplyMatrix) { + var owner = this._owner; + if (owner) { + owner.transform(null, Base.pick(recursively, true), _setApplyMatrix); + return this.isIdentity(); + } + return false; + }, + + translate: function() { + var point = Point.read(arguments), + x = point.x, + y = point.y; + this._tx += x * this._a + y * this._c; + this._ty += x * this._b + y * this._d; + this._changed(); + return this; + }, + + scale: function() { + var args = arguments, + scale = Point.read(args), + center = Point.read(args, 0, { readNull: true }); + if (center) + this.translate(center); + this._a *= scale.x; + this._b *= scale.x; + this._c *= scale.y; + this._d *= scale.y; + if (center) + this.translate(center.negate()); + this._changed(); + return this; + }, + + rotate: function(angle ) { + angle *= Math.PI / 180; + var center = Point.read(arguments, 1), + x = center.x, + y = center.y, + cos = Math.cos(angle), + sin = Math.sin(angle), + tx = x - x * cos + y * sin, + ty = y - x * sin - y * cos, + a = this._a, + b = this._b, + c = this._c, + d = this._d; + this._a = cos * a + sin * c; + this._b = cos * b + sin * d; + this._c = -sin * a + cos * c; + this._d = -sin * b + cos * d; + this._tx += tx * a + ty * c; + this._ty += tx * b + ty * d; + this._changed(); + return this; + }, + + shear: function() { + var args = arguments, + shear = Point.read(args), + center = Point.read(args, 0, { readNull: true }); + if (center) + this.translate(center); + var a = this._a, + b = this._b; + this._a += shear.y * this._c; + this._b += shear.y * this._d; + this._c += shear.x * a; + this._d += shear.x * b; + if (center) + this.translate(center.negate()); + this._changed(); + return this; + }, + + skew: function() { + var args = arguments, + skew = Point.read(args), + center = Point.read(args, 0, { readNull: true }), + toRadians = Math.PI / 180, + shear = new Point(Math.tan(skew.x * toRadians), + Math.tan(skew.y * toRadians)); + return this.shear(shear, center); + }, + + append: function(mx, _dontNotify) { + if (mx) { + var a1 = this._a, + b1 = this._b, + c1 = this._c, + d1 = this._d, + a2 = mx._a, + b2 = mx._c, + c2 = mx._b, + d2 = mx._d, + tx2 = mx._tx, + ty2 = mx._ty; + this._a = a2 * a1 + c2 * c1; + this._c = b2 * a1 + d2 * c1; + this._b = a2 * b1 + c2 * d1; + this._d = b2 * b1 + d2 * d1; + this._tx += tx2 * a1 + ty2 * c1; + this._ty += tx2 * b1 + ty2 * d1; + if (!_dontNotify) + this._changed(); + } + return this; + }, + + prepend: function(mx, _dontNotify) { + if (mx) { + var a1 = this._a, + b1 = this._b, + c1 = this._c, + d1 = this._d, + tx1 = this._tx, + ty1 = this._ty, + a2 = mx._a, + b2 = mx._c, + c2 = mx._b, + d2 = mx._d, + tx2 = mx._tx, + ty2 = mx._ty; + this._a = a2 * a1 + b2 * b1; + this._c = a2 * c1 + b2 * d1; + this._b = c2 * a1 + d2 * b1; + this._d = c2 * c1 + d2 * d1; + this._tx = a2 * tx1 + b2 * ty1 + tx2; + this._ty = c2 * tx1 + d2 * ty1 + ty2; + if (!_dontNotify) + this._changed(); + } + return this; + }, + + appended: function(mx) { + return this.clone().append(mx); + }, + + prepended: function(mx) { + return this.clone().prepend(mx); + }, + + invert: function() { + var a = this._a, + b = this._b, + c = this._c, + d = this._d, + tx = this._tx, + ty = this._ty, + det = a * d - b * c, + res = null; + if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) { + this._a = d / det; + this._b = -b / det; + this._c = -c / det; + this._d = a / det; + this._tx = (c * ty - d * tx) / det; + this._ty = (b * tx - a * ty) / det; + res = this; + } + return res; + }, + + inverted: function() { + return this.clone().invert(); + }, + + concatenate: '#append', + preConcatenate: '#prepend', + chain: '#appended', + + _shiftless: function() { + return new Matrix(this._a, this._b, this._c, this._d, 0, 0); + }, + + _orNullIfIdentity: function() { + return this.isIdentity() ? null : this; + }, + + isIdentity: function() { + return this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1 + && this._tx === 0 && this._ty === 0; + }, + + isInvertible: function() { + var det = this._a * this._d - this._c * this._b; + return det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty); + }, + + isSingular: function() { + return !this.isInvertible(); + }, + + transform: function( src, dst, count) { + return arguments.length < 3 + ? this._transformPoint(Point.read(arguments)) + : this._transformCoordinates(src, dst, count); + }, + + _transformPoint: function(point, dest, _dontNotify) { + var x = point.x, + y = point.y; + if (!dest) + dest = new Point(); + return dest._set( + x * this._a + y * this._c + this._tx, + x * this._b + y * this._d + this._ty, + _dontNotify); + }, + + _transformCoordinates: function(src, dst, count) { + for (var i = 0, max = 2 * count; i < max; i += 2) { + var x = src[i], + y = src[i + 1]; + dst[i] = x * this._a + y * this._c + this._tx; + dst[i + 1] = x * this._b + y * this._d + this._ty; + } + return dst; + }, + + _transformCorners: function(rect) { + var x1 = rect.x, + y1 = rect.y, + x2 = x1 + rect.width, + y2 = y1 + rect.height, + coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ]; + return this._transformCoordinates(coords, coords, 4); + }, + + _transformBounds: function(bounds, dest, _dontNotify) { + var coords = this._transformCorners(bounds), + min = coords.slice(0, 2), + max = min.slice(); + for (var i = 2; i < 8; i++) { + var val = coords[i], + j = i & 1; + if (val < min[j]) { + min[j] = val; + } else if (val > max[j]) { + max[j] = val; + } + } + if (!dest) + dest = new Rectangle(); + return dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1], + _dontNotify); + }, + + inverseTransform: function() { + return this._inverseTransform(Point.read(arguments)); + }, + + _inverseTransform: function(point, dest, _dontNotify) { + var a = this._a, + b = this._b, + c = this._c, + d = this._d, + tx = this._tx, + ty = this._ty, + det = a * d - b * c, + res = null; + if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) { + var x = point.x - this._tx, + y = point.y - this._ty; + if (!dest) + dest = new Point(); + res = dest._set( + (x * d - y * c) / det, + (y * a - x * b) / det, + _dontNotify); + } + return res; + }, + + decompose: function() { + var a = this._a, + b = this._b, + c = this._c, + d = this._d, + det = a * d - b * c, + sqrt = Math.sqrt, + atan2 = Math.atan2, + degrees = 180 / Math.PI, + rotate, + scale, + skew; + if (a !== 0 || b !== 0) { + var r = sqrt(a * a + b * b); + rotate = Math.acos(a / r) * (b > 0 ? 1 : -1); + scale = [r, det / r]; + skew = [atan2(a * c + b * d, r * r), 0]; + } else if (c !== 0 || d !== 0) { + var s = sqrt(c * c + d * d); + rotate = Math.asin(c / s) * (d > 0 ? 1 : -1); + scale = [det / s, s]; + skew = [0, atan2(a * c + b * d, s * s)]; + } else { + rotate = 0; + skew = scale = [0, 0]; + } + return { + translation: this.getTranslation(), + rotation: rotate * degrees, + scaling: new Point(scale), + skewing: new Point(skew[0] * degrees, skew[1] * degrees) + }; + }, + + getValues: function() { + return [ this._a, this._b, this._c, this._d, this._tx, this._ty ]; + }, + + getTranslation: function() { + return new Point(this._tx, this._ty); + }, + + getScaling: function() { + return this.decompose().scaling; + }, + + getRotation: function() { + return this.decompose().rotation; + }, + + applyToContext: function(ctx) { + if (!this.isIdentity()) { + ctx.transform(this._a, this._b, this._c, this._d, + this._tx, this._ty); + } + } +}, Base.each(['a', 'b', 'c', 'd', 'tx', 'ty'], function(key) { + var part = Base.capitalize(key), + prop = '_' + key; + this['get' + part] = function() { + return this[prop]; + }; + this['set' + part] = function(value) { + this[prop] = value; + this._changed(); + }; +}, {})); + +var Line = Base.extend({ + _class: 'Line', + + initialize: function Line(arg0, arg1, arg2, arg3, arg4) { + var asVector = false; + if (arguments.length >= 4) { + this._px = arg0; + this._py = arg1; + this._vx = arg2; + this._vy = arg3; + asVector = arg4; + } else { + this._px = arg0.x; + this._py = arg0.y; + this._vx = arg1.x; + this._vy = arg1.y; + asVector = arg2; + } + if (!asVector) { + this._vx -= this._px; + this._vy -= this._py; + } + }, + + getPoint: function() { + return new Point(this._px, this._py); + }, + + getVector: function() { + return new Point(this._vx, this._vy); + }, + + getLength: function() { + return this.getVector().getLength(); + }, + + intersect: function(line, isInfinite) { + return Line.intersect( + this._px, this._py, this._vx, this._vy, + line._px, line._py, line._vx, line._vy, + true, isInfinite); + }, + + getSide: function(point, isInfinite) { + return Line.getSide( + this._px, this._py, this._vx, this._vy, + point.x, point.y, true, isInfinite); + }, + + getDistance: function(point) { + return Math.abs(this.getSignedDistance(point)); + }, + + getSignedDistance: function(point) { + return Line.getSignedDistance(this._px, this._py, this._vx, this._vy, + point.x, point.y, true); + }, + + isCollinear: function(line) { + return Point.isCollinear(this._vx, this._vy, line._vx, line._vy); + }, + + isOrthogonal: function(line) { + return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy); + }, + + statics: { + intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector, + isInfinite) { + if (!asVector) { + v1x -= p1x; + v1y -= p1y; + v2x -= p2x; + v2y -= p2y; + } + var cross = v1x * v2y - v1y * v2x; + if (!Numerical.isMachineZero(cross)) { + var dx = p1x - p2x, + dy = p1y - p2y, + u1 = (v2x * dy - v2y * dx) / cross, + u2 = (v1x * dy - v1y * dx) / cross, + epsilon = 1e-12, + uMin = -epsilon, + uMax = 1 + epsilon; + if (isInfinite + || uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) { + if (!isInfinite) { + u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1; + } + return new Point( + p1x + u1 * v1x, + p1y + u1 * v1y); + } + } + }, + + getSide: function(px, py, vx, vy, x, y, asVector, isInfinite) { + if (!asVector) { + vx -= px; + vy -= py; + } + var v2x = x - px, + v2y = y - py, + ccw = v2x * vy - v2y * vx; + if (!isInfinite && Numerical.isMachineZero(ccw)) { + ccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy); + if (ccw >= 0 && ccw <= 1) + ccw = 0; + } + return ccw < 0 ? -1 : ccw > 0 ? 1 : 0; + }, + + getSignedDistance: function(px, py, vx, vy, x, y, asVector) { + if (!asVector) { + vx -= px; + vy -= py; + } + return vx === 0 ? (vy > 0 ? x - px : px - x) + : vy === 0 ? (vx < 0 ? y - py : py - y) + : ((x - px) * vy - (y - py) * vx) / ( + vy > vx + ? vy * Math.sqrt(1 + (vx * vx) / (vy * vy)) + : vx * Math.sqrt(1 + (vy * vy) / (vx * vx)) + ); + }, + + getDistance: function(px, py, vx, vy, x, y, asVector) { + return Math.abs( + Line.getSignedDistance(px, py, vx, vy, x, y, asVector)); + } + } +}); + +var Project = PaperScopeItem.extend({ + _class: 'Project', + _list: 'projects', + _reference: 'project', + _compactSerialize: true, + + initialize: function Project(element) { + PaperScopeItem.call(this, true); + this._children = []; + this._namedChildren = {}; + this._activeLayer = null; + this._currentStyle = new Style(null, null, this); + this._view = View.create(this, + element || CanvasProvider.getCanvas(1, 1)); + this._selectionItems = {}; + this._selectionCount = 0; + this._updateVersion = 0; + }, + + _serialize: function(options, dictionary) { + return Base.serialize(this._children, options, true, dictionary); + }, + + _changed: function(flags, item) { + if (flags & 1) { + var view = this._view; + if (view) { + view._needsUpdate = true; + if (!view._requested && view._autoUpdate) + view.requestUpdate(); + } + } + var changes = this._changes; + if (changes && item) { + var changesById = this._changesById, + id = item._id, + entry = changesById[id]; + if (entry) { + entry.flags |= flags; + } else { + changes.push(changesById[id] = { item: item, flags: flags }); + } + } + }, + + clear: function() { + var children = this._children; + for (var i = children.length - 1; i >= 0; i--) + children[i].remove(); + }, + + isEmpty: function() { + return !this._children.length; + }, + + remove: function remove() { + if (!remove.base.call(this)) + return false; + if (this._view) + this._view.remove(); + return true; + }, + + getView: function() { + return this._view; + }, + + getCurrentStyle: function() { + return this._currentStyle; + }, + + setCurrentStyle: function(style) { + this._currentStyle.set(style); + }, + + getIndex: function() { + return this._index; + }, + + getOptions: function() { + return this._scope.settings; + }, + + getLayers: function() { + return this._children; + }, + + getActiveLayer: function() { + return this._activeLayer || new Layer({ project: this, insert: true }); + }, + + getSymbolDefinitions: function() { + var definitions = [], + ids = {}; + this.getItems({ + class: SymbolItem, + match: function(item) { + var definition = item._definition, + id = definition._id; + if (!ids[id]) { + ids[id] = true; + definitions.push(definition); + } + return false; + } + }); + return definitions; + }, + + getSymbols: 'getSymbolDefinitions', + + getSelectedItems: function() { + var selectionItems = this._selectionItems, + items = []; + for (var id in selectionItems) { + var item = selectionItems[id], + selection = item._selection; + if ((selection & 1) && item.isInserted()) { + items.push(item); + } else if (!selection) { + this._updateSelection(item); + } + } + return items; + }, + + _updateSelection: function(item) { + var id = item._id, + selectionItems = this._selectionItems; + if (item._selection) { + if (selectionItems[id] !== item) { + this._selectionCount++; + selectionItems[id] = item; + } + } else if (selectionItems[id] === item) { + this._selectionCount--; + delete selectionItems[id]; + } + }, + + selectAll: function() { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + children[i].setFullySelected(true); + }, + + deselectAll: function() { + var selectionItems = this._selectionItems; + for (var i in selectionItems) + selectionItems[i].setFullySelected(false); + }, + + addLayer: function(layer) { + return this.insertLayer(undefined, layer); + }, + + insertLayer: function(index, layer) { + if (layer instanceof Layer) { + layer._remove(false, true); + Base.splice(this._children, [layer], index, 0); + layer._setProject(this, true); + var name = layer._name; + if (name) + layer.setName(name); + if (this._changes) + layer._changed(5); + if (!this._activeLayer) + this._activeLayer = layer; + } else { + layer = null; + } + return layer; + }, + + _insertItem: function(index, item, _created) { + item = this.insertLayer(index, item) + || (this._activeLayer || this._insertItem(undefined, + new Layer(Item.NO_INSERT), true)) + .insertChild(index, item); + if (_created && item.activate) + item.activate(); + return item; + }, + + getItems: function(options) { + return Item._getItems(this, options); + }, + + getItem: function(options) { + return Item._getItems(this, options, null, null, true)[0] || null; + }, + + importJSON: function(json) { + this.activate(); + var layer = this._activeLayer; + return Base.importJSON(json, layer && layer.isEmpty() && layer); + }, + + removeOn: function(type) { + var sets = this._removeSets; + if (sets) { + if (type === 'mouseup') + sets.mousedrag = null; + var set = sets[type]; + if (set) { + for (var id in set) { + var item = set[id]; + for (var key in sets) { + var other = sets[key]; + if (other && other != set) + delete other[item._id]; + } + item.remove(); + } + sets[type] = null; + } + } + }, + + draw: function(ctx, matrix, pixelRatio) { + this._updateVersion++; + ctx.save(); + matrix.applyToContext(ctx); + var children = this._children, + param = new Base({ + offset: new Point(0, 0), + pixelRatio: pixelRatio, + viewMatrix: matrix.isIdentity() ? null : matrix, + matrices: [new Matrix()], + updateMatrix: true + }); + for (var i = 0, l = children.length; i < l; i++) { + children[i].draw(ctx, param); + } + ctx.restore(); + + if (this._selectionCount > 0) { + ctx.save(); + ctx.strokeWidth = 1; + var items = this._selectionItems, + size = this._scope.settings.handleSize, + version = this._updateVersion; + for (var id in items) { + items[id]._drawSelection(ctx, matrix, size, items, version); + } + ctx.restore(); + } + } +}); + +var Item = Base.extend(Emitter, { + statics: { + extend: function extend(src) { + if (src._serializeFields) + src._serializeFields = Base.set({}, + this.prototype._serializeFields, src._serializeFields); + return extend.base.apply(this, arguments); + }, + + NO_INSERT: { insert: false } + }, + + _class: 'Item', + _name: null, + _applyMatrix: true, + _canApplyMatrix: true, + _canScaleStroke: false, + _pivot: null, + _visible: true, + _blendMode: 'normal', + _opacity: 1, + _locked: false, + _guide: false, + _clipMask: false, + _selection: 0, + _selectBounds: true, + _selectChildren: false, + _serializeFields: { + name: null, + applyMatrix: null, + matrix: new Matrix(), + pivot: null, + visible: true, + blendMode: 'normal', + opacity: 1, + locked: false, + guide: false, + clipMask: false, + selected: false, + data: {} + }, + _prioritize: ['applyMatrix'] +}, +new function() { + var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', + 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave']; + return Base.each(handlers, + function(name) { + this._events[name] = { + install: function(type) { + this.getView()._countItemEvent(type, 1); + }, + + uninstall: function(type) { + this.getView()._countItemEvent(type, -1); + } + }; + }, { + _events: { + onFrame: { + install: function() { + this.getView()._animateItem(this, true); + }, + + uninstall: function() { + this.getView()._animateItem(this, false); + } + }, + + onLoad: {}, + onError: {} + }, + statics: { + _itemHandlers: handlers + } + } + ); +}, { + initialize: function Item() { + }, + + _initialize: function(props, point) { + var hasProps = props && Base.isPlainObject(props), + internal = hasProps && props.internal === true, + matrix = this._matrix = new Matrix(), + project = hasProps && props.project || paper.project, + settings = paper.settings; + this._id = internal ? null : UID.get(); + this._parent = this._index = null; + this._applyMatrix = this._canApplyMatrix && settings.applyMatrix; + if (point) + matrix.translate(point); + matrix._owner = this; + this._style = new Style(project._currentStyle, this, project); + if (internal || hasProps && props.insert == false + || !settings.insertItems && !(hasProps && props.insert === true)) { + this._setProject(project); + } else { + (hasProps && props.parent || project) + ._insertItem(undefined, this, true); + } + if (hasProps && props !== Item.NO_INSERT) { + this.set(props, { + internal: true, insert: true, project: true, parent: true + }); + } + return hasProps; + }, + + _serialize: function(options, dictionary) { + var props = {}, + that = this; + + function serialize(fields) { + for (var key in fields) { + var value = that[key]; + if (!Base.equals(value, key === 'leading' + ? fields.fontSize * 1.2 : fields[key])) { + props[key] = Base.serialize(value, options, + key !== 'data', dictionary); + } + } + } + + serialize(this._serializeFields); + if (!(this instanceof Group)) + serialize(this._style._defaults); + return [ this._class, props ]; + }, + + _changed: function(flags) { + var symbol = this._symbol, + cacheParent = this._parent || symbol, + project = this._project; + if (flags & 8) { + this._bounds = this._position = this._decomposed = undefined; + } + if (flags & 16) { + this._globalMatrix = undefined; + } + if (cacheParent + && (flags & 72)) { + Item._clearBoundsCache(cacheParent); + } + if (flags & 2) { + Item._clearBoundsCache(this); + } + if (project) + project._changed(flags, this); + if (symbol) + symbol._changed(flags); + }, + + getId: function() { + return this._id; + }, + + getName: function() { + return this._name; + }, + + setName: function(name) { + + if (this._name) + this._removeNamed(); + if (name === (+name) + '') + throw new Error( + 'Names consisting only of numbers are not supported.'); + var owner = this._getOwner(); + if (name && owner) { + var children = owner._children, + namedChildren = owner._namedChildren; + (namedChildren[name] = namedChildren[name] || []).push(this); + if (!(name in children)) + children[name] = this; + } + this._name = name || undefined; + this._changed(256); + }, + + getStyle: function() { + return this._style; + }, + + setStyle: function(style) { + this.getStyle().set(style); + } +}, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'], + function(name) { + var part = Base.capitalize(name), + key = '_' + name, + flags = { + locked: 256, + visible: 265 + }; + this['get' + part] = function() { + return this[key]; + }; + this['set' + part] = function(value) { + if (value != this[key]) { + this[key] = value; + this._changed(flags[name] || 257); + } + }; + }, +{}), { + beans: true, + + getSelection: function() { + return this._selection; + }, + + setSelection: function(selection) { + if (selection !== this._selection) { + this._selection = selection; + var project = this._project; + if (project) { + project._updateSelection(this); + this._changed(257); + } + } + }, + + _changeSelection: function(flag, selected) { + var selection = this._selection; + this.setSelection(selected ? selection | flag : selection & ~flag); + }, + + isSelected: function() { + if (this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + if (children[i].isSelected()) + return true; + } + return !!(this._selection & 1); + }, + + setSelected: function(selected) { + if (this._selectChildren) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) + children[i].setSelected(selected); + } + this._changeSelection(1, selected); + }, + + isFullySelected: function() { + var children = this._children, + selected = !!(this._selection & 1); + if (children && selected) { + for (var i = 0, l = children.length; i < l; i++) + if (!children[i].isFullySelected()) + return false; + return true; + } + return selected; + }, + + setFullySelected: function(selected) { + var children = this._children; + if (children) { + for (var i = 0, l = children.length; i < l; i++) + children[i].setFullySelected(selected); + } + this._changeSelection(1, selected); + }, + + isClipMask: function() { + return this._clipMask; + }, + + setClipMask: function(clipMask) { + if (this._clipMask != (clipMask = !!clipMask)) { + this._clipMask = clipMask; + if (clipMask) { + this.setFillColor(null); + this.setStrokeColor(null); + } + this._changed(257); + if (this._parent) + this._parent._changed(2048); + } + }, + + getData: function() { + if (!this._data) + this._data = {}; + return this._data; + }, + + setData: function(data) { + this._data = data; + }, + + getPosition: function(_dontLink) { + var ctor = _dontLink ? Point : LinkedPoint; + var position = this._position || + (this._position = this._getPositionFromBounds()); + return new ctor(position.x, position.y, this, 'setPosition'); + }, + + setPosition: function() { + this.translate(Point.read(arguments).subtract(this.getPosition(true))); + }, + + _getPositionFromBounds: function(bounds) { + return this._pivot + ? this._matrix._transformPoint(this._pivot) + : (bounds || this.getBounds()).getCenter(true); + }, + + getPivot: function() { + var pivot = this._pivot; + return pivot + ? new LinkedPoint(pivot.x, pivot.y, this, 'setPivot') + : null; + }, + + setPivot: function() { + this._pivot = Point.read(arguments, 0, { clone: true, readNull: true }); + this._position = undefined; + } +}, Base.each({ + getStrokeBounds: { stroke: true }, + getHandleBounds: { handle: true }, + getInternalBounds: { internal: true } + }, + function(options, key) { + this[key] = function(matrix) { + return this.getBounds(matrix, options); + }; + }, +{ + beans: true, + + getBounds: function(matrix, options) { + var hasMatrix = options || matrix instanceof Matrix, + opts = Base.set({}, hasMatrix ? options : matrix, + this._boundsOptions); + if (!opts.stroke || this.getStrokeScaling()) + opts.cacheItem = this; + var rect = this._getCachedBounds(hasMatrix && matrix, opts).rect; + return !arguments.length + ? new LinkedRectangle(rect.x, rect.y, rect.width, rect.height, + this, 'setBounds') + : rect; + }, + + setBounds: function() { + var rect = Rectangle.read(arguments), + bounds = this.getBounds(), + _matrix = this._matrix, + matrix = new Matrix(), + center = rect.getCenter(); + matrix.translate(center); + if (rect.width != bounds.width || rect.height != bounds.height) { + if (!_matrix.isInvertible()) { + _matrix.set(_matrix._backup + || new Matrix().translate(_matrix.getTranslation())); + bounds = this.getBounds(); + } + matrix.scale( + bounds.width !== 0 ? rect.width / bounds.width : 0, + bounds.height !== 0 ? rect.height / bounds.height : 0); + } + center = bounds.getCenter(); + matrix.translate(-center.x, -center.y); + this.transform(matrix); + }, + + _getBounds: function(matrix, options) { + var children = this._children; + if (!children || !children.length) + return new Rectangle(); + Item._updateBoundsCache(this, options.cacheItem); + return Item._getBounds(children, matrix, options); + }, + + _getBoundsCacheKey: function(options, internal) { + return [ + options.stroke ? 1 : 0, + options.handle ? 1 : 0, + internal ? 1 : 0 + ].join(''); + }, + + _getCachedBounds: function(matrix, options, noInternal) { + matrix = matrix && matrix._orNullIfIdentity(); + var internal = options.internal && !noInternal, + cacheItem = options.cacheItem, + _matrix = internal ? null : this._matrix._orNullIfIdentity(), + cacheKey = cacheItem && (!matrix || matrix.equals(_matrix)) + && this._getBoundsCacheKey(options, internal), + bounds = this._bounds; + Item._updateBoundsCache(this._parent || this._symbol, cacheItem); + if (cacheKey && bounds && cacheKey in bounds) { + var cached = bounds[cacheKey]; + return { + rect: cached.rect.clone(), + nonscaling: cached.nonscaling + }; + } + var res = this._getBounds(matrix || _matrix, options), + rect = res.rect || res, + style = this._style, + nonscaling = res.nonscaling || style.hasStroke() + && !style.getStrokeScaling(); + if (cacheKey) { + if (!bounds) { + this._bounds = bounds = {}; + } + var cached = bounds[cacheKey] = { + rect: rect.clone(), + nonscaling: nonscaling, + internal: internal + }; + } + return { + rect: rect, + nonscaling: nonscaling + }; + }, + + _getStrokeMatrix: function(matrix, options) { + var parent = this.getStrokeScaling() ? null + : options && options.internal ? this + : this._parent || this._symbol && this._symbol._item, + mx = parent ? parent.getViewMatrix().invert() : matrix; + return mx && mx._shiftless(); + }, + + statics: { + _updateBoundsCache: function(parent, item) { + if (parent && item) { + var id = item._id, + ref = parent._boundsCache = parent._boundsCache || { + ids: {}, + list: [] + }; + if (!ref.ids[id]) { + ref.list.push(item); + ref.ids[id] = item; + } + } + }, + + _clearBoundsCache: function(item) { + var cache = item._boundsCache; + if (cache) { + item._bounds = item._position = item._boundsCache = undefined; + for (var i = 0, list = cache.list, l = list.length; i < l; i++){ + var other = list[i]; + if (other !== item) { + other._bounds = other._position = undefined; + if (other._boundsCache) + Item._clearBoundsCache(other); + } + } + } + }, + + _getBounds: function(items, matrix, options) { + var x1 = Infinity, + x2 = -x1, + y1 = x1, + y2 = x2, + nonscaling = false; + options = options || {}; + for (var i = 0, l = items.length; i < l; i++) { + var item = items[i]; + if (item._visible && !item.isEmpty(true)) { + var bounds = item._getCachedBounds( + matrix && matrix.appended(item._matrix), options, true), + rect = bounds.rect; + x1 = Math.min(rect.x, x1); + y1 = Math.min(rect.y, y1); + x2 = Math.max(rect.x + rect.width, x2); + y2 = Math.max(rect.y + rect.height, y2); + if (bounds.nonscaling) + nonscaling = true; + } + } + return { + rect: isFinite(x1) + ? new Rectangle(x1, y1, x2 - x1, y2 - y1) + : new Rectangle(), + nonscaling: nonscaling + }; + } + } + +}), { + beans: true, + + _decompose: function() { + return this._applyMatrix + ? null + : this._decomposed || (this._decomposed = this._matrix.decompose()); + }, + + getRotation: function() { + var decomposed = this._decompose(); + return decomposed ? decomposed.rotation : 0; + }, + + setRotation: function(rotation) { + var current = this.getRotation(); + if (current != null && rotation != null) { + var decomposed = this._decomposed; + this.rotate(rotation - current); + if (decomposed) { + decomposed.rotation = rotation; + this._decomposed = decomposed; + } + } + }, + + getScaling: function() { + var decomposed = this._decompose(), + s = decomposed && decomposed.scaling; + return new LinkedPoint(s ? s.x : 1, s ? s.y : 1, this, 'setScaling'); + }, + + setScaling: function() { + var current = this.getScaling(), + scaling = Point.read(arguments, 0, { clone: true, readNull: true }); + if (current && scaling && !current.equals(scaling)) { + var rotation = this.getRotation(), + decomposed = this._decomposed, + matrix = new Matrix(), + isZero = Numerical.isZero; + if (isZero(current.x) || isZero(current.y)) { + matrix.translate(decomposed.translation); + if (rotation) { + matrix.rotate(rotation); + } + matrix.scale(scaling.x, scaling.y); + this._matrix.set(matrix); + } else { + var center = this.getPosition(true); + matrix.translate(center); + if (rotation) + matrix.rotate(rotation); + matrix.scale(scaling.x / current.x, scaling.y / current.y); + if (rotation) + matrix.rotate(-rotation); + matrix.translate(center.negate()); + this.transform(matrix); + } + if (decomposed) { + decomposed.scaling = scaling; + this._decomposed = decomposed; + } + } + }, + + getMatrix: function() { + return this._matrix; + }, + + setMatrix: function() { + var matrix = this._matrix; + matrix.set.apply(matrix, arguments); + }, + + getGlobalMatrix: function(_dontClone) { + var matrix = this._globalMatrix; + if (matrix) { + var parent = this._parent; + var parents = []; + while (parent) { + if (!parent._globalMatrix) { + matrix = null; + for (var i = 0, l = parents.length; i < l; i++) { + parents[i]._globalMatrix = null; + } + break; + } + parents.push(parent); + parent = parent._parent; + } + } + if (!matrix) { + matrix = this._globalMatrix = this._matrix.clone(); + var parent = this._parent; + if (parent) + matrix.prepend(parent.getGlobalMatrix(true)); + } + return _dontClone ? matrix : matrix.clone(); + }, + + getViewMatrix: function() { + return this.getGlobalMatrix().prepend(this.getView()._matrix); + }, + + getApplyMatrix: function() { + return this._applyMatrix; + }, + + setApplyMatrix: function(apply) { + if (this._applyMatrix = this._canApplyMatrix && !!apply) + this.transform(null, true); + }, + + getTransformContent: '#getApplyMatrix', + setTransformContent: '#setApplyMatrix', +}, { + getProject: function() { + return this._project; + }, + + _setProject: function(project, installEvents) { + if (this._project !== project) { + if (this._project) + this._installEvents(false); + this._project = project; + var children = this._children; + for (var i = 0, l = children && children.length; i < l; i++) + children[i]._setProject(project); + installEvents = true; + } + if (installEvents) + this._installEvents(true); + }, + + getView: function() { + return this._project._view; + }, + + _installEvents: function _installEvents(install) { + _installEvents.base.call(this, install); + var children = this._children; + for (var i = 0, l = children && children.length; i < l; i++) + children[i]._installEvents(install); + }, + + getLayer: function() { + var parent = this; + while (parent = parent._parent) { + if (parent instanceof Layer) + return parent; + } + return null; + }, + + getParent: function() { + return this._parent; + }, + + setParent: function(item) { + return item.addChild(this); + }, + + _getOwner: '#getParent', + + getChildren: function() { + return this._children; + }, + + setChildren: function(items) { + this.removeChildren(); + this.addChildren(items); + }, + + getFirstChild: function() { + return this._children && this._children[0] || null; + }, + + getLastChild: function() { + return this._children && this._children[this._children.length - 1] + || null; + }, + + getNextSibling: function() { + var owner = this._getOwner(); + return owner && owner._children[this._index + 1] || null; + }, + + getPreviousSibling: function() { + var owner = this._getOwner(); + return owner && owner._children[this._index - 1] || null; + }, + + getIndex: function() { + return this._index; + }, + + equals: function(item) { + return item === this || item && this._class === item._class + && this._style.equals(item._style) + && this._matrix.equals(item._matrix) + && this._locked === item._locked + && this._visible === item._visible + && this._blendMode === item._blendMode + && this._opacity === item._opacity + && this._clipMask === item._clipMask + && this._guide === item._guide + && this._equals(item) + || false; + }, + + _equals: function(item) { + return Base.equals(this._children, item._children); + }, + + clone: function(options) { + var copy = new this.constructor(Item.NO_INSERT), + children = this._children, + insert = Base.pick(options ? options.insert : undefined, + options === undefined || options === true), + deep = Base.pick(options ? options.deep : undefined, true); + if (children) + copy.copyAttributes(this); + if (!children || deep) + copy.copyContent(this); + if (!children) + copy.copyAttributes(this); + if (insert) + copy.insertAbove(this); + var name = this._name, + parent = this._parent; + if (name && parent) { + var children = parent._children, + orig = name, + i = 1; + while (children[name]) + name = orig + ' ' + (i++); + if (name !== orig) + copy.setName(name); + } + return copy; + }, + + copyContent: function(source) { + var children = source._children; + for (var i = 0, l = children && children.length; i < l; i++) { + this.addChild(children[i].clone(false), true); + } + }, + + copyAttributes: function(source, excludeMatrix) { + this.setStyle(source._style); + var keys = ['_locked', '_visible', '_blendMode', '_opacity', + '_clipMask', '_guide']; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + if (source.hasOwnProperty(key)) + this[key] = source[key]; + } + if (!excludeMatrix) + this._matrix.set(source._matrix, true); + this.setApplyMatrix(source._applyMatrix); + this.setPivot(source._pivot); + this.setSelection(source._selection); + var data = source._data, + name = source._name; + this._data = data ? Base.clone(data) : null; + if (name) + this.setName(name); + }, + + rasterize: function(arg0, arg1) { + var resolution, + insert, + raster; + if (Base.isPlainObject(arg0)) { + resolution = arg0.resolution; + insert = arg0.insert; + raster = arg0.raster; + } else { + resolution = arg0; + insert = arg1; + } + if (raster) { + raster.matrix.reset(true); + } else { + raster = new Raster(Item.NO_INSERT); + } + var bounds = this.getStrokeBounds(), + scale = (resolution || this.getView().getResolution()) / 72, + topLeft = bounds.getTopLeft().floor(), + bottomRight = bounds.getBottomRight().ceil(), + boundsSize = new Size(bottomRight.subtract(topLeft)), + rasterSize = boundsSize.multiply(scale); + raster.setSize(rasterSize, true); + + if (!rasterSize.isZero()) { + var ctx = raster.getContext(true), + matrix = new Matrix().scale(scale).translate(topLeft.negate()); + ctx.save(); + matrix.applyToContext(ctx); + this.draw(ctx, new Base({ matrices: [matrix] })); + ctx.restore(); + } + raster.transform( + new Matrix() + .translate(topLeft.add(boundsSize.divide(2))) + .scale(1 / scale) + ); + if (insert === undefined || insert) { + raster.insertAbove(this); + } + return raster; + }, + + contains: function() { + var matrix = this._matrix; + return ( + matrix.isInvertible() && + !!this._contains(matrix._inverseTransform(Point.read(arguments))) + ); + }, + + _contains: function(point) { + var children = this._children; + if (children) { + for (var i = children.length - 1; i >= 0; i--) { + if (children[i].contains(point)) + return true; + } + return false; + } + return point.isInside(this.getInternalBounds()); + }, + + isInside: function() { + return Rectangle.read(arguments).contains(this.getBounds()); + }, + + _asPathItem: function() { + return new Path.Rectangle({ + rectangle: this.getInternalBounds(), + matrix: this._matrix, + insert: false, + }); + }, + + intersects: function(item, _matrix) { + if (!(item instanceof Item)) + return false; + return this._asPathItem().getIntersections(item._asPathItem(), null, + _matrix, true).length > 0; + } +}, +new function() { + function hitTest() { + var args = arguments; + return this._hitTest( + Point.read(args), + HitResult.getOptions(args)); + } + + function hitTestAll() { + var args = arguments, + point = Point.read(args), + options = HitResult.getOptions(args), + all = []; + this._hitTest(point, new Base({ all: all }, options)); + return all; + } + + function hitTestChildren(point, options, viewMatrix, _exclude) { + var children = this._children; + if (children) { + for (var i = children.length - 1; i >= 0; i--) { + var child = children[i]; + var res = child !== _exclude && child._hitTest(point, options, + viewMatrix); + if (res && !options.all) + return res; + } + } + return null; + } + + Project.inject({ + hitTest: hitTest, + hitTestAll: hitTestAll, + _hitTest: hitTestChildren + }); + + return { + hitTest: hitTest, + hitTestAll: hitTestAll, + _hitTestChildren: hitTestChildren, + }; +}, { + + _hitTest: function(point, options, parentViewMatrix) { + if (this._locked || !this._visible || this._guide && !options.guides + || this.isEmpty()) { + return null; + } + + var matrix = this._matrix, + viewMatrix = parentViewMatrix + ? parentViewMatrix.appended(matrix) + : this.getGlobalMatrix().prepend(this.getView()._matrix), + tolerance = Math.max(options.tolerance, 1e-12), + tolerancePadding = options._tolerancePadding = new Size( + Path._getStrokePadding(tolerance, + matrix._shiftless().invert())); + point = matrix._inverseTransform(point); + if (!point || !this._children && + !this.getBounds({ internal: true, stroke: true, handle: true }) + .expand(tolerancePadding.multiply(2))._containsPoint(point)) { + return null; + } + + var checkSelf = !(options.guides && !this._guide + || options.selected && !this.isSelected() + || options.type && options.type !== Base.hyphenate(this._class) + || options.class && !(this instanceof options.class)), + match = options.match, + that = this, + bounds, + res; + + function filter(hit) { + if (hit && match && !match(hit)) + hit = null; + if (hit && options.all) + options.all.push(hit); + return hit; + } + + function checkPoint(type, part) { + var pt = part ? bounds['get' + part]() : that.getPosition(); + if (point.subtract(pt).divide(tolerancePadding).length <= 1) { + return new HitResult(type, that, { + name: part ? Base.hyphenate(part) : type, + point: pt + }); + } + } + + var checkPosition = options.position, + checkCenter = options.center, + checkBounds = options.bounds; + if (checkSelf && this._parent + && (checkPosition || checkCenter || checkBounds)) { + if (checkCenter || checkBounds) { + bounds = this.getInternalBounds(); + } + res = checkPosition && checkPoint('position') || + checkCenter && checkPoint('center', 'Center'); + if (!res && checkBounds) { + var points = [ + 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', + 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter' + ]; + for (var i = 0; i < 8 && !res; i++) { + res = checkPoint('bounds', points[i]); + } + } + res = filter(res); + } + + if (!res) { + res = this._hitTestChildren(point, options, viewMatrix) + || checkSelf + && filter(this._hitTestSelf(point, options, viewMatrix, + this.getStrokeScaling() ? null + : viewMatrix._shiftless().invert())) + || null; + } + if (res && res.point) { + res.point = matrix.transform(res.point); + } + return res; + }, + + _hitTestSelf: function(point, options) { + if (options.fill && this.hasFill() && this._contains(point)) + return new HitResult('fill', this); + }, + + matches: function(name, compare) { + function matchObject(obj1, obj2) { + for (var i in obj1) { + if (obj1.hasOwnProperty(i)) { + var val1 = obj1[i], + val2 = obj2[i]; + if (Base.isPlainObject(val1) && Base.isPlainObject(val2)) { + if (!matchObject(val1, val2)) + return false; + } else if (!Base.equals(val1, val2)) { + return false; + } + } + } + return true; + } + var type = typeof name; + if (type === 'object') { + for (var key in name) { + if (name.hasOwnProperty(key) && !this.matches(key, name[key])) + return false; + } + return true; + } else if (type === 'function') { + return name(this); + } else if (name === 'match') { + return compare(this); + } else { + var value = /^(empty|editable)$/.test(name) + ? this['is' + Base.capitalize(name)]() + : name === 'type' + ? Base.hyphenate(this._class) + : this[name]; + if (name === 'class') { + if (typeof compare === 'function') + return this instanceof compare; + value = this._class; + } + if (typeof compare === 'function') { + return !!compare(value); + } else if (compare) { + if (compare.test) { + return compare.test(value); + } else if (Base.isPlainObject(compare)) { + return matchObject(compare, value); + } + } + return Base.equals(value, compare); + } + }, + + getItems: function(options) { + return Item._getItems(this, options, this._matrix); + }, + + getItem: function(options) { + return Item._getItems(this, options, this._matrix, null, true)[0] + || null; + }, + + statics: { + _getItems: function _getItems(item, options, matrix, param, firstOnly) { + if (!param) { + var obj = typeof options === 'object' && options, + overlapping = obj && obj.overlapping, + inside = obj && obj.inside, + bounds = overlapping || inside, + rect = bounds && Rectangle.read([bounds]); + param = { + items: [], + recursive: obj && obj.recursive !== false, + inside: !!inside, + overlapping: !!overlapping, + rect: rect, + path: overlapping && new Path.Rectangle({ + rectangle: rect, + insert: false + }) + }; + if (obj) { + options = Base.filter({}, options, { + recursive: true, inside: true, overlapping: true + }); + } + } + var children = item._children, + items = param.items, + rect = param.rect; + matrix = rect && (matrix || new Matrix()); + for (var i = 0, l = children && children.length; i < l; i++) { + var child = children[i], + childMatrix = matrix && matrix.appended(child._matrix), + add = true; + if (rect) { + var bounds = child.getBounds(childMatrix); + if (!rect.intersects(bounds)) + continue; + if (!(rect.contains(bounds) + || param.overlapping && (bounds.contains(rect) + || param.path.intersects(child, childMatrix)))) + add = false; + } + if (add && child.matches(options)) { + items.push(child); + if (firstOnly) + break; + } + if (param.recursive !== false) { + _getItems(child, options, childMatrix, param, firstOnly); + } + if (firstOnly && items.length > 0) + break; + } + return items; + } + } +}, { + + importJSON: function(json) { + var res = Base.importJSON(json, this); + return res !== this ? this.addChild(res) : res; + }, + + addChild: function(item) { + return this.insertChild(undefined, item); + }, + + insertChild: function(index, item) { + var res = item ? this.insertChildren(index, [item]) : null; + return res && res[0]; + }, + + addChildren: function(items) { + return this.insertChildren(this._children.length, items); + }, + + insertChildren: function(index, items) { + var children = this._children; + if (children && items && items.length > 0) { + items = Base.slice(items); + var inserted = {}; + for (var i = items.length - 1; i >= 0; i--) { + var item = items[i], + id = item && item._id; + if (!item || inserted[id]) { + items.splice(i, 1); + } else { + item._remove(false, true); + inserted[id] = true; + } + } + Base.splice(children, items, index, 0); + var project = this._project, + notifySelf = project._changes; + for (var i = 0, l = items.length; i < l; i++) { + var item = items[i], + name = item._name; + item._parent = this; + item._setProject(project, true); + if (name) + item.setName(name); + if (notifySelf) + item._changed(5); + } + this._changed(11); + } else { + items = null; + } + return items; + }, + + _insertItem: '#insertChild', + + _insertAt: function(item, offset) { + var owner = item && item._getOwner(), + res = item !== this && owner ? this : null; + if (res) { + res._remove(false, true); + owner._insertItem(item._index + offset, res); + } + return res; + }, + + insertAbove: function(item) { + return this._insertAt(item, 1); + }, + + insertBelow: function(item) { + return this._insertAt(item, 0); + }, + + sendToBack: function() { + var owner = this._getOwner(); + return owner ? owner._insertItem(0, this) : null; + }, + + bringToFront: function() { + var owner = this._getOwner(); + return owner ? owner._insertItem(undefined, this) : null; + }, + + appendTop: '#addChild', + + appendBottom: function(item) { + return this.insertChild(0, item); + }, + + moveAbove: '#insertAbove', + + moveBelow: '#insertBelow', + + addTo: function(owner) { + return owner._insertItem(undefined, this); + }, + + copyTo: function(owner) { + return this.clone(false).addTo(owner); + }, + + reduce: function(options) { + var children = this._children; + if (children && children.length === 1) { + var child = children[0].reduce(options); + if (this._parent) { + child.insertAbove(this); + this.remove(); + } else { + child.remove(); + } + return child; + } + return this; + }, + + _removeNamed: function() { + var owner = this._getOwner(); + if (owner) { + var children = owner._children, + namedChildren = owner._namedChildren, + name = this._name, + namedArray = namedChildren[name], + index = namedArray ? namedArray.indexOf(this) : -1; + if (index !== -1) { + if (children[name] == this) + delete children[name]; + namedArray.splice(index, 1); + if (namedArray.length) { + children[name] = namedArray[0]; + } else { + delete namedChildren[name]; + } + } + } + }, + + _remove: function(notifySelf, notifyParent) { + var owner = this._getOwner(), + project = this._project, + index = this._index; + if (this._style) + this._style._dispose(); + if (owner) { + if (this._name) + this._removeNamed(); + if (index != null) { + if (project._activeLayer === this) + project._activeLayer = this.getNextSibling() + || this.getPreviousSibling(); + Base.splice(owner._children, null, index, 1); + } + this._installEvents(false); + if (notifySelf && project._changes) + this._changed(5); + if (notifyParent) + owner._changed(11, this); + this._parent = null; + return true; + } + return false; + }, + + remove: function() { + return this._remove(true, true); + }, + + replaceWith: function(item) { + var ok = item && item.insertBelow(this); + if (ok) + this.remove(); + return ok; + }, + + removeChildren: function(start, end) { + if (!this._children) + return null; + start = start || 0; + end = Base.pick(end, this._children.length); + var removed = Base.splice(this._children, null, start, end - start); + for (var i = removed.length - 1; i >= 0; i--) { + removed[i]._remove(true, false); + } + if (removed.length > 0) + this._changed(11); + return removed; + }, + + clear: '#removeChildren', + + reverseChildren: function() { + if (this._children) { + this._children.reverse(); + for (var i = 0, l = this._children.length; i < l; i++) + this._children[i]._index = i; + this._changed(11); + } + }, + + isEmpty: function(recursively) { + var children = this._children; + var numChildren = children ? children.length : 0; + if (recursively) { + for (var i = 0; i < numChildren; i++) { + if (!children[i].isEmpty(recursively)) { + return false; + } + } + return true; + } + return !numChildren; + }, + + isEditable: function() { + var item = this; + while (item) { + if (!item._visible || item._locked) + return false; + item = item._parent; + } + return true; + }, + + hasFill: function() { + return this.getStyle().hasFill(); + }, + + hasStroke: function() { + return this.getStyle().hasStroke(); + }, + + hasShadow: function() { + return this.getStyle().hasShadow(); + }, + + _getOrder: function(item) { + function getList(item) { + var list = []; + do { + list.unshift(item); + } while (item = item._parent); + return list; + } + var list1 = getList(this), + list2 = getList(item); + for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) { + if (list1[i] != list2[i]) { + return list1[i]._index < list2[i]._index ? 1 : -1; + } + } + return 0; + }, + + hasChildren: function() { + return this._children && this._children.length > 0; + }, + + isInserted: function() { + return this._parent ? this._parent.isInserted() : false; + }, + + isAbove: function(item) { + return this._getOrder(item) === -1; + }, + + isBelow: function(item) { + return this._getOrder(item) === 1; + }, + + isParent: function(item) { + return this._parent === item; + }, + + isChild: function(item) { + return item && item._parent === this; + }, + + isDescendant: function(item) { + var parent = this; + while (parent = parent._parent) { + if (parent === item) + return true; + } + return false; + }, + + isAncestor: function(item) { + return item ? item.isDescendant(this) : false; + }, + + isSibling: function(item) { + return this._parent === item._parent; + }, + + isGroupedWith: function(item) { + var parent = this._parent; + while (parent) { + if (parent._parent + && /^(Group|Layer|CompoundPath)$/.test(parent._class) + && item.isDescendant(parent)) + return true; + parent = parent._parent; + } + return false; + }, + +}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { + var rotate = key === 'rotate'; + this[key] = function() { + var args = arguments, + value = (rotate ? Base : Point).read(args), + center = Point.read(args, 0, { readNull: true }); + return this.transform(new Matrix()[key](value, + center || this.getPosition(true))); + }; +}, { + translate: function() { + var mx = new Matrix(); + return this.transform(mx.translate.apply(mx, arguments)); + }, + + transform: function(matrix, _applyRecursively, _setApplyMatrix) { + var _matrix = this._matrix, + transformMatrix = matrix && !matrix.isIdentity(), + applyMatrix = ( + _setApplyMatrix && this._canApplyMatrix || + this._applyMatrix && ( + transformMatrix || !_matrix.isIdentity() || + _applyRecursively && this._children + ) + ); + if (!transformMatrix && !applyMatrix) + return this; + if (transformMatrix) { + if (!matrix.isInvertible() && _matrix.isInvertible()) + _matrix._backup = _matrix.getValues(); + _matrix.prepend(matrix, true); + var style = this._style, + fillColor = style.getFillColor(true), + strokeColor = style.getStrokeColor(true); + if (fillColor) + fillColor.transform(matrix); + if (strokeColor) + strokeColor.transform(matrix); + } + + if (applyMatrix && (applyMatrix = this._transformContent( + _matrix, _applyRecursively, _setApplyMatrix))) { + var pivot = this._pivot; + if (pivot) + _matrix._transformPoint(pivot, pivot, true); + _matrix.reset(true); + if (_setApplyMatrix && this._canApplyMatrix) + this._applyMatrix = true; + } + var bounds = this._bounds, + position = this._position; + if (transformMatrix || applyMatrix) { + this._changed(25); + } + var decomp = transformMatrix && bounds && matrix.decompose(); + if (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) { + for (var key in bounds) { + var cache = bounds[key]; + if (cache.nonscaling) { + delete bounds[key]; + } else if (applyMatrix || !cache.internal) { + var rect = cache.rect; + matrix._transformBounds(rect, rect); + } + } + this._bounds = bounds; + var cached = bounds[this._getBoundsCacheKey( + this._boundsOptions || {})]; + if (cached) { + this._position = this._getPositionFromBounds(cached.rect); + } + } else if (transformMatrix && position && this._pivot) { + this._position = matrix._transformPoint(position, position); + } + return this; + }, + + _transformContent: function(matrix, applyRecursively, setApplyMatrix) { + var children = this._children; + if (children) { + for (var i = 0, l = children.length; i < l; i++) { + children[i].transform(matrix, applyRecursively, setApplyMatrix); + } + return true; + } + }, + + globalToLocal: function() { + return this.getGlobalMatrix(true)._inverseTransform( + Point.read(arguments)); + }, + + localToGlobal: function() { + return this.getGlobalMatrix(true)._transformPoint( + Point.read(arguments)); + }, + + parentToLocal: function() { + return this._matrix._inverseTransform(Point.read(arguments)); + }, + + localToParent: function() { + return this._matrix._transformPoint(Point.read(arguments)); + }, + + fitBounds: function(rectangle, fill) { + rectangle = Rectangle.read(arguments); + var bounds = this.getBounds(), + itemRatio = bounds.height / bounds.width, + rectRatio = rectangle.height / rectangle.width, + scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio) + ? rectangle.width / bounds.width + : rectangle.height / bounds.height, + newBounds = new Rectangle(new Point(), + new Size(bounds.width * scale, bounds.height * scale)); + newBounds.setCenter(rectangle.getCenter()); + this.setBounds(newBounds); + } +}), { + + _setStyles: function(ctx, param, viewMatrix) { + var style = this._style, + matrix = this._matrix; + if (style.hasFill()) { + ctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix); + } + if (style.hasStroke()) { + ctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix); + ctx.lineWidth = style.getStrokeWidth(); + var strokeJoin = style.getStrokeJoin(), + strokeCap = style.getStrokeCap(), + miterLimit = style.getMiterLimit(); + if (strokeJoin) + ctx.lineJoin = strokeJoin; + if (strokeCap) + ctx.lineCap = strokeCap; + if (miterLimit) + ctx.miterLimit = miterLimit; + if (paper.support.nativeDash) { + var dashArray = style.getDashArray(), + dashOffset = style.getDashOffset(); + if (dashArray && dashArray.length) { + if ('setLineDash' in ctx) { + ctx.setLineDash(dashArray); + ctx.lineDashOffset = dashOffset; + } else { + ctx.mozDash = dashArray; + ctx.mozDashOffset = dashOffset; + } + } + } + } + if (style.hasShadow()) { + var pixelRatio = param.pixelRatio || 1, + mx = viewMatrix._shiftless().prepend( + new Matrix().scale(pixelRatio, pixelRatio)), + blur = mx.transform(new Point(style.getShadowBlur(), 0)), + offset = mx.transform(this.getShadowOffset()); + ctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx); + ctx.shadowBlur = blur.getLength(); + ctx.shadowOffsetX = offset.x; + ctx.shadowOffsetY = offset.y; + } + }, + + draw: function(ctx, param, parentStrokeMatrix) { + var updateVersion = this._updateVersion = this._project._updateVersion; + if (!this._visible || this._opacity === 0) + return; + var matrices = param.matrices, + viewMatrix = param.viewMatrix, + matrix = this._matrix, + globalMatrix = matrices[matrices.length - 1].appended(matrix); + if (!globalMatrix.isInvertible()) + return; + + viewMatrix = viewMatrix ? viewMatrix.appended(globalMatrix) + : globalMatrix; + + matrices.push(globalMatrix); + if (param.updateMatrix) { + this._globalMatrix = globalMatrix; + } + + var blendMode = this._blendMode, + opacity = Numerical.clamp(this._opacity, 0, 1), + normalBlend = blendMode === 'normal', + nativeBlend = BlendMode.nativeModes[blendMode], + direct = normalBlend && opacity === 1 + || param.dontStart + || param.clip + || (nativeBlend || normalBlend && opacity < 1) + && this._canComposite(), + pixelRatio = param.pixelRatio || 1, + mainCtx, itemOffset, prevOffset; + if (!direct) { + var bounds = this.getStrokeBounds(viewMatrix); + if (!bounds.width || !bounds.height) { + matrices.pop(); + return; + } + prevOffset = param.offset; + itemOffset = param.offset = bounds.getTopLeft().floor(); + mainCtx = ctx; + ctx = CanvasProvider.getContext(bounds.getSize().ceil().add(1) + .multiply(pixelRatio)); + if (pixelRatio !== 1) + ctx.scale(pixelRatio, pixelRatio); + } + ctx.save(); + var strokeMatrix = parentStrokeMatrix + ? parentStrokeMatrix.appended(matrix) + : this._canScaleStroke && !this.getStrokeScaling(true) + && viewMatrix, + clip = !direct && param.clipItem, + transform = !strokeMatrix || clip; + if (direct) { + ctx.globalAlpha = opacity; + if (nativeBlend) + ctx.globalCompositeOperation = blendMode; + } else if (transform) { + ctx.translate(-itemOffset.x, -itemOffset.y); + } + if (transform) { + (direct ? matrix : viewMatrix).applyToContext(ctx); + } + if (clip) { + param.clipItem.draw(ctx, param.extend({ clip: true })); + } + if (strokeMatrix) { + ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); + var offset = param.offset; + if (offset) + ctx.translate(-offset.x, -offset.y); + } + this._draw(ctx, param, viewMatrix, strokeMatrix); + ctx.restore(); + matrices.pop(); + if (param.clip && !param.dontFinish) { + ctx.clip(this.getFillRule()); + } + if (!direct) { + BlendMode.process(blendMode, ctx, mainCtx, opacity, + itemOffset.subtract(prevOffset).multiply(pixelRatio)); + CanvasProvider.release(ctx); + param.offset = prevOffset; + } + }, + + _isUpdated: function(updateVersion) { + var parent = this._parent; + if (parent instanceof CompoundPath) + return parent._isUpdated(updateVersion); + var updated = this._updateVersion === updateVersion; + if (!updated && parent && parent._visible + && parent._isUpdated(updateVersion)) { + this._updateVersion = updateVersion; + updated = true; + } + return updated; + }, + + _drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) { + var selection = this._selection, + itemSelected = selection & 1, + boundsSelected = selection & 2 + || itemSelected && this._selectBounds, + positionSelected = selection & 4; + if (!this._drawSelected) + itemSelected = false; + if ((itemSelected || boundsSelected || positionSelected) + && this._isUpdated(updateVersion)) { + var layer, + color = this.getSelectedColor(true) || (layer = this.getLayer()) + && layer.getSelectedColor(true), + mx = matrix.appended(this.getGlobalMatrix(true)), + half = size / 2; + ctx.strokeStyle = ctx.fillStyle = color + ? color.toCanvasStyle(ctx) : '#009dec'; + if (itemSelected) + this._drawSelected(ctx, mx, selectionItems); + if (positionSelected) { + var pos = this.getPosition(true), + parent = this._parent, + point = parent ? parent.localToGlobal(pos) : pos, + x = point.x, + y = point.y; + ctx.beginPath(); + ctx.arc(x, y, half, 0, Math.PI * 2, true); + ctx.stroke(); + var deltas = [[0, -1], [1, 0], [0, 1], [-1, 0]], + start = half, + end = size + 1; + for (var i = 0; i < 4; i++) { + var delta = deltas[i], + dx = delta[0], + dy = delta[1]; + ctx.moveTo(x + dx * start, y + dy * start); + ctx.lineTo(x + dx * end, y + dy * end); + ctx.stroke(); + } + } + if (boundsSelected) { + var coords = mx._transformCorners(this.getInternalBounds()); + ctx.beginPath(); + for (var i = 0; i < 8; i++) { + ctx[!i ? 'moveTo' : 'lineTo'](coords[i], coords[++i]); + } + ctx.closePath(); + ctx.stroke(); + for (var i = 0; i < 8; i++) { + ctx.fillRect(coords[i] - half, coords[++i] - half, + size, size); + } + } + } + }, + + _canComposite: function() { + return false; + } +}, Base.each(['down', 'drag', 'up', 'move'], function(key) { + this['removeOn' + Base.capitalize(key)] = function() { + var hash = {}; + hash[key] = true; + return this.removeOn(hash); + }; +}, { + + removeOn: function(obj) { + for (var name in obj) { + if (obj[name]) { + var key = 'mouse' + name, + project = this._project, + sets = project._removeSets = project._removeSets || {}; + sets[key] = sets[key] || {}; + sets[key][this._id] = this; + } + } + return this; + } +}), { + tween: function(from, to, options) { + if (!options) { + options = to; + to = from; + from = null; + if (!options) { + options = to; + to = null; + } + } + var easing = options && options.easing, + start = options && options.start, + duration = options != null && ( + typeof options === 'number' ? options : options.duration + ), + tween = new Tween(this, from, to, duration, easing, start); + function onFrame(event) { + tween._handleFrame(event.time * 1000); + if (!tween.running) { + this.off('frame', onFrame); + } + } + if (duration) { + this.on('frame', onFrame); + } + return tween; + }, + + tweenTo: function(to, options) { + return this.tween(null, to, options); + }, + + tweenFrom: function(from, options) { + return this.tween(from, null, options); + } +}); + +var Group = Item.extend({ + _class: 'Group', + _selectBounds: false, + _selectChildren: true, + _serializeFields: { + children: [] + }, + + initialize: function Group(arg) { + this._children = []; + this._namedChildren = {}; + if (!this._initialize(arg)) + this.addChildren(Array.isArray(arg) ? arg : arguments); + }, + + _changed: function _changed(flags) { + _changed.base.call(this, flags); + if (flags & 2050) { + this._clipItem = undefined; + } + }, + + _getClipItem: function() { + var clipItem = this._clipItem; + if (clipItem === undefined) { + clipItem = null; + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + if (children[i]._clipMask) { + clipItem = children[i]; + break; + } + } + this._clipItem = clipItem; + } + return clipItem; + }, + + isClipped: function() { + return !!this._getClipItem(); + }, + + setClipped: function(clipped) { + var child = this.getFirstChild(); + if (child) + child.setClipMask(clipped); + }, + + _getBounds: function _getBounds(matrix, options) { + var clipItem = this._getClipItem(); + return clipItem + ? clipItem._getCachedBounds(clipItem._matrix.prepended(matrix), + Base.set({}, options, { stroke: false })) + : _getBounds.base.call(this, matrix, options); + }, + + _hitTestChildren: function _hitTestChildren(point, options, viewMatrix) { + var clipItem = this._getClipItem(); + return (!clipItem || clipItem.contains(point)) + && _hitTestChildren.base.call(this, point, options, viewMatrix, + clipItem); + }, + + _draw: function(ctx, param) { + var clip = param.clip, + clipItem = !clip && this._getClipItem(); + param = param.extend({ clipItem: clipItem, clip: false }); + if (clip) { + ctx.beginPath(); + param.dontStart = param.dontFinish = true; + } else if (clipItem) { + clipItem.draw(ctx, param.extend({ clip: true })); + } + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + var item = children[i]; + if (item !== clipItem) + item.draw(ctx, param); + } + } +}); + +var Layer = Group.extend({ + _class: 'Layer', + + initialize: function Layer() { + Group.apply(this, arguments); + }, + + _getOwner: function() { + return this._parent || this._index != null && this._project; + }, + + isInserted: function isInserted() { + return this._parent ? isInserted.base.call(this) : this._index != null; + }, + + activate: function() { + this._project._activeLayer = this; + }, + + _hitTestSelf: function() { + } +}); + +var Shape = Item.extend({ + _class: 'Shape', + _applyMatrix: false, + _canApplyMatrix: false, + _canScaleStroke: true, + _serializeFields: { + type: null, + size: null, + radius: null + }, + + initialize: function Shape(props, point) { + this._initialize(props, point); + }, + + _equals: function(item) { + return this._type === item._type + && this._size.equals(item._size) + && Base.equals(this._radius, item._radius); + }, + + copyContent: function(source) { + this.setType(source._type); + this.setSize(source._size); + this.setRadius(source._radius); + }, + + getType: function() { + return this._type; + }, + + setType: function(type) { + this._type = type; + }, + + getShape: '#getType', + setShape: '#setType', + + getSize: function() { + var size = this._size; + return new LinkedSize(size.width, size.height, this, 'setSize'); + }, + + setSize: function() { + var size = Size.read(arguments); + if (!this._size) { + this._size = size.clone(); + } else if (!this._size.equals(size)) { + var type = this._type, + width = size.width, + height = size.height; + if (type === 'rectangle') { + this._radius.set(Size.min(this._radius, size.divide(2).abs())); + } else if (type === 'circle') { + width = height = (width + height) / 2; + this._radius = width / 2; + } else if (type === 'ellipse') { + this._radius._set(width / 2, height / 2); + } + this._size._set(width, height); + this._changed(9); + } + }, + + getRadius: function() { + var rad = this._radius; + return this._type === 'circle' + ? rad + : new LinkedSize(rad.width, rad.height, this, 'setRadius'); + }, + + setRadius: function(radius) { + var type = this._type; + if (type === 'circle') { + if (radius === this._radius) + return; + var size = radius * 2; + this._radius = radius; + this._size._set(size, size); + } else { + radius = Size.read(arguments); + if (!this._radius) { + this._radius = radius.clone(); + } else { + if (this._radius.equals(radius)) + return; + this._radius.set(radius); + if (type === 'rectangle') { + var size = Size.max(this._size, radius.multiply(2)); + this._size.set(size); + } else if (type === 'ellipse') { + this._size._set(radius.width * 2, radius.height * 2); + } + } + } + this._changed(9); + }, + + isEmpty: function() { + return false; + }, + + toPath: function(insert) { + var path = new Path[Base.capitalize(this._type)]({ + center: new Point(), + size: this._size, + radius: this._radius, + insert: false + }); + path.copyAttributes(this); + if (paper.settings.applyMatrix) + path.setApplyMatrix(true); + if (insert === undefined || insert) + path.insertAbove(this); + return path; + }, + + toShape: '#clone', + + _asPathItem: function() { + return this.toPath(false); + }, + + _draw: function(ctx, param, viewMatrix, strokeMatrix) { + var style = this._style, + hasFill = style.hasFill(), + hasStroke = style.hasStroke(), + dontPaint = param.dontFinish || param.clip, + untransformed = !strokeMatrix; + if (hasFill || hasStroke || dontPaint) { + var type = this._type, + radius = this._radius, + isCircle = type === 'circle'; + if (!param.dontStart) + ctx.beginPath(); + if (untransformed && isCircle) { + ctx.arc(0, 0, radius, 0, Math.PI * 2, true); + } else { + var rx = isCircle ? radius : radius.width, + ry = isCircle ? radius : radius.height, + size = this._size, + width = size.width, + height = size.height; + if (untransformed && type === 'rectangle' && rx === 0 && ry === 0) { + ctx.rect(-width / 2, -height / 2, width, height); + } else { + var x = width / 2, + y = height / 2, + kappa = 1 - 0.5522847498307936, + cx = rx * kappa, + cy = ry * kappa, + c = [ + -x, -y + ry, + -x, -y + cy, + -x + cx, -y, + -x + rx, -y, + x - rx, -y, + x - cx, -y, + x, -y + cy, + x, -y + ry, + x, y - ry, + x, y - cy, + x - cx, y, + x - rx, y, + -x + rx, y, + -x + cx, y, + -x, y - cy, + -x, y - ry + ]; + if (strokeMatrix) + strokeMatrix.transform(c, c, 32); + ctx.moveTo(c[0], c[1]); + ctx.bezierCurveTo(c[2], c[3], c[4], c[5], c[6], c[7]); + if (x !== rx) + ctx.lineTo(c[8], c[9]); + ctx.bezierCurveTo(c[10], c[11], c[12], c[13], c[14], c[15]); + if (y !== ry) + ctx.lineTo(c[16], c[17]); + ctx.bezierCurveTo(c[18], c[19], c[20], c[21], c[22], c[23]); + if (x !== rx) + ctx.lineTo(c[24], c[25]); + ctx.bezierCurveTo(c[26], c[27], c[28], c[29], c[30], c[31]); + } + } + ctx.closePath(); + } + if (!dontPaint && (hasFill || hasStroke)) { + this._setStyles(ctx, param, viewMatrix); + if (hasFill) { + ctx.fill(style.getFillRule()); + ctx.shadowColor = 'rgba(0,0,0,0)'; + } + if (hasStroke) + ctx.stroke(); + } + }, + + _canComposite: function() { + return !(this.hasFill() && this.hasStroke()); + }, + + _getBounds: function(matrix, options) { + var rect = new Rectangle(this._size).setCenter(0, 0), + style = this._style, + strokeWidth = options.stroke && style.hasStroke() + && style.getStrokeWidth(); + if (matrix) + rect = matrix._transformBounds(rect); + return strokeWidth + ? rect.expand(Path._getStrokePadding(strokeWidth, + this._getStrokeMatrix(matrix, options))) + : rect; + } +}, +new function() { + function getCornerCenter(that, point, expand) { + var radius = that._radius; + if (!radius.isZero()) { + var halfSize = that._size.divide(2); + for (var q = 1; q <= 4; q++) { + var dir = new Point(q > 1 && q < 4 ? -1 : 1, q > 2 ? -1 : 1), + corner = dir.multiply(halfSize), + center = corner.subtract(dir.multiply(radius)), + rect = new Rectangle( + expand ? corner.add(dir.multiply(expand)) : corner, + center); + if (rect.contains(point)) + return { point: center, quadrant: q }; + } + } + } + + function isOnEllipseStroke(point, radius, padding, quadrant) { + var vector = point.divide(radius); + return (!quadrant || vector.isInQuadrant(quadrant)) && + vector.subtract(vector.normalize()).multiply(radius) + .divide(padding).length <= 1; + } + + return { + _contains: function _contains(point) { + if (this._type === 'rectangle') { + var center = getCornerCenter(this, point); + return center + ? point.subtract(center.point).divide(this._radius) + .getLength() <= 1 + : _contains.base.call(this, point); + } else { + return point.divide(this.size).getLength() <= 0.5; + } + }, + + _hitTestSelf: function _hitTestSelf(point, options, viewMatrix, + strokeMatrix) { + var hit = false, + style = this._style, + hitStroke = options.stroke && style.hasStroke(), + hitFill = options.fill && style.hasFill(); + if (hitStroke || hitFill) { + var type = this._type, + radius = this._radius, + strokeRadius = hitStroke ? style.getStrokeWidth() / 2 : 0, + strokePadding = options._tolerancePadding.add( + Path._getStrokePadding(strokeRadius, + !style.getStrokeScaling() && strokeMatrix)); + if (type === 'rectangle') { + var padding = strokePadding.multiply(2), + center = getCornerCenter(this, point, padding); + if (center) { + hit = isOnEllipseStroke(point.subtract(center.point), + radius, strokePadding, center.quadrant); + } else { + var rect = new Rectangle(this._size).setCenter(0, 0), + outer = rect.expand(padding), + inner = rect.expand(padding.negate()); + hit = outer._containsPoint(point) + && !inner._containsPoint(point); + } + } else { + hit = isOnEllipseStroke(point, radius, strokePadding); + } + } + return hit ? new HitResult(hitStroke ? 'stroke' : 'fill', this) + : _hitTestSelf.base.apply(this, arguments); + } + }; +}, { + +statics: new function() { + function createShape(type, point, size, radius, args) { + var item = Base.create(Shape.prototype); + item._type = type; + item._size = size; + item._radius = radius; + item._initialize(Base.getNamed(args), point); + return item; + } + + return { + Circle: function() { + var args = arguments, + center = Point.readNamed(args, 'center'), + radius = Base.readNamed(args, 'radius'); + return createShape('circle', center, new Size(radius * 2), radius, + args); + }, + + Rectangle: function() { + var args = arguments, + rect = Rectangle.readNamed(args, 'rectangle'), + radius = Size.min(Size.readNamed(args, 'radius'), + rect.getSize(true).divide(2)); + return createShape('rectangle', rect.getCenter(true), + rect.getSize(true), radius, args); + }, + + Ellipse: function() { + var args = arguments, + ellipse = Shape._readEllipse(args), + radius = ellipse.radius; + return createShape('ellipse', ellipse.center, radius.multiply(2), + radius, args); + }, + + _readEllipse: function(args) { + var center, + radius; + if (Base.hasNamed(args, 'radius')) { + center = Point.readNamed(args, 'center'); + radius = Size.readNamed(args, 'radius'); + } else { + var rect = Rectangle.readNamed(args, 'rectangle'); + center = rect.getCenter(true); + radius = rect.getSize(true).divide(2); + } + return { center: center, radius: radius }; + } + }; +}}); + +var Raster = Item.extend({ + _class: 'Raster', + _applyMatrix: false, + _canApplyMatrix: false, + _boundsOptions: { stroke: false, handle: false }, + _serializeFields: { + crossOrigin: null, + source: null + }, + _prioritize: ['crossOrigin'], + _smoothing: 'low', + beans: true, + + initialize: function Raster(source, position) { + if (!this._initialize(source, + position !== undefined && Point.read(arguments))) { + var image, + type = typeof source, + object = type === 'string' + ? document.getElementById(source) + : type === 'object' + ? source + : null; + if (object && object !== Item.NO_INSERT) { + if (object.getContext || object.naturalHeight != null) { + image = object; + } else if (object) { + var size = Size.read(arguments); + if (!size.isZero()) { + image = CanvasProvider.getCanvas(size); + } + } + } + if (image) { + this.setImage(image); + } else { + this.setSource(source); + } + } + if (!this._size) { + this._size = new Size(); + this._loaded = false; + } + }, + + _equals: function(item) { + return this.getSource() === item.getSource(); + }, + + copyContent: function(source) { + var image = source._image, + canvas = source._canvas; + if (image) { + this._setImage(image); + } else if (canvas) { + var copyCanvas = CanvasProvider.getCanvas(source._size); + copyCanvas.getContext('2d').drawImage(canvas, 0, 0); + this._setImage(copyCanvas); + } + this._crossOrigin = source._crossOrigin; + }, + + getSize: function() { + var size = this._size; + return new LinkedSize(size ? size.width : 0, size ? size.height : 0, + this, 'setSize'); + }, + + setSize: function(_size, _clear) { + var size = Size.read(arguments); + if (!size.equals(this._size)) { + if (size.width > 0 && size.height > 0) { + var element = !_clear && this.getElement(); + this._setImage(CanvasProvider.getCanvas(size)); + if (element) { + this.getContext(true).drawImage(element, 0, 0, + size.width, size.height); + } + } else { + if (this._canvas) + CanvasProvider.release(this._canvas); + this._size = size.clone(); + } + } else if (_clear) { + this.clear(); + } + }, + + getWidth: function() { + return this._size ? this._size.width : 0; + }, + + setWidth: function(width) { + this.setSize(width, this.getHeight()); + }, + + getHeight: function() { + return this._size ? this._size.height : 0; + }, + + setHeight: function(height) { + this.setSize(this.getWidth(), height); + }, + + getLoaded: function() { + return this._loaded; + }, + + isEmpty: function() { + var size = this._size; + return !size || size.width === 0 && size.height === 0; + }, + + getResolution: function() { + var matrix = this._matrix, + orig = new Point(0, 0).transform(matrix), + u = new Point(1, 0).transform(matrix).subtract(orig), + v = new Point(0, 1).transform(matrix).subtract(orig); + return new Size( + 72 / u.getLength(), + 72 / v.getLength() + ); + }, + + getPpi: '#getResolution', + + getImage: function() { + return this._image; + }, + + setImage: function(image) { + var that = this; + + function emit(event) { + var view = that.getView(), + type = event && event.type || 'load'; + if (view && that.responds(type)) { + paper = view._scope; + that.emit(type, new Event(event)); + } + } + + this._setImage(image); + if (this._loaded) { + setTimeout(emit, 0); + } else if (image) { + DomEvent.add(image, { + load: function(event) { + that._setImage(image); + emit(event); + }, + error: emit + }); + } + }, + + _setImage: function(image) { + if (this._canvas) + CanvasProvider.release(this._canvas); + if (image && image.getContext) { + this._image = null; + this._canvas = image; + this._loaded = true; + } else { + this._image = image; + this._canvas = null; + this._loaded = !!(image && image.src && image.complete); + } + this._size = new Size( + image ? image.naturalWidth || image.width : 0, + image ? image.naturalHeight || image.height : 0); + this._context = null; + this._changed(1033); + }, + + getCanvas: function() { + if (!this._canvas) { + var ctx = CanvasProvider.getContext(this._size); + try { + if (this._image) + ctx.drawImage(this._image, 0, 0); + this._canvas = ctx.canvas; + } catch (e) { + CanvasProvider.release(ctx); + } + } + return this._canvas; + }, + + setCanvas: '#setImage', + + getContext: function(_change) { + if (!this._context) + this._context = this.getCanvas().getContext('2d'); + if (_change) { + this._image = null; + this._changed(1025); + } + return this._context; + }, + + setContext: function(context) { + this._context = context; + }, + + getSource: function() { + var image = this._image; + return image && image.src || this.toDataURL(); + }, + + setSource: function(src) { + var image = new self.Image(), + crossOrigin = this._crossOrigin; + if (crossOrigin) + image.crossOrigin = crossOrigin; + if (src) + image.src = src; + this.setImage(image); + }, + + getCrossOrigin: function() { + var image = this._image; + return image && image.crossOrigin || this._crossOrigin || ''; + }, + + setCrossOrigin: function(crossOrigin) { + this._crossOrigin = crossOrigin; + var image = this._image; + if (image) + image.crossOrigin = crossOrigin; + }, + + getSmoothing: function() { + return this._smoothing; + }, + + setSmoothing: function(smoothing) { + this._smoothing = typeof smoothing === 'string' + ? smoothing + : smoothing ? 'low' : 'off'; + this._changed(257); + }, + + getElement: function() { + return this._canvas || this._loaded && this._image; + } +}, { + beans: false, + + getSubCanvas: function() { + var rect = Rectangle.read(arguments), + ctx = CanvasProvider.getContext(rect.getSize()); + ctx.drawImage(this.getCanvas(), rect.x, rect.y, + rect.width, rect.height, 0, 0, rect.width, rect.height); + return ctx.canvas; + }, + + getSubRaster: function() { + var rect = Rectangle.read(arguments), + raster = new Raster(Item.NO_INSERT); + raster._setImage(this.getSubCanvas(rect)); + raster.translate(rect.getCenter().subtract(this.getSize().divide(2))); + raster._matrix.prepend(this._matrix); + raster.insertAbove(this); + return raster; + }, + + toDataURL: function() { + var image = this._image, + src = image && image.src; + if (/^data:/.test(src)) + return src; + var canvas = this.getCanvas(); + return canvas ? canvas.toDataURL.apply(canvas, arguments) : null; + }, + + drawImage: function(image ) { + var point = Point.read(arguments, 1); + this.getContext(true).drawImage(image, point.x, point.y); + }, + + getAverageColor: function(object) { + var bounds, path; + if (!object) { + bounds = this.getBounds(); + } else if (object instanceof PathItem) { + path = object; + bounds = object.getBounds(); + } else if (typeof object === 'object') { + if ('width' in object) { + bounds = new Rectangle(object); + } else if ('x' in object) { + bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1); + } + } + if (!bounds) + return null; + var sampleSize = 32, + width = Math.min(bounds.width, sampleSize), + height = Math.min(bounds.height, sampleSize); + var ctx = Raster._sampleContext; + if (!ctx) { + ctx = Raster._sampleContext = CanvasProvider.getContext( + new Size(sampleSize)); + } else { + ctx.clearRect(0, 0, sampleSize + 1, sampleSize + 1); + } + ctx.save(); + var matrix = new Matrix() + .scale(width / bounds.width, height / bounds.height) + .translate(-bounds.x, -bounds.y); + matrix.applyToContext(ctx); + if (path) + path.draw(ctx, new Base({ clip: true, matrices: [matrix] })); + this._matrix.applyToContext(ctx); + var element = this.getElement(), + size = this._size; + if (element) + ctx.drawImage(element, -size.width / 2, -size.height / 2); + ctx.restore(); + var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width), + Math.ceil(height)).data, + channels = [0, 0, 0], + total = 0; + for (var i = 0, l = pixels.length; i < l; i += 4) { + var alpha = pixels[i + 3]; + total += alpha; + alpha /= 255; + channels[0] += pixels[i] * alpha; + channels[1] += pixels[i + 1] * alpha; + channels[2] += pixels[i + 2] * alpha; + } + for (var i = 0; i < 3; i++) + channels[i] /= total; + return total ? Color.read(channels) : null; + }, + + getPixel: function() { + var point = Point.read(arguments); + var data = this.getContext().getImageData(point.x, point.y, 1, 1).data; + return new Color('rgb', [data[0] / 255, data[1] / 255, data[2] / 255], + data[3] / 255); + }, + + setPixel: function() { + var args = arguments, + point = Point.read(args), + color = Color.read(args), + components = color._convert('rgb'), + alpha = color._alpha, + ctx = this.getContext(true), + imageData = ctx.createImageData(1, 1), + data = imageData.data; + data[0] = components[0] * 255; + data[1] = components[1] * 255; + data[2] = components[2] * 255; + data[3] = alpha != null ? alpha * 255 : 255; + ctx.putImageData(imageData, point.x, point.y); + }, + + clear: function() { + var size = this._size; + this.getContext(true).clearRect(0, 0, size.width + 1, size.height + 1); + }, + + createImageData: function() { + var size = Size.read(arguments); + return this.getContext().createImageData(size.width, size.height); + }, + + getImageData: function() { + var rect = Rectangle.read(arguments); + if (rect.isEmpty()) + rect = new Rectangle(this._size); + return this.getContext().getImageData(rect.x, rect.y, + rect.width, rect.height); + }, + + setImageData: function(data ) { + var point = Point.read(arguments, 1); + this.getContext(true).putImageData(data, point.x, point.y); + }, + + _getBounds: function(matrix, options) { + var rect = new Rectangle(this._size).setCenter(0, 0); + return matrix ? matrix._transformBounds(rect) : rect; + }, + + _hitTestSelf: function(point) { + if (this._contains(point)) { + var that = this; + return new HitResult('pixel', that, { + offset: point.add(that._size.divide(2)).round(), + color: { + get: function() { + return that.getPixel(this.offset); + } + } + }); + } + }, + + _draw: function(ctx, param, viewMatrix) { + var element = this.getElement(); + if (element && element.width > 0 && element.height > 0) { + ctx.globalAlpha = Numerical.clamp(this._opacity, 0, 1); + + this._setStyles(ctx, param, viewMatrix); + + var smoothing = this._smoothing, + disabled = smoothing === 'off'; + DomElement.setPrefixed( + ctx, + disabled ? 'imageSmoothingEnabled' : 'imageSmoothingQuality', + disabled ? false : smoothing + ); + + ctx.drawImage(element, + -this._size.width / 2, -this._size.height / 2); + } + }, + + _canComposite: function() { + return true; + } +}); + +var SymbolItem = Item.extend({ + _class: 'SymbolItem', + _applyMatrix: false, + _canApplyMatrix: false, + _boundsOptions: { stroke: true }, + _serializeFields: { + symbol: null + }, + + initialize: function SymbolItem(arg0, arg1) { + if (!this._initialize(arg0, + arg1 !== undefined && Point.read(arguments, 1))) + this.setDefinition(arg0 instanceof SymbolDefinition ? + arg0 : new SymbolDefinition(arg0)); + }, + + _equals: function(item) { + return this._definition === item._definition; + }, + + copyContent: function(source) { + this.setDefinition(source._definition); + }, + + getDefinition: function() { + return this._definition; + }, + + setDefinition: function(definition) { + this._definition = definition; + this._changed(9); + }, + + getSymbol: '#getDefinition', + setSymbol: '#setDefinition', + + isEmpty: function() { + return this._definition._item.isEmpty(); + }, + + _getBounds: function(matrix, options) { + var item = this._definition._item; + return item._getCachedBounds(item._matrix.prepended(matrix), options); + }, + + _hitTestSelf: function(point, options, viewMatrix) { + var opts = options.extend({ all: false }); + var res = this._definition._item._hitTest(point, opts, viewMatrix); + if (res) + res.item = this; + return res; + }, + + _draw: function(ctx, param) { + this._definition._item.draw(ctx, param); + } + +}); + +var SymbolDefinition = Base.extend({ + _class: 'SymbolDefinition', + + initialize: function SymbolDefinition(item, dontCenter) { + this._id = UID.get(); + this.project = paper.project; + if (item) + this.setItem(item, dontCenter); + }, + + _serialize: function(options, dictionary) { + return dictionary.add(this, function() { + return Base.serialize([this._class, this._item], + options, false, dictionary); + }); + }, + + _changed: function(flags) { + if (flags & 8) + Item._clearBoundsCache(this); + if (flags & 1) + this.project._changed(flags); + }, + + getItem: function() { + return this._item; + }, + + setItem: function(item, _dontCenter) { + if (item._symbol) + item = item.clone(); + if (this._item) + this._item._symbol = null; + this._item = item; + item.remove(); + item.setSelected(false); + if (!_dontCenter) + item.setPosition(new Point()); + item._symbol = this; + this._changed(9); + }, + + getDefinition: '#getItem', + setDefinition: '#setItem', + + place: function(position) { + return new SymbolItem(this, position); + }, + + clone: function() { + return new SymbolDefinition(this._item.clone(false)); + }, + + equals: function(symbol) { + return symbol === this + || symbol && this._item.equals(symbol._item) + || false; + } +}); + +var HitResult = Base.extend({ + _class: 'HitResult', + + initialize: function HitResult(type, item, values) { + this.type = type; + this.item = item; + if (values) + this.inject(values); + }, + + statics: { + getOptions: function(args) { + var options = args && Base.read(args); + return new Base({ + type: null, + tolerance: paper.settings.hitTolerance, + fill: !options, + stroke: !options, + segments: !options, + handles: false, + ends: false, + position: false, + center: false, + bounds: false, + guides: false, + selected: false + }, options); + } + } +}); + +var Segment = Base.extend({ + _class: 'Segment', + beans: true, + _selection: 0, + + initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) { + var count = arguments.length, + point, handleIn, handleOut, selection; + if (count > 0) { + if (arg0 == null || typeof arg0 === 'object') { + if (count === 1 && arg0 && 'point' in arg0) { + point = arg0.point; + handleIn = arg0.handleIn; + handleOut = arg0.handleOut; + selection = arg0.selection; + } else { + point = arg0; + handleIn = arg1; + handleOut = arg2; + selection = arg3; + } + } else { + point = [ arg0, arg1 ]; + handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null; + handleOut = arg4 !== undefined ? [ arg4, arg5 ] : null; + } + } + new SegmentPoint(point, this, '_point'); + new SegmentPoint(handleIn, this, '_handleIn'); + new SegmentPoint(handleOut, this, '_handleOut'); + if (selection) + this.setSelection(selection); + }, + + _serialize: function(options, dictionary) { + var point = this._point, + selection = this._selection, + obj = selection || this.hasHandles() + ? [point, this._handleIn, this._handleOut] + : point; + if (selection) + obj.push(selection); + return Base.serialize(obj, options, true, dictionary); + }, + + _changed: function(point) { + var path = this._path; + if (!path) + return; + var curves = path._curves, + index = this._index, + curve; + if (curves) { + if ((!point || point === this._point || point === this._handleIn) + && (curve = index > 0 ? curves[index - 1] : path._closed + ? curves[curves.length - 1] : null)) + curve._changed(); + if ((!point || point === this._point || point === this._handleOut) + && (curve = curves[index])) + curve._changed(); + } + path._changed(41); + }, + + getPoint: function() { + return this._point; + }, + + setPoint: function() { + this._point.set(Point.read(arguments)); + }, + + getHandleIn: function() { + return this._handleIn; + }, + + setHandleIn: function() { + this._handleIn.set(Point.read(arguments)); + }, + + getHandleOut: function() { + return this._handleOut; + }, + + setHandleOut: function() { + this._handleOut.set(Point.read(arguments)); + }, + + hasHandles: function() { + return !this._handleIn.isZero() || !this._handleOut.isZero(); + }, + + isSmooth: function() { + var handleIn = this._handleIn, + handleOut = this._handleOut; + return !handleIn.isZero() && !handleOut.isZero() + && handleIn.isCollinear(handleOut); + }, + + clearHandles: function() { + this._handleIn._set(0, 0); + this._handleOut._set(0, 0); + }, + + getSelection: function() { + return this._selection; + }, + + setSelection: function(selection) { + var oldSelection = this._selection, + path = this._path; + this._selection = selection = selection || 0; + if (path && selection !== oldSelection) { + path._updateSelection(this, oldSelection, selection); + path._changed(257); + } + }, + + _changeSelection: function(flag, selected) { + var selection = this._selection; + this.setSelection(selected ? selection | flag : selection & ~flag); + }, + + isSelected: function() { + return !!(this._selection & 7); + }, + + setSelected: function(selected) { + this._changeSelection(7, selected); + }, + + getIndex: function() { + return this._index !== undefined ? this._index : null; + }, + + getPath: function() { + return this._path || null; + }, + + getCurve: function() { + var path = this._path, + index = this._index; + if (path) { + if (index > 0 && !path._closed + && index === path._segments.length - 1) + index--; + return path.getCurves()[index] || null; + } + return null; + }, + + getLocation: function() { + var curve = this.getCurve(); + return curve + ? new CurveLocation(curve, this === curve._segment1 ? 0 : 1) + : null; + }, + + getNext: function() { + var segments = this._path && this._path._segments; + return segments && (segments[this._index + 1] + || this._path._closed && segments[0]) || null; + }, + + smooth: function(options, _first, _last) { + var opts = options || {}, + type = opts.type, + factor = opts.factor, + prev = this.getPrevious(), + next = this.getNext(), + p0 = (prev || this)._point, + p1 = this._point, + p2 = (next || this)._point, + d1 = p0.getDistance(p1), + d2 = p1.getDistance(p2); + if (!type || type === 'catmull-rom') { + var a = factor === undefined ? 0.5 : factor, + d1_a = Math.pow(d1, a), + d1_2a = d1_a * d1_a, + d2_a = Math.pow(d2, a), + d2_2a = d2_a * d2_a; + if (!_first && prev) { + var A = 2 * d2_2a + 3 * d2_a * d1_a + d1_2a, + N = 3 * d2_a * (d2_a + d1_a); + this.setHandleIn(N !== 0 + ? new Point( + (d2_2a * p0._x + A * p1._x - d1_2a * p2._x) / N - p1._x, + (d2_2a * p0._y + A * p1._y - d1_2a * p2._y) / N - p1._y) + : new Point()); + } + if (!_last && next) { + var A = 2 * d1_2a + 3 * d1_a * d2_a + d2_2a, + N = 3 * d1_a * (d1_a + d2_a); + this.setHandleOut(N !== 0 + ? new Point( + (d1_2a * p2._x + A * p1._x - d2_2a * p0._x) / N - p1._x, + (d1_2a * p2._y + A * p1._y - d2_2a * p0._y) / N - p1._y) + : new Point()); + } + } else if (type === 'geometric') { + if (prev && next) { + var vector = p0.subtract(p2), + t = factor === undefined ? 0.4 : factor, + k = t * d1 / (d1 + d2); + if (!_first) + this.setHandleIn(vector.multiply(k)); + if (!_last) + this.setHandleOut(vector.multiply(k - t)); + } + } else { + throw new Error('Smoothing method \'' + type + '\' not supported.'); + } + }, + + getPrevious: function() { + var segments = this._path && this._path._segments; + return segments && (segments[this._index - 1] + || this._path._closed && segments[segments.length - 1]) || null; + }, + + isFirst: function() { + return !this._index; + }, + + isLast: function() { + var path = this._path; + return path && this._index === path._segments.length - 1 || false; + }, + + reverse: function() { + var handleIn = this._handleIn, + handleOut = this._handleOut, + tmp = handleIn.clone(); + handleIn.set(handleOut); + handleOut.set(tmp); + }, + + reversed: function() { + return new Segment(this._point, this._handleOut, this._handleIn); + }, + + remove: function() { + return this._path ? !!this._path.removeSegment(this._index) : false; + }, + + clone: function() { + return new Segment(this._point, this._handleIn, this._handleOut); + }, + + equals: function(segment) { + return segment === this || segment && this._class === segment._class + && this._point.equals(segment._point) + && this._handleIn.equals(segment._handleIn) + && this._handleOut.equals(segment._handleOut) + || false; + }, + + toString: function() { + var parts = [ 'point: ' + this._point ]; + if (!this._handleIn.isZero()) + parts.push('handleIn: ' + this._handleIn); + if (!this._handleOut.isZero()) + parts.push('handleOut: ' + this._handleOut); + return '{ ' + parts.join(', ') + ' }'; + }, + + transform: function(matrix) { + this._transformCoordinates(matrix, new Array(6), true); + this._changed(); + }, + + interpolate: function(from, to, factor) { + var u = 1 - factor, + v = factor, + point1 = from._point, + point2 = to._point, + handleIn1 = from._handleIn, + handleIn2 = to._handleIn, + handleOut2 = to._handleOut, + handleOut1 = from._handleOut; + this._point._set( + u * point1._x + v * point2._x, + u * point1._y + v * point2._y, true); + this._handleIn._set( + u * handleIn1._x + v * handleIn2._x, + u * handleIn1._y + v * handleIn2._y, true); + this._handleOut._set( + u * handleOut1._x + v * handleOut2._x, + u * handleOut1._y + v * handleOut2._y, true); + this._changed(); + }, + + _transformCoordinates: function(matrix, coords, change) { + var point = this._point, + handleIn = !change || !this._handleIn.isZero() + ? this._handleIn : null, + handleOut = !change || !this._handleOut.isZero() + ? this._handleOut : null, + x = point._x, + y = point._y, + i = 2; + coords[0] = x; + coords[1] = y; + if (handleIn) { + coords[i++] = handleIn._x + x; + coords[i++] = handleIn._y + y; + } + if (handleOut) { + coords[i++] = handleOut._x + x; + coords[i++] = handleOut._y + y; + } + if (matrix) { + matrix._transformCoordinates(coords, coords, i / 2); + x = coords[0]; + y = coords[1]; + if (change) { + point._x = x; + point._y = y; + i = 2; + if (handleIn) { + handleIn._x = coords[i++] - x; + handleIn._y = coords[i++] - y; + } + if (handleOut) { + handleOut._x = coords[i++] - x; + handleOut._y = coords[i++] - y; + } + } else { + if (!handleIn) { + coords[i++] = x; + coords[i++] = y; + } + if (!handleOut) { + coords[i++] = x; + coords[i++] = y; + } + } + } + return coords; + } +}); + +var SegmentPoint = Point.extend({ + initialize: function SegmentPoint(point, owner, key) { + var x, y, + selected; + if (!point) { + x = y = 0; + } else if ((x = point[0]) !== undefined) { + y = point[1]; + } else { + var pt = point; + if ((x = pt.x) === undefined) { + pt = Point.read(arguments); + x = pt.x; + } + y = pt.y; + selected = pt.selected; + } + this._x = x; + this._y = y; + this._owner = owner; + owner[key] = this; + if (selected) + this.setSelected(true); + }, + + _set: function(x, y) { + this._x = x; + this._y = y; + this._owner._changed(this); + return this; + }, + + getX: function() { + return this._x; + }, + + setX: function(x) { + this._x = x; + this._owner._changed(this); + }, + + getY: function() { + return this._y; + }, + + setY: function(y) { + this._y = y; + this._owner._changed(this); + }, + + isZero: function() { + var isZero = Numerical.isZero; + return isZero(this._x) && isZero(this._y); + }, + + isSelected: function() { + return !!(this._owner._selection & this._getSelection()); + }, + + setSelected: function(selected) { + this._owner._changeSelection(this._getSelection(), selected); + }, + + _getSelection: function() { + var owner = this._owner; + return this === owner._point ? 1 + : this === owner._handleIn ? 2 + : this === owner._handleOut ? 4 + : 0; + } +}); + +var Curve = Base.extend({ + _class: 'Curve', + beans: true, + + initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { + var count = arguments.length, + seg1, seg2, + point1, point2, + handle1, handle2; + if (count === 3) { + this._path = arg0; + seg1 = arg1; + seg2 = arg2; + } else if (!count) { + seg1 = new Segment(); + seg2 = new Segment(); + } else if (count === 1) { + if ('segment1' in arg0) { + seg1 = new Segment(arg0.segment1); + seg2 = new Segment(arg0.segment2); + } else if ('point1' in arg0) { + point1 = arg0.point1; + handle1 = arg0.handle1; + handle2 = arg0.handle2; + point2 = arg0.point2; + } else if (Array.isArray(arg0)) { + point1 = [arg0[0], arg0[1]]; + point2 = [arg0[6], arg0[7]]; + handle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]]; + handle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]]; + } + } else if (count === 2) { + seg1 = new Segment(arg0); + seg2 = new Segment(arg1); + } else if (count === 4) { + point1 = arg0; + handle1 = arg1; + handle2 = arg2; + point2 = arg3; + } else if (count === 8) { + point1 = [arg0, arg1]; + point2 = [arg6, arg7]; + handle1 = [arg2 - arg0, arg3 - arg1]; + handle2 = [arg4 - arg6, arg5 - arg7]; + } + this._segment1 = seg1 || new Segment(point1, null, handle1); + this._segment2 = seg2 || new Segment(point2, handle2, null); + }, + + _serialize: function(options, dictionary) { + return Base.serialize(this.hasHandles() + ? [this.getPoint1(), this.getHandle1(), this.getHandle2(), + this.getPoint2()] + : [this.getPoint1(), this.getPoint2()], + options, true, dictionary); + }, + + _changed: function() { + this._length = this._bounds = undefined; + }, + + clone: function() { + return new Curve(this._segment1, this._segment2); + }, + + toString: function() { + var parts = [ 'point1: ' + this._segment1._point ]; + if (!this._segment1._handleOut.isZero()) + parts.push('handle1: ' + this._segment1._handleOut); + if (!this._segment2._handleIn.isZero()) + parts.push('handle2: ' + this._segment2._handleIn); + parts.push('point2: ' + this._segment2._point); + return '{ ' + parts.join(', ') + ' }'; + }, + + classify: function() { + return Curve.classify(this.getValues()); + }, + + remove: function() { + var removed = false; + if (this._path) { + var segment2 = this._segment2, + handleOut = segment2._handleOut; + removed = segment2.remove(); + if (removed) + this._segment1._handleOut.set(handleOut); + } + return removed; + }, + + getPoint1: function() { + return this._segment1._point; + }, + + setPoint1: function() { + this._segment1._point.set(Point.read(arguments)); + }, + + getPoint2: function() { + return this._segment2._point; + }, + + setPoint2: function() { + this._segment2._point.set(Point.read(arguments)); + }, + + getHandle1: function() { + return this._segment1._handleOut; + }, + + setHandle1: function() { + this._segment1._handleOut.set(Point.read(arguments)); + }, + + getHandle2: function() { + return this._segment2._handleIn; + }, + + setHandle2: function() { + this._segment2._handleIn.set(Point.read(arguments)); + }, + + getSegment1: function() { + return this._segment1; + }, + + getSegment2: function() { + return this._segment2; + }, + + getPath: function() { + return this._path; + }, + + getIndex: function() { + return this._segment1._index; + }, + + getNext: function() { + var curves = this._path && this._path._curves; + return curves && (curves[this._segment1._index + 1] + || this._path._closed && curves[0]) || null; + }, + + getPrevious: function() { + var curves = this._path && this._path._curves; + return curves && (curves[this._segment1._index - 1] + || this._path._closed && curves[curves.length - 1]) || null; + }, + + isFirst: function() { + return !this._segment1._index; + }, + + isLast: function() { + var path = this._path; + return path && this._segment1._index === path._curves.length - 1 + || false; + }, + + isSelected: function() { + return this.getPoint1().isSelected() + && this.getHandle1().isSelected() + && this.getHandle2().isSelected() + && this.getPoint2().isSelected(); + }, + + setSelected: function(selected) { + this.getPoint1().setSelected(selected); + this.getHandle1().setSelected(selected); + this.getHandle2().setSelected(selected); + this.getPoint2().setSelected(selected); + }, + + getValues: function(matrix) { + return Curve.getValues(this._segment1, this._segment2, matrix); + }, + + getPoints: function() { + var coords = this.getValues(), + points = []; + for (var i = 0; i < 8; i += 2) + points.push(new Point(coords[i], coords[i + 1])); + return points; + } +}, { + getLength: function() { + if (this._length == null) + this._length = Curve.getLength(this.getValues(), 0, 1); + return this._length; + }, + + getArea: function() { + return Curve.getArea(this.getValues()); + }, + + getLine: function() { + return new Line(this._segment1._point, this._segment2._point); + }, + + getPart: function(from, to) { + return new Curve(Curve.getPart(this.getValues(), from, to)); + }, + + getPartLength: function(from, to) { + return Curve.getLength(this.getValues(), from, to); + }, + + divideAt: function(location) { + return this.divideAtTime(location && location.curve === this + ? location.time : this.getTimeAt(location)); + }, + + divideAtTime: function(time, _setHandles) { + var tMin = 1e-8, + tMax = 1 - tMin, + res = null; + if (time >= tMin && time <= tMax) { + var parts = Curve.subdivide(this.getValues(), time), + left = parts[0], + right = parts[1], + setHandles = _setHandles || this.hasHandles(), + seg1 = this._segment1, + seg2 = this._segment2, + path = this._path; + if (setHandles) { + seg1._handleOut._set(left[2] - left[0], left[3] - left[1]); + seg2._handleIn._set(right[4] - right[6],right[5] - right[7]); + } + var x = left[6], y = left[7], + segment = new Segment(new Point(x, y), + setHandles && new Point(left[4] - x, left[5] - y), + setHandles && new Point(right[2] - x, right[3] - y)); + if (path) { + path.insert(seg1._index + 1, segment); + res = this.getNext(); + } else { + this._segment2 = segment; + this._changed(); + res = new Curve(segment, seg2); + } + } + return res; + }, + + splitAt: function(location) { + var path = this._path; + return path ? path.splitAt(location) : null; + }, + + splitAtTime: function(time) { + return this.splitAt(this.getLocationAtTime(time)); + }, + + divide: function(offset, isTime) { + return this.divideAtTime(offset === undefined ? 0.5 : isTime ? offset + : this.getTimeAt(offset)); + }, + + split: function(offset, isTime) { + return this.splitAtTime(offset === undefined ? 0.5 : isTime ? offset + : this.getTimeAt(offset)); + }, + + reversed: function() { + return new Curve(this._segment2.reversed(), this._segment1.reversed()); + }, + + clearHandles: function() { + this._segment1._handleOut._set(0, 0); + this._segment2._handleIn._set(0, 0); + }, + +statics: { + getValues: function(segment1, segment2, matrix, straight) { + var p1 = segment1._point, + h1 = segment1._handleOut, + h2 = segment2._handleIn, + p2 = segment2._point, + x1 = p1.x, y1 = p1.y, + x2 = p2.x, y2 = p2.y, + values = straight + ? [ x1, y1, x1, y1, x2, y2, x2, y2 ] + : [ + x1, y1, + x1 + h1._x, y1 + h1._y, + x2 + h2._x, y2 + h2._y, + x2, y2 + ]; + if (matrix) + matrix._transformCoordinates(values, values, 4); + return values; + }, + + subdivide: function(v, t) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7]; + if (t === undefined) + t = 0.5; + var u = 1 - t, + x4 = u * x0 + t * x1, y4 = u * y0 + t * y1, + x5 = u * x1 + t * x2, y5 = u * y1 + t * y2, + x6 = u * x2 + t * x3, y6 = u * y2 + t * y3, + x7 = u * x4 + t * x5, y7 = u * y4 + t * y5, + x8 = u * x5 + t * x6, y8 = u * y5 + t * y6, + x9 = u * x7 + t * x8, y9 = u * y7 + t * y8; + return [ + [x0, y0, x4, y4, x7, y7, x9, y9], + [x9, y9, x8, y8, x6, y6, x3, y3] + ]; + }, + + getMonoCurves: function(v, dir) { + var curves = [], + io = dir ? 0 : 1, + o0 = v[io + 0], + o1 = v[io + 2], + o2 = v[io + 4], + o3 = v[io + 6]; + if ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3) + || Curve.isStraight(v)) { + curves.push(v); + } else { + var a = 3 * (o1 - o2) - o0 + o3, + b = 2 * (o0 + o2) - 4 * o1, + c = o1 - o0, + tMin = 1e-8, + tMax = 1 - tMin, + roots = [], + n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax); + if (!n) { + curves.push(v); + } else { + roots.sort(); + var t = roots[0], + parts = Curve.subdivide(v, t); + curves.push(parts[0]); + if (n > 1) { + t = (roots[1] - t) / (1 - t); + parts = Curve.subdivide(parts[1], t); + curves.push(parts[0]); + } + curves.push(parts[1]); + } + } + return curves; + }, + + solveCubic: function (v, coord, val, roots, min, max) { + var v0 = v[coord], + v1 = v[coord + 2], + v2 = v[coord + 4], + v3 = v[coord + 6], + res = 0; + if ( !(v0 < val && v3 < val && v1 < val && v2 < val || + v0 > val && v3 > val && v1 > val && v2 > val)) { + var c = 3 * (v1 - v0), + b = 3 * (v2 - v1) - c, + a = v3 - v0 - c - b; + res = Numerical.solveCubic(a, b, c, v0 - val, roots, min, max); + } + return res; + }, + + getTimeOf: function(v, point) { + var p0 = new Point(v[0], v[1]), + p3 = new Point(v[6], v[7]), + epsilon = 1e-12, + geomEpsilon = 1e-7, + t = point.isClose(p0, epsilon) ? 0 + : point.isClose(p3, epsilon) ? 1 + : null; + if (t === null) { + var coords = [point.x, point.y], + roots = []; + for (var c = 0; c < 2; c++) { + var count = Curve.solveCubic(v, c, coords[c], roots, 0, 1); + for (var i = 0; i < count; i++) { + var u = roots[i]; + if (point.isClose(Curve.getPoint(v, u), geomEpsilon)) + return u; + } + } + } + return point.isClose(p0, geomEpsilon) ? 0 + : point.isClose(p3, geomEpsilon) ? 1 + : null; + }, + + getNearestTime: function(v, point) { + if (Curve.isStraight(v)) { + var x0 = v[0], y0 = v[1], + x3 = v[6], y3 = v[7], + vx = x3 - x0, vy = y3 - y0, + det = vx * vx + vy * vy; + if (det === 0) + return 0; + var u = ((point.x - x0) * vx + (point.y - y0) * vy) / det; + return u < 1e-12 ? 0 + : u > 0.999999999999 ? 1 + : Curve.getTimeOf(v, + new Point(x0 + u * vx, y0 + u * vy)); + } + + var count = 100, + minDist = Infinity, + minT = 0; + + function refine(t) { + if (t >= 0 && t <= 1) { + var dist = point.getDistance(Curve.getPoint(v, t), true); + if (dist < minDist) { + minDist = dist; + minT = t; + return true; + } + } + } + + for (var i = 0; i <= count; i++) + refine(i / count); + + var step = 1 / (count * 2); + while (step > 1e-8) { + if (!refine(minT - step) && !refine(minT + step)) + step /= 2; + } + return minT; + }, + + getPart: function(v, from, to) { + var flip = from > to; + if (flip) { + var tmp = from; + from = to; + to = tmp; + } + if (from > 0) + v = Curve.subdivide(v, from)[1]; + if (to < 1) + v = Curve.subdivide(v, (to - from) / (1 - from))[0]; + return flip + ? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]] + : v; + }, + + isFlatEnough: function(v, flatness) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + ux = 3 * x1 - 2 * x0 - x3, + uy = 3 * y1 - 2 * y0 - y3, + vx = 3 * x2 - 2 * x3 - x0, + vy = 3 * y2 - 2 * y3 - y0; + return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy) + <= 16 * flatness * flatness; + }, + + getArea: function(v) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7]; + return 3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2) + + y1 * (x0 - x2) - x1 * (y0 - y2) + + y3 * (x2 + x0 / 3) - x3 * (y2 + y0 / 3)) / 20; + }, + + getBounds: function(v) { + var min = v.slice(0, 2), + max = min.slice(), + roots = [0, 0]; + for (var i = 0; i < 2; i++) + Curve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6], + i, 0, min, max, roots); + return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]); + }, + + _addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) { + function add(value, padding) { + var left = value - padding, + right = value + padding; + if (left < min[coord]) + min[coord] = left; + if (right > max[coord]) + max[coord] = right; + } + + padding /= 2; + var minPad = min[coord] + padding, + maxPad = max[coord] - padding; + if ( v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad || + v0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) { + if (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) { + add(v0, 0); + add(v3, 0); + } else { + var a = 3 * (v1 - v2) - v0 + v3, + b = 2 * (v0 + v2) - 4 * v1, + c = v1 - v0, + count = Numerical.solveQuadratic(a, b, c, roots), + tMin = 1e-8, + tMax = 1 - tMin; + add(v3, 0); + for (var i = 0; i < count; i++) { + var t = roots[i], + u = 1 - t; + if (tMin <= t && t <= tMax) + add(u * u * u * v0 + + 3 * u * u * t * v1 + + 3 * u * t * t * v2 + + t * t * t * v3, + padding); + } + } + } + } +}}, Base.each( + ['getBounds', 'getStrokeBounds', 'getHandleBounds'], + function(name) { + this[name] = function() { + if (!this._bounds) + this._bounds = {}; + var bounds = this._bounds[name]; + if (!bounds) { + bounds = this._bounds[name] = Path[name]( + [this._segment1, this._segment2], false, this._path); + } + return bounds.clone(); + }; + }, +{ + +}), Base.each({ + isStraight: function(p1, h1, h2, p2) { + if (h1.isZero() && h2.isZero()) { + return true; + } else { + var v = p2.subtract(p1); + if (v.isZero()) { + return false; + } else if (v.isCollinear(h1) && v.isCollinear(h2)) { + var l = new Line(p1, p2), + epsilon = 1e-7; + if (l.getDistance(p1.add(h1)) < epsilon && + l.getDistance(p2.add(h2)) < epsilon) { + var div = v.dot(v), + s1 = v.dot(h1) / div, + s2 = v.dot(h2) / div; + return s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1; + } + } + } + return false; + }, + + isLinear: function(p1, h1, h2, p2) { + var third = p2.subtract(p1).divide(3); + return h1.equals(third) && h2.negate().equals(third); + } +}, function(test, name) { + this[name] = function(epsilon) { + var seg1 = this._segment1, + seg2 = this._segment2; + return test(seg1._point, seg1._handleOut, seg2._handleIn, seg2._point, + epsilon); + }; + + this.statics[name] = function(v, epsilon) { + var x0 = v[0], y0 = v[1], + x3 = v[6], y3 = v[7]; + return test( + new Point(x0, y0), + new Point(v[2] - x0, v[3] - y0), + new Point(v[4] - x3, v[5] - y3), + new Point(x3, y3), epsilon); + }; +}, { + statics: {}, + + hasHandles: function() { + return !this._segment1._handleOut.isZero() + || !this._segment2._handleIn.isZero(); + }, + + hasLength: function(epsilon) { + return (!this.getPoint1().equals(this.getPoint2()) || this.hasHandles()) + && this.getLength() > (epsilon || 0); + }, + + isCollinear: function(curve) { + return curve && this.isStraight() && curve.isStraight() + && this.getLine().isCollinear(curve.getLine()); + }, + + isHorizontal: function() { + return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).y) + < 1e-8; + }, + + isVertical: function() { + return this.isStraight() && Math.abs(this.getTangentAtTime(0.5).x) + < 1e-8; + } +}), { + beans: false, + + getLocationAt: function(offset, _isTime) { + return this.getLocationAtTime( + _isTime ? offset : this.getTimeAt(offset)); + }, + + getLocationAtTime: function(t) { + return t != null && t >= 0 && t <= 1 + ? new CurveLocation(this, t) + : null; + }, + + getTimeAt: function(offset, start) { + return Curve.getTimeAt(this.getValues(), offset, start); + }, + + getParameterAt: '#getTimeAt', + + getTimesWithTangent: function () { + var tangent = Point.read(arguments); + return tangent.isZero() + ? [] + : Curve.getTimesWithTangent(this.getValues(), tangent); + }, + + getOffsetAtTime: function(t) { + return this.getPartLength(0, t); + }, + + getLocationOf: function() { + return this.getLocationAtTime(this.getTimeOf(Point.read(arguments))); + }, + + getOffsetOf: function() { + var loc = this.getLocationOf.apply(this, arguments); + return loc ? loc.getOffset() : null; + }, + + getTimeOf: function() { + return Curve.getTimeOf(this.getValues(), Point.read(arguments)); + }, + + getParameterOf: '#getTimeOf', + + getNearestLocation: function() { + var point = Point.read(arguments), + values = this.getValues(), + t = Curve.getNearestTime(values, point), + pt = Curve.getPoint(values, t); + return new CurveLocation(this, t, pt, null, point.getDistance(pt)); + }, + + getNearestPoint: function() { + var loc = this.getNearestLocation.apply(this, arguments); + return loc ? loc.getPoint() : loc; + } + +}, +new function() { + var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent', + 'getWeightedNormal', 'getCurvature']; + return Base.each(methods, + function(name) { + this[name + 'At'] = function(location, _isTime) { + var values = this.getValues(); + return Curve[name](values, _isTime ? location + : Curve.getTimeAt(values, location)); + }; + + this[name + 'AtTime'] = function(time) { + return Curve[name](this.getValues(), time); + }; + }, { + statics: { + _evaluateMethods: methods + } + } + ); +}, +new function() { + + function getLengthIntegrand(v) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + + ax = 9 * (x1 - x2) + 3 * (x3 - x0), + bx = 6 * (x0 + x2) - 12 * x1, + cx = 3 * (x1 - x0), + + ay = 9 * (y1 - y2) + 3 * (y3 - y0), + by = 6 * (y0 + y2) - 12 * y1, + cy = 3 * (y1 - y0); + + return function(t) { + var dx = (ax * t + bx) * t + cx, + dy = (ay * t + by) * t + cy; + return Math.sqrt(dx * dx + dy * dy); + }; + } + + function getIterations(a, b) { + return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32))); + } + + function evaluate(v, t, type, normalized) { + if (t == null || t < 0 || t > 1) + return null; + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + isZero = Numerical.isZero; + if (isZero(x1 - x0) && isZero(y1 - y0)) { + x1 = x0; + y1 = y0; + } + if (isZero(x2 - x3) && isZero(y2 - y3)) { + x2 = x3; + y2 = y3; + } + var cx = 3 * (x1 - x0), + bx = 3 * (x2 - x1) - cx, + ax = x3 - x0 - cx - bx, + cy = 3 * (y1 - y0), + by = 3 * (y2 - y1) - cy, + ay = y3 - y0 - cy - by, + x, y; + if (type === 0) { + x = t === 0 ? x0 : t === 1 ? x3 + : ((ax * t + bx) * t + cx) * t + x0; + y = t === 0 ? y0 : t === 1 ? y3 + : ((ay * t + by) * t + cy) * t + y0; + } else { + var tMin = 1e-8, + tMax = 1 - tMin; + if (t < tMin) { + x = cx; + y = cy; + } else if (t > tMax) { + x = 3 * (x3 - x2); + y = 3 * (y3 - y2); + } else { + x = (3 * ax * t + 2 * bx) * t + cx; + y = (3 * ay * t + 2 * by) * t + cy; + } + if (normalized) { + if (x === 0 && y === 0 && (t < tMin || t > tMax)) { + x = x2 - x1; + y = y2 - y1; + } + var len = Math.sqrt(x * x + y * y); + if (len) { + x /= len; + y /= len; + } + } + if (type === 3) { + var x2 = 6 * ax * t + 2 * bx, + y2 = 6 * ay * t + 2 * by, + d = Math.pow(x * x + y * y, 3 / 2); + x = d !== 0 ? (x * y2 - y * x2) / d : 0; + y = 0; + } + } + return type === 2 ? new Point(y, -x) : new Point(x, y); + } + + return { statics: { + + classify: function(v) { + + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + a1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2, + a2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3, + a3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0, + d3 = 3 * a3, + d2 = d3 - a2, + d1 = d2 - a2 + a1, + l = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3), + s = l !== 0 ? 1 / l : 0, + isZero = Numerical.isZero, + serpentine = 'serpentine'; + d1 *= s; + d2 *= s; + d3 *= s; + + function type(type, t1, t2) { + var hasRoots = t1 !== undefined, + t1Ok = hasRoots && t1 > 0 && t1 < 1, + t2Ok = hasRoots && t2 > 0 && t2 < 1; + if (hasRoots && (!(t1Ok || t2Ok) + || type === 'loop' && !(t1Ok && t2Ok))) { + type = 'arch'; + t1Ok = t2Ok = false; + } + return { + type: type, + roots: t1Ok || t2Ok + ? t1Ok && t2Ok + ? t1 < t2 ? [t1, t2] : [t2, t1] + : [t1Ok ? t1 : t2] + : null + }; + } + + if (isZero(d1)) { + return isZero(d2) + ? type(isZero(d3) ? 'line' : 'quadratic') + : type(serpentine, d3 / (3 * d2)); + } + var d = 3 * d2 * d2 - 4 * d1 * d3; + if (isZero(d)) { + return type('cusp', d2 / (2 * d1)); + } + var f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d), + f2 = 2 * d1; + return type(d > 0 ? serpentine : 'loop', + (d2 + f1) / f2, + (d2 - f1) / f2); + }, + + getLength: function(v, a, b, ds) { + if (a === undefined) + a = 0; + if (b === undefined) + b = 1; + if (Curve.isStraight(v)) { + var c = v; + if (b < 1) { + c = Curve.subdivide(c, b)[0]; + a /= b; + } + if (a > 0) { + c = Curve.subdivide(c, a)[1]; + } + var dx = c[6] - c[0], + dy = c[7] - c[1]; + return Math.sqrt(dx * dx + dy * dy); + } + return Numerical.integrate(ds || getLengthIntegrand(v), a, b, + getIterations(a, b)); + }, + + getTimeAt: function(v, offset, start) { + if (start === undefined) + start = offset < 0 ? 1 : 0; + if (offset === 0) + return start; + var abs = Math.abs, + epsilon = 1e-12, + forward = offset > 0, + a = forward ? start : 0, + b = forward ? 1 : start, + ds = getLengthIntegrand(v), + rangeLength = Curve.getLength(v, a, b, ds), + diff = abs(offset) - rangeLength; + if (abs(diff) < epsilon) { + return forward ? b : a; + } else if (diff > epsilon) { + return null; + } + var guess = offset / rangeLength, + length = 0; + function f(t) { + length += Numerical.integrate(ds, start, t, + getIterations(start, t)); + start = t; + return length - offset; + } + return Numerical.findRoot(f, ds, start + guess, a, b, 32, + 1e-12); + }, + + getPoint: function(v, t) { + return evaluate(v, t, 0, false); + }, + + getTangent: function(v, t) { + return evaluate(v, t, 1, true); + }, + + getWeightedTangent: function(v, t) { + return evaluate(v, t, 1, false); + }, + + getNormal: function(v, t) { + return evaluate(v, t, 2, true); + }, + + getWeightedNormal: function(v, t) { + return evaluate(v, t, 2, false); + }, + + getCurvature: function(v, t) { + return evaluate(v, t, 3, false).x; + }, + + getPeaks: function(v) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + ax = -x0 + 3 * x1 - 3 * x2 + x3, + bx = 3 * x0 - 6 * x1 + 3 * x2, + cx = -3 * x0 + 3 * x1, + ay = -y0 + 3 * y1 - 3 * y2 + y3, + by = 3 * y0 - 6 * y1 + 3 * y2, + cy = -3 * y0 + 3 * y1, + tMin = 1e-8, + tMax = 1 - tMin, + roots = []; + Numerical.solveCubic( + 9 * (ax * ax + ay * ay), + 9 * (ax * bx + by * ay), + 2 * (bx * bx + by * by) + 3 * (cx * ax + cy * ay), + (cx * bx + by * cy), + roots, tMin, tMax); + return roots.sort(); + } + }}; +}, +new function() { + + function addLocation(locations, include, c1, t1, c2, t2, overlap) { + var excludeStart = !overlap && c1.getPrevious() === c2, + excludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2, + tMin = 1e-8, + tMax = 1 - tMin; + if (t1 !== null && t1 >= (excludeStart ? tMin : 0) && + t1 <= (excludeEnd ? tMax : 1)) { + if (t2 !== null && t2 >= (excludeEnd ? tMin : 0) && + t2 <= (excludeStart ? tMax : 1)) { + var loc1 = new CurveLocation(c1, t1, null, overlap), + loc2 = new CurveLocation(c2, t2, null, overlap); + loc1._intersection = loc2; + loc2._intersection = loc1; + if (!include || include(loc1)) { + CurveLocation.insert(locations, loc1, true); + } + } + } + } + + function addCurveIntersections(v1, v2, c1, c2, locations, include, flip, + recursion, calls, tMin, tMax, uMin, uMax) { + if (++calls >= 4096 || ++recursion >= 40) + return calls; + var fatLineEpsilon = 1e-9, + q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7], + getSignedDistance = Line.getSignedDistance, + d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]), + d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]), + factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, + dMin = factor * Math.min(0, d1, d2), + dMax = factor * Math.max(0, d1, d2), + dp0 = getSignedDistance(q0x, q0y, q3x, q3y, v1[0], v1[1]), + dp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]), + dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]), + dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]), + hull = getConvexHull(dp0, dp1, dp2, dp3), + top = hull[0], + bottom = hull[1], + tMinClip, + tMaxClip; + if (d1 === 0 && d2 === 0 + && dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0 + || (tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null + || (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(), + dMin, dMax)) == null) + return calls; + var tMinNew = tMin + (tMax - tMin) * tMinClip, + tMaxNew = tMin + (tMax - tMin) * tMaxClip; + if (Math.max(uMax - uMin, tMaxNew - tMinNew) < fatLineEpsilon) { + var t = (tMinNew + tMaxNew) / 2, + u = (uMin + uMax) / 2; + addLocation(locations, include, + flip ? c2 : c1, flip ? u : t, + flip ? c1 : c2, flip ? t : u); + } else { + v1 = Curve.getPart(v1, tMinClip, tMaxClip); + var uDiff = uMax - uMin; + if (tMaxClip - tMinClip > 0.8) { + if (tMaxNew - tMinNew > uDiff) { + var parts = Curve.subdivide(v1, 0.5), + t = (tMinNew + tMaxNew) / 2; + calls = addCurveIntersections( + v2, parts[0], c2, c1, locations, include, !flip, + recursion, calls, uMin, uMax, tMinNew, t); + calls = addCurveIntersections( + v2, parts[1], c2, c1, locations, include, !flip, + recursion, calls, uMin, uMax, t, tMaxNew); + } else { + var parts = Curve.subdivide(v2, 0.5), + u = (uMin + uMax) / 2; + calls = addCurveIntersections( + parts[0], v1, c2, c1, locations, include, !flip, + recursion, calls, uMin, u, tMinNew, tMaxNew); + calls = addCurveIntersections( + parts[1], v1, c2, c1, locations, include, !flip, + recursion, calls, u, uMax, tMinNew, tMaxNew); + } + } else { + if (uDiff === 0 || uDiff >= fatLineEpsilon) { + calls = addCurveIntersections( + v2, v1, c2, c1, locations, include, !flip, + recursion, calls, uMin, uMax, tMinNew, tMaxNew); + } else { + calls = addCurveIntersections( + v1, v2, c1, c2, locations, include, flip, + recursion, calls, tMinNew, tMaxNew, uMin, uMax); + } + } + } + return calls; + } + + function getConvexHull(dq0, dq1, dq2, dq3) { + var p0 = [ 0, dq0 ], + p1 = [ 1 / 3, dq1 ], + p2 = [ 2 / 3, dq2 ], + p3 = [ 1, dq3 ], + dist1 = dq1 - (2 * dq0 + dq3) / 3, + dist2 = dq2 - (dq0 + 2 * dq3) / 3, + hull; + if (dist1 * dist2 < 0) { + hull = [[p0, p1, p3], [p0, p2, p3]]; + } else { + var distRatio = dist1 / dist2; + hull = [ + distRatio >= 2 ? [p0, p1, p3] + : distRatio <= 0.5 ? [p0, p2, p3] + : [p0, p1, p2, p3], + [p0, p3] + ]; + } + return (dist1 || dist2) < 0 ? hull.reverse() : hull; + } + + function clipConvexHull(hullTop, hullBottom, dMin, dMax) { + if (hullTop[0][1] < dMin) { + return clipConvexHullPart(hullTop, true, dMin); + } else if (hullBottom[0][1] > dMax) { + return clipConvexHullPart(hullBottom, false, dMax); + } else { + return hullTop[0][0]; + } + } + + function clipConvexHullPart(part, top, threshold) { + var px = part[0][0], + py = part[0][1]; + for (var i = 1, l = part.length; i < l; i++) { + var qx = part[i][0], + qy = part[i][1]; + if (top ? qy >= threshold : qy <= threshold) { + return qy === threshold ? qx + : px + (threshold - py) * (qx - px) / (qy - py); + } + px = qx; + py = qy; + } + return null; + } + + function getCurveLineIntersections(v, px, py, vx, vy) { + var isZero = Numerical.isZero; + if (isZero(vx) && isZero(vy)) { + var t = Curve.getTimeOf(v, new Point(px, py)); + return t === null ? [] : [t]; + } + var angle = Math.atan2(-vy, vx), + sin = Math.sin(angle), + cos = Math.cos(angle), + rv = [], + roots = []; + for (var i = 0; i < 8; i += 2) { + var x = v[i] - px, + y = v[i + 1] - py; + rv.push( + x * cos - y * sin, + x * sin + y * cos); + } + Curve.solveCubic(rv, 1, 0, roots, 0, 1); + return roots; + } + + function addCurveLineIntersections(v1, v2, c1, c2, locations, include, + flip) { + var x1 = v2[0], y1 = v2[1], + x2 = v2[6], y2 = v2[7], + roots = getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1); + for (var i = 0, l = roots.length; i < l; i++) { + var t1 = roots[i], + p1 = Curve.getPoint(v1, t1), + t2 = Curve.getTimeOf(v2, p1); + if (t2 !== null) { + addLocation(locations, include, + flip ? c2 : c1, flip ? t2 : t1, + flip ? c1 : c2, flip ? t1 : t2); + } + } + } + + function addLineIntersection(v1, v2, c1, c2, locations, include) { + var pt = Line.intersect( + v1[0], v1[1], v1[6], v1[7], + v2[0], v2[1], v2[6], v2[7]); + if (pt) { + addLocation(locations, include, + c1, Curve.getTimeOf(v1, pt), + c2, Curve.getTimeOf(v2, pt)); + } + } + + function getCurveIntersections(v1, v2, c1, c2, locations, include) { + var epsilon = 1e-12, + min = Math.min, + max = Math.max; + + if (max(v1[0], v1[2], v1[4], v1[6]) + epsilon > + min(v2[0], v2[2], v2[4], v2[6]) && + min(v1[0], v1[2], v1[4], v1[6]) - epsilon < + max(v2[0], v2[2], v2[4], v2[6]) && + max(v1[1], v1[3], v1[5], v1[7]) + epsilon > + min(v2[1], v2[3], v2[5], v2[7]) && + min(v1[1], v1[3], v1[5], v1[7]) - epsilon < + max(v2[1], v2[3], v2[5], v2[7])) { + var overlaps = getOverlaps(v1, v2); + if (overlaps) { + for (var i = 0; i < 2; i++) { + var overlap = overlaps[i]; + addLocation(locations, include, + c1, overlap[0], + c2, overlap[1], true); + } + } else { + var straight1 = Curve.isStraight(v1), + straight2 = Curve.isStraight(v2), + straight = straight1 && straight2, + flip = straight1 && !straight2, + before = locations.length; + (straight + ? addLineIntersection + : straight1 || straight2 + ? addCurveLineIntersections + : addCurveIntersections)( + flip ? v2 : v1, flip ? v1 : v2, + flip ? c2 : c1, flip ? c1 : c2, + locations, include, flip, + 0, 0, 0, 1, 0, 1); + if (!straight || locations.length === before) { + for (var i = 0; i < 4; i++) { + var t1 = i >> 1, + t2 = i & 1, + i1 = t1 * 6, + i2 = t2 * 6, + p1 = new Point(v1[i1], v1[i1 + 1]), + p2 = new Point(v2[i2], v2[i2 + 1]); + if (p1.isClose(p2, epsilon)) { + addLocation(locations, include, + c1, t1, + c2, t2); + } + } + } + } + } + return locations; + } + + function getSelfIntersection(v1, c1, locations, include) { + var info = Curve.classify(v1); + if (info.type === 'loop') { + var roots = info.roots; + addLocation(locations, include, + c1, roots[0], + c1, roots[1]); + } + return locations; + } + + function getIntersections(curves1, curves2, include, matrix1, matrix2, + _returnFirst) { + var epsilon = 1e-7, + self = !curves2; + if (self) + curves2 = curves1; + var length1 = curves1.length, + length2 = curves2.length, + values1 = new Array(length1), + values2 = self ? values1 : new Array(length2), + locations = []; + + for (var i = 0; i < length1; i++) { + values1[i] = curves1[i].getValues(matrix1); + } + if (!self) { + for (var i = 0; i < length2; i++) { + values2[i] = curves2[i].getValues(matrix2); + } + } + var boundsCollisions = CollisionDetection.findCurveBoundsCollisions( + values1, values2, epsilon); + for (var index1 = 0; index1 < length1; index1++) { + var curve1 = curves1[index1], + v1 = values1[index1]; + if (self) { + getSelfIntersection(v1, curve1, locations, include); + } + var collisions1 = boundsCollisions[index1]; + if (collisions1) { + for (var j = 0; j < collisions1.length; j++) { + if (_returnFirst && locations.length) + return locations; + var index2 = collisions1[j]; + if (!self || index2 > index1) { + var curve2 = curves2[index2], + v2 = values2[index2]; + getCurveIntersections( + v1, v2, curve1, curve2, locations, include); + } + } + } + } + return locations; + } + + function getOverlaps(v1, v2) { + + function getSquaredLineLength(v) { + var x = v[6] - v[0], + y = v[7] - v[1]; + return x * x + y * y; + } + + var abs = Math.abs, + getDistance = Line.getDistance, + timeEpsilon = 1e-8, + geomEpsilon = 1e-7, + straight1 = Curve.isStraight(v1), + straight2 = Curve.isStraight(v2), + straightBoth = straight1 && straight2, + flip = getSquaredLineLength(v1) < getSquaredLineLength(v2), + l1 = flip ? v2 : v1, + l2 = flip ? v1 : v2, + px = l1[0], py = l1[1], + vx = l1[6] - px, vy = l1[7] - py; + if (getDistance(px, py, vx, vy, l2[0], l2[1], true) < geomEpsilon && + getDistance(px, py, vx, vy, l2[6], l2[7], true) < geomEpsilon) { + if (!straightBoth && + getDistance(px, py, vx, vy, l1[2], l1[3], true) < geomEpsilon && + getDistance(px, py, vx, vy, l1[4], l1[5], true) < geomEpsilon && + getDistance(px, py, vx, vy, l2[2], l2[3], true) < geomEpsilon && + getDistance(px, py, vx, vy, l2[4], l2[5], true) < geomEpsilon) { + straight1 = straight2 = straightBoth = true; + } + } else if (straightBoth) { + return null; + } + if (straight1 ^ straight2) { + return null; + } + + var v = [v1, v2], + pairs = []; + for (var i = 0; i < 4 && pairs.length < 2; i++) { + var i1 = i & 1, + i2 = i1 ^ 1, + t1 = i >> 1, + t2 = Curve.getTimeOf(v[i1], new Point( + v[i2][t1 ? 6 : 0], + v[i2][t1 ? 7 : 1])); + if (t2 != null) { + var pair = i1 ? [t1, t2] : [t2, t1]; + if (!pairs.length || + abs(pair[0] - pairs[0][0]) > timeEpsilon && + abs(pair[1] - pairs[0][1]) > timeEpsilon) { + pairs.push(pair); + } + } + if (i > 2 && !pairs.length) + break; + } + if (pairs.length !== 2) { + pairs = null; + } else if (!straightBoth) { + var o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]), + o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]); + if (abs(o2[2] - o1[2]) > geomEpsilon || + abs(o2[3] - o1[3]) > geomEpsilon || + abs(o2[4] - o1[4]) > geomEpsilon || + abs(o2[5] - o1[5]) > geomEpsilon) + pairs = null; + } + return pairs; + } + + function getTimesWithTangent(v, tangent) { + var x0 = v[0], y0 = v[1], + x1 = v[2], y1 = v[3], + x2 = v[4], y2 = v[5], + x3 = v[6], y3 = v[7], + normalized = tangent.normalize(), + tx = normalized.x, + ty = normalized.y, + ax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0, + ay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0, + bx = 6 * x2 - 12 * x1 + 6 * x0, + by = 6 * y2 - 12 * y1 + 6 * y0, + cx = 3 * x1 - 3 * x0, + cy = 3 * y1 - 3 * y0, + den = 2 * ax * ty - 2 * ay * tx, + times = []; + if (Math.abs(den) < Numerical.CURVETIME_EPSILON) { + var num = ax * cy - ay * cx, + den = ax * by - ay * bx; + if (den != 0) { + var t = -num / den; + if (t >= 0 && t <= 1) times.push(t); + } + } else { + var delta = (bx * bx - 4 * ax * cx) * ty * ty + + (-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty + + (by * by - 4 * ay * cy) * tx * tx, + k = bx * ty - by * tx; + if (delta >= 0 && den != 0) { + var d = Math.sqrt(delta), + t0 = -(k + d) / den, + t1 = (-k + d) / den; + if (t0 >= 0 && t0 <= 1) times.push(t0); + if (t1 >= 0 && t1 <= 1) times.push(t1); + } + } + return times; + } + + return { + getIntersections: function(curve) { + var v1 = this.getValues(), + v2 = curve && curve !== this && curve.getValues(); + return v2 ? getCurveIntersections(v1, v2, this, curve, []) + : getSelfIntersection(v1, this, []); + }, + + statics: { + getOverlaps: getOverlaps, + getIntersections: getIntersections, + getCurveLineIntersections: getCurveLineIntersections, + getTimesWithTangent: getTimesWithTangent + } + }; +}); + +var CurveLocation = Base.extend({ + _class: 'CurveLocation', + + initialize: function CurveLocation(curve, time, point, _overlap, _distance) { + if (time >= 0.99999999) { + var next = curve.getNext(); + if (next) { + time = 0; + curve = next; + } + } + this._setCurve(curve); + this._time = time; + this._point = point || curve.getPointAtTime(time); + this._overlap = _overlap; + this._distance = _distance; + this._intersection = this._next = this._previous = null; + }, + + _setPath: function(path) { + this._path = path; + this._version = path ? path._version : 0; + }, + + _setCurve: function(curve) { + this._setPath(curve._path); + this._curve = curve; + this._segment = null; + this._segment1 = curve._segment1; + this._segment2 = curve._segment2; + }, + + _setSegment: function(segment) { + var curve = segment.getCurve(); + if (curve) { + this._setCurve(curve); + } else { + this._setPath(segment._path); + this._segment1 = segment; + this._segment2 = null; + } + this._segment = segment; + this._time = segment === this._segment1 ? 0 : 1; + this._point = segment._point.clone(); + }, + + getSegment: function() { + var segment = this._segment; + if (!segment) { + var curve = this.getCurve(), + time = this.getTime(); + if (time === 0) { + segment = curve._segment1; + } else if (time === 1) { + segment = curve._segment2; + } else if (time != null) { + segment = curve.getPartLength(0, time) + < curve.getPartLength(time, 1) + ? curve._segment1 + : curve._segment2; + } + this._segment = segment; + } + return segment; + }, + + getCurve: function() { + var path = this._path, + that = this; + if (path && path._version !== this._version) { + this._time = this._offset = this._curveOffset = this._curve = null; + } + + function trySegment(segment) { + var curve = segment && segment.getCurve(); + if (curve && (that._time = curve.getTimeOf(that._point)) != null) { + that._setCurve(curve); + return curve; + } + } + + return this._curve + || trySegment(this._segment) + || trySegment(this._segment1) + || trySegment(this._segment2.getPrevious()); + }, + + getPath: function() { + var curve = this.getCurve(); + return curve && curve._path; + }, + + getIndex: function() { + var curve = this.getCurve(); + return curve && curve.getIndex(); + }, + + getTime: function() { + var curve = this.getCurve(), + time = this._time; + return curve && time == null + ? this._time = curve.getTimeOf(this._point) + : time; + }, + + getParameter: '#getTime', + + getPoint: function() { + return this._point; + }, + + getOffset: function() { + var offset = this._offset; + if (offset == null) { + offset = 0; + var path = this.getPath(), + index = this.getIndex(); + if (path && index != null) { + var curves = path.getCurves(); + for (var i = 0; i < index; i++) + offset += curves[i].getLength(); + } + this._offset = offset += this.getCurveOffset(); + } + return offset; + }, + + getCurveOffset: function() { + var offset = this._curveOffset; + if (offset == null) { + var curve = this.getCurve(), + time = this.getTime(); + this._curveOffset = offset = time != null && curve + && curve.getPartLength(0, time); + } + return offset; + }, + + getIntersection: function() { + return this._intersection; + }, + + getDistance: function() { + return this._distance; + }, + + divide: function() { + var curve = this.getCurve(), + res = curve && curve.divideAtTime(this.getTime()); + if (res) { + this._setSegment(res._segment1); + } + return res; + }, + + split: function() { + var curve = this.getCurve(), + path = curve._path, + res = curve && curve.splitAtTime(this.getTime()); + if (res) { + this._setSegment(path.getLastSegment()); + } + return res; + }, + + equals: function(loc, _ignoreOther) { + var res = this === loc; + if (!res && loc instanceof CurveLocation) { + var c1 = this.getCurve(), + c2 = loc.getCurve(), + p1 = c1._path, + p2 = c2._path; + if (p1 === p2) { + var abs = Math.abs, + epsilon = 1e-7, + diff = abs(this.getOffset() - loc.getOffset()), + i1 = !_ignoreOther && this._intersection, + i2 = !_ignoreOther && loc._intersection; + res = (diff < epsilon + || p1 && abs(p1.getLength() - diff) < epsilon) + && (!i1 && !i2 || i1 && i2 && i1.equals(i2, true)); + } + } + return res; + }, + + toString: function() { + var parts = [], + point = this.getPoint(), + f = Formatter.instance; + if (point) + parts.push('point: ' + point); + var index = this.getIndex(); + if (index != null) + parts.push('index: ' + index); + var time = this.getTime(); + if (time != null) + parts.push('time: ' + f.number(time)); + if (this._distance != null) + parts.push('distance: ' + f.number(this._distance)); + return '{ ' + parts.join(', ') + ' }'; + }, + + isTouching: function() { + var inter = this._intersection; + if (inter && this.getTangent().isCollinear(inter.getTangent())) { + var curve1 = this.getCurve(), + curve2 = inter.getCurve(); + return !(curve1.isStraight() && curve2.isStraight() + && curve1.getLine().intersect(curve2.getLine())); + } + return false; + }, + + isCrossing: function() { + var inter = this._intersection; + if (!inter) + return false; + var t1 = this.getTime(), + t2 = inter.getTime(), + tMin = 1e-8, + tMax = 1 - tMin, + t1Inside = t1 >= tMin && t1 <= tMax, + t2Inside = t2 >= tMin && t2 <= tMax; + if (t1Inside && t2Inside) + return !this.isTouching(); + var c2 = this.getCurve(), + c1 = c2 && t1 < tMin ? c2.getPrevious() : c2, + c4 = inter.getCurve(), + c3 = c4 && t2 < tMin ? c4.getPrevious() : c4; + if (t1 > tMax) + c2 = c2.getNext(); + if (t2 > tMax) + c4 = c4.getNext(); + if (!c1 || !c2 || !c3 || !c4) + return false; + + var offsets = []; + + function addOffsets(curve, end) { + var v = curve.getValues(), + roots = Curve.classify(v).roots || Curve.getPeaks(v), + count = roots.length, + offset = Curve.getLength(v, + end && count ? roots[count - 1] : 0, + !end && count ? roots[0] : 1); + offsets.push(count ? offset : offset / 32); + } + + function isInRange(angle, min, max) { + return min < max + ? angle > min && angle < max + : angle > min || angle < max; + } + + if (!t1Inside) { + addOffsets(c1, true); + addOffsets(c2, false); + } + if (!t2Inside) { + addOffsets(c3, true); + addOffsets(c4, false); + } + var pt = this.getPoint(), + offset = Math.min.apply(Math, offsets), + v2 = t1Inside ? c2.getTangentAtTime(t1) + : c2.getPointAt(offset).subtract(pt), + v1 = t1Inside ? v2.negate() + : c1.getPointAt(-offset).subtract(pt), + v4 = t2Inside ? c4.getTangentAtTime(t2) + : c4.getPointAt(offset).subtract(pt), + v3 = t2Inside ? v4.negate() + : c3.getPointAt(-offset).subtract(pt), + a1 = v1.getAngle(), + a2 = v2.getAngle(), + a3 = v3.getAngle(), + a4 = v4.getAngle(); + return !!(t1Inside + ? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) && + (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3)) + : (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) && + (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1))); + }, + + hasOverlap: function() { + return !!this._overlap; + } +}, Base.each(Curve._evaluateMethods, function(name) { + var get = name + 'At'; + this[name] = function() { + var curve = this.getCurve(), + time = this.getTime(); + return time != null && curve && curve[get](time, true); + }; +}, { + preserve: true +}), +new function() { + + function insert(locations, loc, merge) { + var length = locations.length, + l = 0, + r = length - 1; + + function search(index, dir) { + for (var i = index + dir; i >= -1 && i <= length; i += dir) { + var loc2 = locations[((i % length) + length) % length]; + if (!loc.getPoint().isClose(loc2.getPoint(), + 1e-7)) + break; + if (loc.equals(loc2)) + return loc2; + } + return null; + } + + while (l <= r) { + var m = (l + r) >>> 1, + loc2 = locations[m], + found; + if (merge && (found = loc.equals(loc2) ? loc2 + : (search(m, -1) || search(m, 1)))) { + if (loc._overlap) { + found._overlap = found._intersection._overlap = true; + } + return found; + } + var path1 = loc.getPath(), + path2 = loc2.getPath(), + diff = path1 !== path2 + ? path1._id - path2._id + : (loc.getIndex() + loc.getTime()) + - (loc2.getIndex() + loc2.getTime()); + if (diff < 0) { + r = m - 1; + } else { + l = m + 1; + } + } + locations.splice(l, 0, loc); + return loc; + } + + return { statics: { + insert: insert, + + expand: function(locations) { + var expanded = locations.slice(); + for (var i = locations.length - 1; i >= 0; i--) { + insert(expanded, locations[i]._intersection, false); + } + return expanded; + } + }}; +}); + +var PathItem = Item.extend({ + _class: 'PathItem', + _selectBounds: false, + _canScaleStroke: true, + beans: true, + + initialize: function PathItem() { + }, + + statics: { + create: function(arg) { + var data, + segments, + compound; + if (Base.isPlainObject(arg)) { + segments = arg.segments; + data = arg.pathData; + } else if (Array.isArray(arg)) { + segments = arg; + } else if (typeof arg === 'string') { + data = arg; + } + if (segments) { + var first = segments[0]; + compound = first && Array.isArray(first[0]); + } else if (data) { + compound = (data.match(/m/gi) || []).length > 1 + || /z\s*\S+/i.test(data); + } + var ctor = compound ? CompoundPath : Path; + return new ctor(arg); + } + }, + + _asPathItem: function() { + return this; + }, + + isClockwise: function() { + return this.getArea() >= 0; + }, + + setClockwise: function(clockwise) { + if (this.isClockwise() != (clockwise = !!clockwise)) + this.reverse(); + }, + + setPathData: function(data) { + + var parts = data && data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig), + coords, + relative = false, + previous, + control, + current = new Point(), + start = new Point(); + + function getCoord(index, coord) { + var val = +coords[index]; + if (relative) + val += current[coord]; + return val; + } + + function getPoint(index) { + return new Point( + getCoord(index, 'x'), + getCoord(index + 1, 'y') + ); + } + + this.clear(); + + for (var i = 0, l = parts && parts.length; i < l; i++) { + var part = parts[i], + command = part[0], + lower = command.toLowerCase(); + coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); + var length = coords && coords.length; + relative = command === lower; + if (previous === 'z' && !/[mz]/.test(lower)) + this.moveTo(current); + switch (lower) { + case 'm': + case 'l': + var move = lower === 'm'; + for (var j = 0; j < length; j += 2) { + this[move ? 'moveTo' : 'lineTo'](current = getPoint(j)); + if (move) { + start = current; + move = false; + } + } + control = current; + break; + case 'h': + case 'v': + var coord = lower === 'h' ? 'x' : 'y'; + current = current.clone(); + for (var j = 0; j < length; j++) { + current[coord] = getCoord(j, coord); + this.lineTo(current); + } + control = current; + break; + case 'c': + for (var j = 0; j < length; j += 6) { + this.cubicCurveTo( + getPoint(j), + control = getPoint(j + 2), + current = getPoint(j + 4)); + } + break; + case 's': + for (var j = 0; j < length; j += 4) { + this.cubicCurveTo( + /[cs]/.test(previous) + ? current.multiply(2).subtract(control) + : current, + control = getPoint(j), + current = getPoint(j + 2)); + previous = lower; + } + break; + case 'q': + for (var j = 0; j < length; j += 4) { + this.quadraticCurveTo( + control = getPoint(j), + current = getPoint(j + 2)); + } + break; + case 't': + for (var j = 0; j < length; j += 2) { + this.quadraticCurveTo( + control = (/[qt]/.test(previous) + ? current.multiply(2).subtract(control) + : current), + current = getPoint(j)); + previous = lower; + } + break; + case 'a': + for (var j = 0; j < length; j += 7) { + this.arcTo(current = getPoint(j + 5), + new Size(+coords[j], +coords[j + 1]), + +coords[j + 2], +coords[j + 4], +coords[j + 3]); + } + break; + case 'z': + this.closePath(1e-12); + current = start; + break; + } + previous = lower; + } + }, + + _canComposite: function() { + return !(this.hasFill() && this.hasStroke()); + }, + + _contains: function(point) { + var winding = point.isInside( + this.getBounds({ internal: true, handle: true })) + ? this._getWinding(point) + : {}; + return winding.onPath || !!(this.getFillRule() === 'evenodd' + ? winding.windingL & 1 || winding.windingR & 1 + : winding.winding); + }, + + getIntersections: function(path, include, _matrix, _returnFirst) { + var self = this === path || !path, + matrix1 = this._matrix._orNullIfIdentity(), + matrix2 = self ? matrix1 + : (_matrix || path._matrix)._orNullIfIdentity(); + return self || this.getBounds(matrix1).intersects( + path.getBounds(matrix2), 1e-12) + ? Curve.getIntersections( + this.getCurves(), !self && path.getCurves(), include, + matrix1, matrix2, _returnFirst) + : []; + }, + + getCrossings: function(path) { + return this.getIntersections(path, function(inter) { + return inter.isCrossing(); + }); + }, + + getNearestLocation: function() { + var point = Point.read(arguments), + curves = this.getCurves(), + minDist = Infinity, + minLoc = null; + for (var i = 0, l = curves.length; i < l; i++) { + var loc = curves[i].getNearestLocation(point); + if (loc._distance < minDist) { + minDist = loc._distance; + minLoc = loc; + } + } + return minLoc; + }, + + getNearestPoint: function() { + var loc = this.getNearestLocation.apply(this, arguments); + return loc ? loc.getPoint() : loc; + }, + + interpolate: function(from, to, factor) { + var isPath = !this._children, + name = isPath ? '_segments' : '_children', + itemsFrom = from[name], + itemsTo = to[name], + items = this[name]; + if (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) { + throw new Error('Invalid operands in interpolate() call: ' + + from + ', ' + to); + } + var current = items.length, + length = itemsTo.length; + if (current < length) { + var ctor = isPath ? Segment : Path; + for (var i = current; i < length; i++) { + this.add(new ctor()); + } + } else if (current > length) { + this[isPath ? 'removeSegments' : 'removeChildren'](length, current); + } + for (var i = 0; i < length; i++) { + items[i].interpolate(itemsFrom[i], itemsTo[i], factor); + } + if (isPath) { + this.setClosed(from._closed); + this._changed(9); + } + }, + + compare: function(path) { + var ok = false; + if (path) { + var paths1 = this._children || [this], + paths2 = path._children ? path._children.slice() : [path], + length1 = paths1.length, + length2 = paths2.length, + matched = [], + count = 0; + ok = true; + var boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON); + for (var i1 = length1 - 1; i1 >= 0 && ok; i1--) { + var path1 = paths1[i1]; + ok = false; + var pathBoundsOverlaps = boundsOverlaps[i1]; + if (pathBoundsOverlaps) { + for (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) { + if (path1.compare(paths2[pathBoundsOverlaps[i2]])) { + if (!matched[pathBoundsOverlaps[i2]]) { + matched[pathBoundsOverlaps[i2]] = true; + count++; + } + ok = true; + } + } + } + } + ok = ok && count === length2; + } + return ok; + }, + +}); + +var Path = PathItem.extend({ + _class: 'Path', + _serializeFields: { + segments: [], + closed: false + }, + + initialize: function Path(arg) { + this._closed = false; + this._segments = []; + this._version = 0; + var args = arguments, + segments = Array.isArray(arg) + ? typeof arg[0] === 'object' + ? arg + : args + : arg && (arg.size === undefined && (arg.x !== undefined + || arg.point !== undefined)) + ? args + : null; + if (segments && segments.length > 0) { + this.setSegments(segments); + } else { + this._curves = undefined; + this._segmentSelection = 0; + if (!segments && typeof arg === 'string') { + this.setPathData(arg); + arg = null; + } + } + this._initialize(!segments && arg); + }, + + _equals: function(item) { + return this._closed === item._closed + && Base.equals(this._segments, item._segments); + }, + + copyContent: function(source) { + this.setSegments(source._segments); + this._closed = source._closed; + }, + + _changed: function _changed(flags) { + _changed.base.call(this, flags); + if (flags & 8) { + this._length = this._area = undefined; + if (flags & 32) { + this._version++; + } else if (this._curves) { + for (var i = 0, l = this._curves.length; i < l; i++) + this._curves[i]._changed(); + } + } else if (flags & 64) { + this._bounds = undefined; + } + }, + + getStyle: function() { + var parent = this._parent; + return (parent instanceof CompoundPath ? parent : this)._style; + }, + + getSegments: function() { + return this._segments; + }, + + setSegments: function(segments) { + var fullySelected = this.isFullySelected(), + length = segments && segments.length; + this._segments.length = 0; + this._segmentSelection = 0; + this._curves = undefined; + if (length) { + var last = segments[length - 1]; + if (typeof last === 'boolean') { + this.setClosed(last); + length--; + } + this._add(Segment.readList(segments, 0, {}, length)); + } + if (fullySelected) + this.setFullySelected(true); + }, + + getFirstSegment: function() { + return this._segments[0]; + }, + + getLastSegment: function() { + return this._segments[this._segments.length - 1]; + }, + + getCurves: function() { + var curves = this._curves, + segments = this._segments; + if (!curves) { + var length = this._countCurves(); + curves = this._curves = new Array(length); + for (var i = 0; i < length; i++) + curves[i] = new Curve(this, segments[i], + segments[i + 1] || segments[0]); + } + return curves; + }, + + getFirstCurve: function() { + return this.getCurves()[0]; + }, + + getLastCurve: function() { + var curves = this.getCurves(); + return curves[curves.length - 1]; + }, + + isClosed: function() { + return this._closed; + }, + + setClosed: function(closed) { + if (this._closed != (closed = !!closed)) { + this._closed = closed; + if (this._curves) { + var length = this._curves.length = this._countCurves(); + if (closed) + this._curves[length - 1] = new Curve(this, + this._segments[length - 1], this._segments[0]); + } + this._changed(41); + } + } +}, { + beans: true, + + getPathData: function(_matrix, _precision) { + var segments = this._segments, + length = segments.length, + f = new Formatter(_precision), + coords = new Array(6), + first = true, + curX, curY, + prevX, prevY, + inX, inY, + outX, outY, + parts = []; + + function addSegment(segment, skipLine) { + segment._transformCoordinates(_matrix, coords); + curX = coords[0]; + curY = coords[1]; + if (first) { + parts.push('M' + f.pair(curX, curY)); + first = false; + } else { + inX = coords[2]; + inY = coords[3]; + if (inX === curX && inY === curY + && outX === prevX && outY === prevY) { + if (!skipLine) { + var dx = curX - prevX, + dy = curY - prevY; + parts.push( + dx === 0 ? 'v' + f.number(dy) + : dy === 0 ? 'h' + f.number(dx) + : 'l' + f.pair(dx, dy)); + } + } else { + parts.push('c' + f.pair(outX - prevX, outY - prevY) + + ' ' + f.pair( inX - prevX, inY - prevY) + + ' ' + f.pair(curX - prevX, curY - prevY)); + } + } + prevX = curX; + prevY = curY; + outX = coords[4]; + outY = coords[5]; + } + + if (!length) + return ''; + + for (var i = 0; i < length; i++) + addSegment(segments[i]); + if (this._closed && length > 0) { + addSegment(segments[0], true); + parts.push('z'); + } + return parts.join(''); + }, + + isEmpty: function() { + return !this._segments.length; + }, + + _transformContent: function(matrix) { + var segments = this._segments, + coords = new Array(6); + for (var i = 0, l = segments.length; i < l; i++) + segments[i]._transformCoordinates(matrix, coords, true); + return true; + }, + + _add: function(segs, index) { + var segments = this._segments, + curves = this._curves, + amount = segs.length, + append = index == null, + index = append ? segments.length : index; + for (var i = 0; i < amount; i++) { + var segment = segs[i]; + if (segment._path) + segment = segs[i] = segment.clone(); + segment._path = this; + segment._index = index + i; + if (segment._selection) + this._updateSelection(segment, 0, segment._selection); + } + if (append) { + Base.push(segments, segs); + } else { + segments.splice.apply(segments, [index, 0].concat(segs)); + for (var i = index + amount, l = segments.length; i < l; i++) + segments[i]._index = i; + } + if (curves) { + var total = this._countCurves(), + start = index > 0 && index + amount - 1 === total ? index - 1 + : index, + insert = start, + end = Math.min(start + amount, total); + if (segs._curves) { + curves.splice.apply(curves, [start, 0].concat(segs._curves)); + insert += segs._curves.length; + } + for (var i = insert; i < end; i++) + curves.splice(i, 0, new Curve(this, null, null)); + this._adjustCurves(start, end); + } + this._changed(41); + return segs; + }, + + _adjustCurves: function(start, end) { + var segments = this._segments, + curves = this._curves, + curve; + for (var i = start; i < end; i++) { + curve = curves[i]; + curve._path = this; + curve._segment1 = segments[i]; + curve._segment2 = segments[i + 1] || segments[0]; + curve._changed(); + } + if (curve = curves[this._closed && !start ? segments.length - 1 + : start - 1]) { + curve._segment2 = segments[start] || segments[0]; + curve._changed(); + } + if (curve = curves[end]) { + curve._segment1 = segments[end]; + curve._changed(); + } + }, + + _countCurves: function() { + var length = this._segments.length; + return !this._closed && length > 0 ? length - 1 : length; + }, + + add: function(segment1 ) { + var args = arguments; + return args.length > 1 && typeof segment1 !== 'number' + ? this._add(Segment.readList(args)) + : this._add([ Segment.read(args) ])[0]; + }, + + insert: function(index, segment1 ) { + var args = arguments; + return args.length > 2 && typeof segment1 !== 'number' + ? this._add(Segment.readList(args, 1), index) + : this._add([ Segment.read(args, 1) ], index)[0]; + }, + + addSegment: function() { + return this._add([ Segment.read(arguments) ])[0]; + }, + + insertSegment: function(index ) { + return this._add([ Segment.read(arguments, 1) ], index)[0]; + }, + + addSegments: function(segments) { + return this._add(Segment.readList(segments)); + }, + + insertSegments: function(index, segments) { + return this._add(Segment.readList(segments), index); + }, + + removeSegment: function(index) { + return this.removeSegments(index, index + 1)[0] || null; + }, + + removeSegments: function(start, end, _includeCurves) { + start = start || 0; + end = Base.pick(end, this._segments.length); + var segments = this._segments, + curves = this._curves, + count = segments.length, + removed = segments.splice(start, end - start), + amount = removed.length; + if (!amount) + return removed; + for (var i = 0; i < amount; i++) { + var segment = removed[i]; + if (segment._selection) + this._updateSelection(segment, segment._selection, 0); + segment._index = segment._path = null; + } + for (var i = start, l = segments.length; i < l; i++) + segments[i]._index = i; + if (curves) { + var index = start > 0 && end === count + (this._closed ? 1 : 0) + ? start - 1 + : start, + curves = curves.splice(index, amount); + for (var i = curves.length - 1; i >= 0; i--) + curves[i]._path = null; + if (_includeCurves) + removed._curves = curves.slice(1); + this._adjustCurves(index, index); + } + this._changed(41); + return removed; + }, + + clear: '#removeSegments', + + hasHandles: function() { + var segments = this._segments; + for (var i = 0, l = segments.length; i < l; i++) { + if (segments[i].hasHandles()) + return true; + } + return false; + }, + + clearHandles: function() { + var segments = this._segments; + for (var i = 0, l = segments.length; i < l; i++) + segments[i].clearHandles(); + }, + + getLength: function() { + if (this._length == null) { + var curves = this.getCurves(), + length = 0; + for (var i = 0, l = curves.length; i < l; i++) + length += curves[i].getLength(); + this._length = length; + } + return this._length; + }, + + getArea: function() { + var area = this._area; + if (area == null) { + var segments = this._segments, + closed = this._closed; + area = 0; + for (var i = 0, l = segments.length; i < l; i++) { + var last = i + 1 === l; + area += Curve.getArea(Curve.getValues( + segments[i], segments[last ? 0 : i + 1], + null, last && !closed)); + } + this._area = area; + } + return area; + }, + + isFullySelected: function() { + var length = this._segments.length; + return this.isSelected() && length > 0 && this._segmentSelection + === length * 7; + }, + + setFullySelected: function(selected) { + if (selected) + this._selectSegments(true); + this.setSelected(selected); + }, + + setSelection: function setSelection(selection) { + if (!(selection & 1)) + this._selectSegments(false); + setSelection.base.call(this, selection); + }, + + _selectSegments: function(selected) { + var segments = this._segments, + length = segments.length, + selection = selected ? 7 : 0; + this._segmentSelection = selection * length; + for (var i = 0; i < length; i++) + segments[i]._selection = selection; + }, + + _updateSelection: function(segment, oldSelection, newSelection) { + segment._selection = newSelection; + var selection = this._segmentSelection += newSelection - oldSelection; + if (selection > 0) + this.setSelected(true); + }, + + divideAt: function(location) { + var loc = this.getLocationAt(location), + curve; + return loc && (curve = loc.getCurve().divideAt(loc.getCurveOffset())) + ? curve._segment1 + : null; + }, + + splitAt: function(location) { + var loc = this.getLocationAt(location), + index = loc && loc.index, + time = loc && loc.time, + tMin = 1e-8, + tMax = 1 - tMin; + if (time > tMax) { + index++; + time = 0; + } + var curves = this.getCurves(); + if (index >= 0 && index < curves.length) { + if (time >= tMin) { + curves[index++].divideAtTime(time); + } + var segs = this.removeSegments(index, this._segments.length, true), + path; + if (this._closed) { + this.setClosed(false); + path = this; + } else { + path = new Path(Item.NO_INSERT); + path.insertAbove(this); + path.copyAttributes(this); + } + path._add(segs, 0); + this.addSegment(segs[0]); + return path; + } + return null; + }, + + split: function(index, time) { + var curve, + location = time === undefined ? index + : (curve = this.getCurves()[index]) + && curve.getLocationAtTime(time); + return location != null ? this.splitAt(location) : null; + }, + + join: function(path, tolerance) { + var epsilon = tolerance || 0; + if (path && path !== this) { + var segments = path._segments, + last1 = this.getLastSegment(), + last2 = path.getLastSegment(); + if (!last2) + return this; + if (last1 && last1._point.isClose(last2._point, epsilon)) + path.reverse(); + var first2 = path.getFirstSegment(); + if (last1 && last1._point.isClose(first2._point, epsilon)) { + last1.setHandleOut(first2._handleOut); + this._add(segments.slice(1)); + } else { + var first1 = this.getFirstSegment(); + if (first1 && first1._point.isClose(first2._point, epsilon)) + path.reverse(); + last2 = path.getLastSegment(); + if (first1 && first1._point.isClose(last2._point, epsilon)) { + first1.setHandleIn(last2._handleIn); + this._add(segments.slice(0, segments.length - 1), 0); + } else { + this._add(segments.slice()); + } + } + if (path._closed) + this._add([segments[0]]); + path.remove(); + } + var first = this.getFirstSegment(), + last = this.getLastSegment(); + if (first !== last && first._point.isClose(last._point, epsilon)) { + first.setHandleIn(last._handleIn); + last.remove(); + this.setClosed(true); + } + return this; + }, + + reduce: function(options) { + var curves = this.getCurves(), + simplify = options && options.simplify, + tolerance = simplify ? 1e-7 : 0; + for (var i = curves.length - 1; i >= 0; i--) { + var curve = curves[i]; + if (!curve.hasHandles() && (!curve.hasLength(tolerance) + || simplify && curve.isCollinear(curve.getNext()))) + curve.remove(); + } + return this; + }, + + reverse: function() { + this._segments.reverse(); + for (var i = 0, l = this._segments.length; i < l; i++) { + var segment = this._segments[i]; + var handleIn = segment._handleIn; + segment._handleIn = segment._handleOut; + segment._handleOut = handleIn; + segment._index = i; + } + this._curves = null; + this._changed(9); + }, + + flatten: function(flatness) { + var flattener = new PathFlattener(this, flatness || 0.25, 256, true), + parts = flattener.parts, + length = parts.length, + segments = []; + for (var i = 0; i < length; i++) { + segments.push(new Segment(parts[i].curve.slice(0, 2))); + } + if (!this._closed && length > 0) { + segments.push(new Segment(parts[length - 1].curve.slice(6))); + } + this.setSegments(segments); + }, + + simplify: function(tolerance) { + var segments = new PathFitter(this).fit(tolerance || 2.5); + if (segments) + this.setSegments(segments); + return !!segments; + }, + + smooth: function(options) { + var that = this, + opts = options || {}, + type = opts.type || 'asymmetric', + segments = this._segments, + length = segments.length, + closed = this._closed; + + function getIndex(value, _default) { + var index = value && value.index; + if (index != null) { + var path = value.path; + if (path && path !== that) + throw new Error(value._class + ' ' + index + ' of ' + path + + ' is not part of ' + that); + if (_default && value instanceof Curve) + index++; + } else { + index = typeof value === 'number' ? value : _default; + } + return Math.min(index < 0 && closed + ? index % length + : index < 0 ? index + length : index, length - 1); + } + + var loop = closed && opts.from === undefined && opts.to === undefined, + from = getIndex(opts.from, 0), + to = getIndex(opts.to, length - 1); + + if (from > to) { + if (closed) { + from -= length; + } else { + var tmp = from; + from = to; + to = tmp; + } + } + if (/^(?:asymmetric|continuous)$/.test(type)) { + var asymmetric = type === 'asymmetric', + min = Math.min, + amount = to - from + 1, + n = amount - 1, + padding = loop ? min(amount, 4) : 1, + paddingLeft = padding, + paddingRight = padding, + knots = []; + if (!closed) { + paddingLeft = min(1, from); + paddingRight = min(1, length - to - 1); + } + n += paddingLeft + paddingRight; + if (n <= 1) + return; + for (var i = 0, j = from - paddingLeft; i <= n; i++, j++) { + knots[i] = segments[(j < 0 ? j + length : j) % length]._point; + } + + var x = knots[0]._x + 2 * knots[1]._x, + y = knots[0]._y + 2 * knots[1]._y, + f = 2, + n_1 = n - 1, + rx = [x], + ry = [y], + rf = [f], + px = [], + py = []; + for (var i = 1; i < n; i++) { + var internal = i < n_1, + a = internal ? 1 : asymmetric ? 1 : 2, + b = internal ? 4 : asymmetric ? 2 : 7, + u = internal ? 4 : asymmetric ? 3 : 8, + v = internal ? 2 : asymmetric ? 0 : 1, + m = a / f; + f = rf[i] = b - m; + x = rx[i] = u * knots[i]._x + v * knots[i + 1]._x - m * x; + y = ry[i] = u * knots[i]._y + v * knots[i + 1]._y - m * y; + } + + px[n_1] = rx[n_1] / rf[n_1]; + py[n_1] = ry[n_1] / rf[n_1]; + for (var i = n - 2; i >= 0; i--) { + px[i] = (rx[i] - px[i + 1]) / rf[i]; + py[i] = (ry[i] - py[i + 1]) / rf[i]; + } + px[n] = (3 * knots[n]._x - px[n_1]) / 2; + py[n] = (3 * knots[n]._y - py[n_1]) / 2; + + for (var i = paddingLeft, max = n - paddingRight, j = from; + i <= max; i++, j++) { + var segment = segments[j < 0 ? j + length : j], + pt = segment._point, + hx = px[i] - pt._x, + hy = py[i] - pt._y; + if (loop || i < max) + segment.setHandleOut(hx, hy); + if (loop || i > paddingLeft) + segment.setHandleIn(-hx, -hy); + } + } else { + for (var i = from; i <= to; i++) { + segments[i < 0 ? i + length : i].smooth(opts, + !loop && i === from, !loop && i === to); + } + } + }, + + toShape: function(insert) { + if (!this._closed) + return null; + + var segments = this._segments, + type, + size, + radius, + topCenter; + + function isCollinear(i, j) { + var seg1 = segments[i], + seg2 = seg1.getNext(), + seg3 = segments[j], + seg4 = seg3.getNext(); + return seg1._handleOut.isZero() && seg2._handleIn.isZero() + && seg3._handleOut.isZero() && seg4._handleIn.isZero() + && seg2._point.subtract(seg1._point).isCollinear( + seg4._point.subtract(seg3._point)); + } + + function isOrthogonal(i) { + var seg2 = segments[i], + seg1 = seg2.getPrevious(), + seg3 = seg2.getNext(); + return seg1._handleOut.isZero() && seg2._handleIn.isZero() + && seg2._handleOut.isZero() && seg3._handleIn.isZero() + && seg2._point.subtract(seg1._point).isOrthogonal( + seg3._point.subtract(seg2._point)); + } + + function isArc(i) { + var seg1 = segments[i], + seg2 = seg1.getNext(), + handle1 = seg1._handleOut, + handle2 = seg2._handleIn, + kappa = 0.5522847498307936; + if (handle1.isOrthogonal(handle2)) { + var pt1 = seg1._point, + pt2 = seg2._point, + corner = new Line(pt1, handle1, true).intersect( + new Line(pt2, handle2, true), true); + return corner && Numerical.isZero(handle1.getLength() / + corner.subtract(pt1).getLength() - kappa) + && Numerical.isZero(handle2.getLength() / + corner.subtract(pt2).getLength() - kappa); + } + return false; + } + + function getDistance(i, j) { + return segments[i]._point.getDistance(segments[j]._point); + } + + if (!this.hasHandles() && segments.length === 4 + && isCollinear(0, 2) && isCollinear(1, 3) && isOrthogonal(1)) { + type = Shape.Rectangle; + size = new Size(getDistance(0, 3), getDistance(0, 1)); + topCenter = segments[1]._point.add(segments[2]._point).divide(2); + } else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4) + && isArc(6) && isCollinear(1, 5) && isCollinear(3, 7)) { + type = Shape.Rectangle; + size = new Size(getDistance(1, 6), getDistance(0, 3)); + radius = size.subtract(new Size(getDistance(0, 7), + getDistance(1, 2))).divide(2); + topCenter = segments[3]._point.add(segments[4]._point).divide(2); + } else if (segments.length === 4 + && isArc(0) && isArc(1) && isArc(2) && isArc(3)) { + if (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) { + type = Shape.Circle; + radius = getDistance(0, 2) / 2; + } else { + type = Shape.Ellipse; + radius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2); + } + topCenter = segments[1]._point; + } + + if (type) { + var center = this.getPosition(true), + shape = new type({ + center: center, + size: size, + radius: radius, + insert: false + }); + shape.copyAttributes(this, true); + shape._matrix.prepend(this._matrix); + shape.rotate(topCenter.subtract(center).getAngle() + 90); + if (insert === undefined || insert) + shape.insertAbove(this); + return shape; + } + return null; + }, + + toPath: '#clone', + + compare: function compare(path) { + if (!path || path instanceof CompoundPath) + return compare.base.call(this, path); + var curves1 = this.getCurves(), + curves2 = path.getCurves(), + length1 = curves1.length, + length2 = curves2.length; + if (!length1 || !length2) { + return length1 == length2; + } + var v1 = curves1[0].getValues(), + values2 = [], + pos1 = 0, pos2, + end1 = 0, end2; + for (var i = 0; i < length2; i++) { + var v2 = curves2[i].getValues(); + values2.push(v2); + var overlaps = Curve.getOverlaps(v1, v2); + if (overlaps) { + pos2 = !i && overlaps[0][0] > 0 ? length2 - 1 : i; + end2 = overlaps[0][1]; + break; + } + } + var abs = Math.abs, + epsilon = 1e-8, + v2 = values2[pos2], + start2; + while (v1 && v2) { + var overlaps = Curve.getOverlaps(v1, v2); + if (overlaps) { + var t1 = overlaps[0][0]; + if (abs(t1 - end1) < epsilon) { + end1 = overlaps[1][0]; + if (end1 === 1) { + v1 = ++pos1 < length1 ? curves1[pos1].getValues() : null; + end1 = 0; + } + var t2 = overlaps[0][1]; + if (abs(t2 - end2) < epsilon) { + if (!start2) + start2 = [pos2, t2]; + end2 = overlaps[1][1]; + if (end2 === 1) { + if (++pos2 >= length2) + pos2 = 0; + v2 = values2[pos2] || curves2[pos2].getValues(); + end2 = 0; + } + if (!v1) { + return start2[0] === pos2 && start2[1] === end2; + } + continue; + } + } + } + break; + } + return false; + }, + + _hitTestSelf: function(point, options, viewMatrix, strokeMatrix) { + var that = this, + style = this.getStyle(), + segments = this._segments, + numSegments = segments.length, + closed = this._closed, + tolerancePadding = options._tolerancePadding, + strokePadding = tolerancePadding, + join, cap, miterLimit, + area, loc, res, + hitStroke = options.stroke && style.hasStroke(), + hitFill = options.fill && style.hasFill(), + hitCurves = options.curves, + strokeRadius = hitStroke + ? style.getStrokeWidth() / 2 + : hitFill && options.tolerance > 0 || hitCurves + ? 0 : null; + if (strokeRadius !== null) { + if (strokeRadius > 0) { + join = style.getStrokeJoin(); + cap = style.getStrokeCap(); + miterLimit = style.getMiterLimit(); + strokePadding = strokePadding.add( + Path._getStrokePadding(strokeRadius, strokeMatrix)); + } else { + join = cap = 'round'; + } + } + + function isCloseEnough(pt, padding) { + return point.subtract(pt).divide(padding).length <= 1; + } + + function checkSegmentPoint(seg, pt, name) { + if (!options.selected || pt.isSelected()) { + var anchor = seg._point; + if (pt !== anchor) + pt = pt.add(anchor); + if (isCloseEnough(pt, strokePadding)) { + return new HitResult(name, that, { + segment: seg, + point: pt + }); + } + } + } + + function checkSegmentPoints(seg, ends) { + return (ends || options.segments) + && checkSegmentPoint(seg, seg._point, 'segment') + || (!ends && options.handles) && ( + checkSegmentPoint(seg, seg._handleIn, 'handle-in') || + checkSegmentPoint(seg, seg._handleOut, 'handle-out')); + } + + function addToArea(point) { + area.add(point); + } + + function checkSegmentStroke(segment) { + var isJoin = closed || segment._index > 0 + && segment._index < numSegments - 1; + if ((isJoin ? join : cap) === 'round') { + return isCloseEnough(segment._point, strokePadding); + } else { + area = new Path({ internal: true, closed: true }); + if (isJoin) { + if (!segment.isSmooth()) { + Path._addBevelJoin(segment, join, strokeRadius, + miterLimit, null, strokeMatrix, addToArea, true); + } + } else if (cap === 'square') { + Path._addSquareCap(segment, cap, strokeRadius, null, + strokeMatrix, addToArea, true); + } + if (!area.isEmpty()) { + var loc; + return area.contains(point) + || (loc = area.getNearestLocation(point)) + && isCloseEnough(loc.getPoint(), tolerancePadding); + } + } + } + + if (options.ends && !options.segments && !closed) { + if (res = checkSegmentPoints(segments[0], true) + || checkSegmentPoints(segments[numSegments - 1], true)) + return res; + } else if (options.segments || options.handles) { + for (var i = 0; i < numSegments; i++) + if (res = checkSegmentPoints(segments[i])) + return res; + } + if (strokeRadius !== null) { + loc = this.getNearestLocation(point); + if (loc) { + var time = loc.getTime(); + if (time === 0 || time === 1 && numSegments > 1) { + if (!checkSegmentStroke(loc.getSegment())) + loc = null; + } else if (!isCloseEnough(loc.getPoint(), strokePadding)) { + loc = null; + } + } + if (!loc && join === 'miter' && numSegments > 1) { + for (var i = 0; i < numSegments; i++) { + var segment = segments[i]; + if (point.getDistance(segment._point) + <= miterLimit * strokeRadius + && checkSegmentStroke(segment)) { + loc = segment.getLocation(); + break; + } + } + } + } + return !loc && hitFill && this._contains(point) + || loc && !hitStroke && !hitCurves + ? new HitResult('fill', this) + : loc + ? new HitResult(hitStroke ? 'stroke' : 'curve', this, { + location: loc, + point: loc.getPoint() + }) + : null; + } + +}, Base.each(Curve._evaluateMethods, + function(name) { + this[name + 'At'] = function(offset) { + var loc = this.getLocationAt(offset); + return loc && loc[name](); + }; + }, +{ + beans: false, + + getLocationOf: function() { + var point = Point.read(arguments), + curves = this.getCurves(); + for (var i = 0, l = curves.length; i < l; i++) { + var loc = curves[i].getLocationOf(point); + if (loc) + return loc; + } + return null; + }, + + getOffsetOf: function() { + var loc = this.getLocationOf.apply(this, arguments); + return loc ? loc.getOffset() : null; + }, + + getLocationAt: function(offset) { + if (typeof offset === 'number') { + var curves = this.getCurves(), + length = 0; + for (var i = 0, l = curves.length; i < l; i++) { + var start = length, + curve = curves[i]; + length += curve.getLength(); + if (length > offset) { + return curve.getLocationAt(offset - start); + } + } + if (curves.length > 0 && offset <= this.getLength()) { + return new CurveLocation(curves[curves.length - 1], 1); + } + } else if (offset && offset.getPath && offset.getPath() === this) { + return offset; + } + return null; + }, + + getOffsetsWithTangent: function() { + var tangent = Point.read(arguments); + if (tangent.isZero()) { + return []; + } + + var offsets = []; + var curveStart = 0; + var curves = this.getCurves(); + for (var i = 0, l = curves.length; i < l; i++) { + var curve = curves[i]; + var curveTimes = curve.getTimesWithTangent(tangent); + for (var j = 0, m = curveTimes.length; j < m; j++) { + var offset = curveStart + curve.getOffsetAtTime(curveTimes[j]); + if (offsets.indexOf(offset) < 0) { + offsets.push(offset); + } + } + curveStart += curve.length; + } + return offsets; + } +}), +new function() { + + function drawHandles(ctx, segments, matrix, size) { + if (size <= 0) return; + + var half = size / 2, + miniSize = size - 2, + miniHalf = half - 1, + coords = new Array(6), + pX, pY; + + function drawHandle(index) { + var hX = coords[index], + hY = coords[index + 1]; + if (pX != hX || pY != hY) { + ctx.beginPath(); + ctx.moveTo(pX, pY); + ctx.lineTo(hX, hY); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(hX, hY, half, 0, Math.PI * 2, true); + ctx.fill(); + } + } + + for (var i = 0, l = segments.length; i < l; i++) { + var segment = segments[i], + selection = segment._selection; + segment._transformCoordinates(matrix, coords); + pX = coords[0]; + pY = coords[1]; + if (selection & 2) + drawHandle(2); + if (selection & 4) + drawHandle(4); + ctx.fillRect(pX - half, pY - half, size, size); + if (miniSize > 0 && !(selection & 1)) { + var fillStyle = ctx.fillStyle; + ctx.fillStyle = '#ffffff'; + ctx.fillRect(pX - miniHalf, pY - miniHalf, miniSize, miniSize); + ctx.fillStyle = fillStyle; + } + } + } + + function drawSegments(ctx, path, matrix) { + var segments = path._segments, + length = segments.length, + coords = new Array(6), + first = true, + curX, curY, + prevX, prevY, + inX, inY, + outX, outY; + + function drawSegment(segment) { + if (matrix) { + segment._transformCoordinates(matrix, coords); + curX = coords[0]; + curY = coords[1]; + } else { + var point = segment._point; + curX = point._x; + curY = point._y; + } + if (first) { + ctx.moveTo(curX, curY); + first = false; + } else { + if (matrix) { + inX = coords[2]; + inY = coords[3]; + } else { + var handle = segment._handleIn; + inX = curX + handle._x; + inY = curY + handle._y; + } + if (inX === curX && inY === curY + && outX === prevX && outY === prevY) { + ctx.lineTo(curX, curY); + } else { + ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY); + } + } + prevX = curX; + prevY = curY; + if (matrix) { + outX = coords[4]; + outY = coords[5]; + } else { + var handle = segment._handleOut; + outX = prevX + handle._x; + outY = prevY + handle._y; + } + } + + for (var i = 0; i < length; i++) + drawSegment(segments[i]); + if (path._closed && length > 0) + drawSegment(segments[0]); + } + + return { + _draw: function(ctx, param, viewMatrix, strokeMatrix) { + var dontStart = param.dontStart, + dontPaint = param.dontFinish || param.clip, + style = this.getStyle(), + hasFill = style.hasFill(), + hasStroke = style.hasStroke(), + dashArray = style.getDashArray(), + dashLength = !paper.support.nativeDash && hasStroke + && dashArray && dashArray.length; + + if (!dontStart) + ctx.beginPath(); + + if (hasFill || hasStroke && !dashLength || dontPaint) { + drawSegments(ctx, this, strokeMatrix); + if (this._closed) + ctx.closePath(); + } + + function getOffset(i) { + return dashArray[((i % dashLength) + dashLength) % dashLength]; + } + + if (!dontPaint && (hasFill || hasStroke)) { + this._setStyles(ctx, param, viewMatrix); + if (hasFill) { + ctx.fill(style.getFillRule()); + ctx.shadowColor = 'rgba(0,0,0,0)'; + } + if (hasStroke) { + if (dashLength) { + if (!dontStart) + ctx.beginPath(); + var flattener = new PathFlattener(this, 0.25, 32, false, + strokeMatrix), + length = flattener.length, + from = -style.getDashOffset(), to, + i = 0; + while (from > 0) { + from -= getOffset(i--) + getOffset(i--); + } + while (from < length) { + to = from + getOffset(i++); + if (from > 0 || to > 0) + flattener.drawPart(ctx, + Math.max(from, 0), Math.max(to, 0)); + from = to + getOffset(i++); + } + } + ctx.stroke(); + } + } + }, + + _drawSelected: function(ctx, matrix) { + ctx.beginPath(); + drawSegments(ctx, this, matrix); + ctx.stroke(); + drawHandles(ctx, this._segments, matrix, paper.settings.handleSize); + } + }; +}, +new function() { + function getCurrentSegment(that) { + var segments = that._segments; + if (!segments.length) + throw new Error('Use a moveTo() command first'); + return segments[segments.length - 1]; + } + + return { + moveTo: function() { + var segments = this._segments; + if (segments.length === 1) + this.removeSegment(0); + if (!segments.length) + this._add([ new Segment(Point.read(arguments)) ]); + }, + + moveBy: function() { + throw new Error('moveBy() is unsupported on Path items.'); + }, + + lineTo: function() { + this._add([ new Segment(Point.read(arguments)) ]); + }, + + cubicCurveTo: function() { + var args = arguments, + handle1 = Point.read(args), + handle2 = Point.read(args), + to = Point.read(args), + current = getCurrentSegment(this); + current.setHandleOut(handle1.subtract(current._point)); + this._add([ new Segment(to, handle2.subtract(to)) ]); + }, + + quadraticCurveTo: function() { + var args = arguments, + handle = Point.read(args), + to = Point.read(args), + current = getCurrentSegment(this)._point; + this.cubicCurveTo( + handle.add(current.subtract(handle).multiply(1 / 3)), + handle.add(to.subtract(handle).multiply(1 / 3)), + to + ); + }, + + curveTo: function() { + var args = arguments, + through = Point.read(args), + to = Point.read(args), + t = Base.pick(Base.read(args), 0.5), + t1 = 1 - t, + current = getCurrentSegment(this)._point, + handle = through.subtract(current.multiply(t1 * t1)) + .subtract(to.multiply(t * t)).divide(2 * t * t1); + if (handle.isNaN()) + throw new Error( + 'Cannot put a curve through points with parameter = ' + t); + this.quadraticCurveTo(handle, to); + }, + + arcTo: function() { + var args = arguments, + abs = Math.abs, + sqrt = Math.sqrt, + current = getCurrentSegment(this), + from = current._point, + to = Point.read(args), + through, + peek = Base.peek(args), + clockwise = Base.pick(peek, true), + center, extent, vector, matrix; + if (typeof clockwise === 'boolean') { + var middle = from.add(to).divide(2), + through = middle.add(middle.subtract(from).rotate( + clockwise ? -90 : 90)); + } else if (Base.remain(args) <= 2) { + through = to; + to = Point.read(args); + } else if (!from.equals(to)) { + var radius = Size.read(args), + isZero = Numerical.isZero; + if (isZero(radius.width) || isZero(radius.height)) + return this.lineTo(to); + var rotation = Base.read(args), + clockwise = !!Base.read(args), + large = !!Base.read(args), + middle = from.add(to).divide(2), + pt = from.subtract(middle).rotate(-rotation), + x = pt.x, + y = pt.y, + rx = abs(radius.width), + ry = abs(radius.height), + rxSq = rx * rx, + rySq = ry * ry, + xSq = x * x, + ySq = y * y; + var factor = sqrt(xSq / rxSq + ySq / rySq); + if (factor > 1) { + rx *= factor; + ry *= factor; + rxSq = rx * rx; + rySq = ry * ry; + } + factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) / + (rxSq * ySq + rySq * xSq); + if (abs(factor) < 1e-12) + factor = 0; + if (factor < 0) + throw new Error( + 'Cannot create an arc with the given arguments'); + center = new Point(rx * y / ry, -ry * x / rx) + .multiply((large === clockwise ? -1 : 1) * sqrt(factor)) + .rotate(rotation).add(middle); + matrix = new Matrix().translate(center).rotate(rotation) + .scale(rx, ry); + vector = matrix._inverseTransform(from); + extent = vector.getDirectedAngle(matrix._inverseTransform(to)); + if (!clockwise && extent > 0) + extent -= 360; + else if (clockwise && extent < 0) + extent += 360; + } + if (through) { + var l1 = new Line(from.add(through).divide(2), + through.subtract(from).rotate(90), true), + l2 = new Line(through.add(to).divide(2), + to.subtract(through).rotate(90), true), + line = new Line(from, to), + throughSide = line.getSide(through); + center = l1.intersect(l2, true); + if (!center) { + if (!throughSide) + return this.lineTo(to); + throw new Error( + 'Cannot create an arc with the given arguments'); + } + vector = from.subtract(center); + extent = vector.getDirectedAngle(to.subtract(center)); + var centerSide = line.getSide(center, true); + if (centerSide === 0) { + extent = throughSide * abs(extent); + } else if (throughSide === centerSide) { + extent += extent < 0 ? 360 : -360; + } + } + if (extent) { + var epsilon = 1e-7, + ext = abs(extent), + count = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90), + inc = extent / count, + half = inc * Math.PI / 360, + z = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)), + segments = []; + for (var i = 0; i <= count; i++) { + var pt = to, + out = null; + if (i < count) { + out = vector.rotate(90).multiply(z); + if (matrix) { + pt = matrix._transformPoint(vector); + out = matrix._transformPoint(vector.add(out)) + .subtract(pt); + } else { + pt = center.add(vector); + } + } + if (!i) { + current.setHandleOut(out); + } else { + var _in = vector.rotate(-90).multiply(z); + if (matrix) { + _in = matrix._transformPoint(vector.add(_in)) + .subtract(pt); + } + segments.push(new Segment(pt, _in, out)); + } + vector = vector.rotate(inc); + } + this._add(segments); + } + }, + + lineBy: function() { + var to = Point.read(arguments), + current = getCurrentSegment(this)._point; + this.lineTo(current.add(to)); + }, + + curveBy: function() { + var args = arguments, + through = Point.read(args), + to = Point.read(args), + parameter = Base.read(args), + current = getCurrentSegment(this)._point; + this.curveTo(current.add(through), current.add(to), parameter); + }, + + cubicCurveBy: function() { + var args = arguments, + handle1 = Point.read(args), + handle2 = Point.read(args), + to = Point.read(args), + current = getCurrentSegment(this)._point; + this.cubicCurveTo(current.add(handle1), current.add(handle2), + current.add(to)); + }, + + quadraticCurveBy: function() { + var args = arguments, + handle = Point.read(args), + to = Point.read(args), + current = getCurrentSegment(this)._point; + this.quadraticCurveTo(current.add(handle), current.add(to)); + }, + + arcBy: function() { + var args = arguments, + current = getCurrentSegment(this)._point, + point = current.add(Point.read(args)), + clockwise = Base.pick(Base.peek(args), true); + if (typeof clockwise === 'boolean') { + this.arcTo(point, clockwise); + } else { + this.arcTo(point, current.add(Point.read(args))); + } + }, + + closePath: function(tolerance) { + this.setClosed(true); + this.join(this, tolerance); + } + }; +}, { + + _getBounds: function(matrix, options) { + var method = options.handle + ? 'getHandleBounds' + : options.stroke + ? 'getStrokeBounds' + : 'getBounds'; + return Path[method](this._segments, this._closed, this, matrix, options); + }, + +statics: { + getBounds: function(segments, closed, path, matrix, options, strokePadding) { + var first = segments[0]; + if (!first) + return new Rectangle(); + var coords = new Array(6), + prevCoords = first._transformCoordinates(matrix, new Array(6)), + min = prevCoords.slice(0, 2), + max = min.slice(), + roots = new Array(2); + + function processSegment(segment) { + segment._transformCoordinates(matrix, coords); + for (var i = 0; i < 2; i++) { + Curve._addBounds( + prevCoords[i], + prevCoords[i + 4], + coords[i + 2], + coords[i], + i, strokePadding ? strokePadding[i] : 0, min, max, roots); + } + var tmp = prevCoords; + prevCoords = coords; + coords = tmp; + } + + for (var i = 1, l = segments.length; i < l; i++) + processSegment(segments[i]); + if (closed) + processSegment(first); + return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]); + }, + + getStrokeBounds: function(segments, closed, path, matrix, options) { + var style = path.getStyle(), + stroke = style.hasStroke(), + strokeWidth = style.getStrokeWidth(), + strokeMatrix = stroke && path._getStrokeMatrix(matrix, options), + strokePadding = stroke && Path._getStrokePadding(strokeWidth, + strokeMatrix), + bounds = Path.getBounds(segments, closed, path, matrix, options, + strokePadding); + if (!stroke) + return bounds; + var strokeRadius = strokeWidth / 2, + join = style.getStrokeJoin(), + cap = style.getStrokeCap(), + miterLimit = style.getMiterLimit(), + joinBounds = new Rectangle(new Size(strokePadding)); + + function addPoint(point) { + bounds = bounds.include(point); + } + + function addRound(segment) { + bounds = bounds.unite( + joinBounds.setCenter(segment._point.transform(matrix))); + } + + function addJoin(segment, join) { + if (join === 'round' || segment.isSmooth()) { + addRound(segment); + } else { + Path._addBevelJoin(segment, join, strokeRadius, miterLimit, + matrix, strokeMatrix, addPoint); + } + } + + function addCap(segment, cap) { + if (cap === 'round') { + addRound(segment); + } else { + Path._addSquareCap(segment, cap, strokeRadius, matrix, + strokeMatrix, addPoint); + } + } + + var length = segments.length - (closed ? 0 : 1); + if (length > 0) { + for (var i = 1; i < length; i++) { + addJoin(segments[i], join); + } + if (closed) { + addJoin(segments[0], join); + } else { + addCap(segments[0], cap); + addCap(segments[segments.length - 1], cap); + } + } + return bounds; + }, + + _getStrokePadding: function(radius, matrix) { + if (!matrix) + return [radius, radius]; + var hor = new Point(radius, 0).transform(matrix), + ver = new Point(0, radius).transform(matrix), + phi = hor.getAngleInRadians(), + a = hor.getLength(), + b = ver.getLength(); + var sin = Math.sin(phi), + cos = Math.cos(phi), + tan = Math.tan(phi), + tx = Math.atan2(b * tan, a), + ty = Math.atan2(b, tan * a); + return [Math.abs(a * Math.cos(tx) * cos + b * Math.sin(tx) * sin), + Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)]; + }, + + _addBevelJoin: function(segment, join, radius, miterLimit, matrix, + strokeMatrix, addPoint, isArea) { + var curve2 = segment.getCurve(), + curve1 = curve2.getPrevious(), + point = curve2.getPoint1().transform(matrix), + normal1 = curve1.getNormalAtTime(1).multiply(radius) + .transform(strokeMatrix), + normal2 = curve2.getNormalAtTime(0).multiply(radius) + .transform(strokeMatrix), + angle = normal1.getDirectedAngle(normal2); + if (angle < 0 || angle >= 180) { + normal1 = normal1.negate(); + normal2 = normal2.negate(); + } + if (isArea) + addPoint(point); + addPoint(point.add(normal1)); + if (join === 'miter') { + var corner = new Line(point.add(normal1), + new Point(-normal1.y, normal1.x), true + ).intersect(new Line(point.add(normal2), + new Point(-normal2.y, normal2.x), true + ), true); + if (corner && point.getDistance(corner) <= miterLimit * radius) { + addPoint(corner); + } + } + addPoint(point.add(normal2)); + }, + + _addSquareCap: function(segment, cap, radius, matrix, strokeMatrix, + addPoint, isArea) { + var point = segment._point.transform(matrix), + loc = segment.getLocation(), + normal = loc.getNormal() + .multiply(loc.getTime() === 0 ? radius : -radius) + .transform(strokeMatrix); + if (cap === 'square') { + if (isArea) { + addPoint(point.subtract(normal)); + addPoint(point.add(normal)); + } + point = point.add(normal.rotate(-90)); + } + addPoint(point.add(normal)); + addPoint(point.subtract(normal)); + }, + + getHandleBounds: function(segments, closed, path, matrix, options) { + var style = path.getStyle(), + stroke = options.stroke && style.hasStroke(), + strokePadding, + joinPadding; + if (stroke) { + var strokeMatrix = path._getStrokeMatrix(matrix, options), + strokeRadius = style.getStrokeWidth() / 2, + joinRadius = strokeRadius; + if (style.getStrokeJoin() === 'miter') + joinRadius = strokeRadius * style.getMiterLimit(); + if (style.getStrokeCap() === 'square') + joinRadius = Math.max(joinRadius, strokeRadius * Math.SQRT2); + strokePadding = Path._getStrokePadding(strokeRadius, strokeMatrix); + joinPadding = Path._getStrokePadding(joinRadius, strokeMatrix); + } + var coords = new Array(6), + x1 = Infinity, + x2 = -x1, + y1 = x1, + y2 = x2; + for (var i = 0, l = segments.length; i < l; i++) { + var segment = segments[i]; + segment._transformCoordinates(matrix, coords); + for (var j = 0; j < 6; j += 2) { + var padding = !j ? joinPadding : strokePadding, + paddingX = padding ? padding[0] : 0, + paddingY = padding ? padding[1] : 0, + x = coords[j], + y = coords[j + 1], + xn = x - paddingX, + xx = x + paddingX, + yn = y - paddingY, + yx = y + paddingY; + if (xn < x1) x1 = xn; + if (xx > x2) x2 = xx; + if (yn < y1) y1 = yn; + if (yx > y2) y2 = yx; + } + } + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } +}}); + +Path.inject({ statics: new function() { + + var kappa = 0.5522847498307936, + ellipseSegments = [ + new Segment([-1, 0], [0, kappa ], [0, -kappa]), + new Segment([0, -1], [-kappa, 0], [kappa, 0 ]), + new Segment([1, 0], [0, -kappa], [0, kappa ]), + new Segment([0, 1], [kappa, 0 ], [-kappa, 0]) + ]; + + function createPath(segments, closed, args) { + var props = Base.getNamed(args), + path = new Path(props && props.insert == false && Item.NO_INSERT); + path._add(segments); + path._closed = closed; + return path.set(props, { insert: true }); + } + + function createEllipse(center, radius, args) { + var segments = new Array(4); + for (var i = 0; i < 4; i++) { + var segment = ellipseSegments[i]; + segments[i] = new Segment( + segment._point.multiply(radius).add(center), + segment._handleIn.multiply(radius), + segment._handleOut.multiply(radius) + ); + } + return createPath(segments, true, args); + } + + return { + Line: function() { + var args = arguments; + return createPath([ + new Segment(Point.readNamed(args, 'from')), + new Segment(Point.readNamed(args, 'to')) + ], false, args); + }, + + Circle: function() { + var args = arguments, + center = Point.readNamed(args, 'center'), + radius = Base.readNamed(args, 'radius'); + return createEllipse(center, new Size(radius), args); + }, + + Rectangle: function() { + var args = arguments, + rect = Rectangle.readNamed(args, 'rectangle'), + radius = Size.readNamed(args, 'radius', 0, + { readNull: true }), + bl = rect.getBottomLeft(true), + tl = rect.getTopLeft(true), + tr = rect.getTopRight(true), + br = rect.getBottomRight(true), + segments; + if (!radius || radius.isZero()) { + segments = [ + new Segment(bl), + new Segment(tl), + new Segment(tr), + new Segment(br) + ]; + } else { + radius = Size.min(radius, rect.getSize(true).divide(2)); + var rx = radius.width, + ry = radius.height, + hx = rx * kappa, + hy = ry * kappa; + segments = [ + new Segment(bl.add(rx, 0), null, [-hx, 0]), + new Segment(bl.subtract(0, ry), [0, hy]), + new Segment(tl.add(0, ry), null, [0, -hy]), + new Segment(tl.add(rx, 0), [-hx, 0], null), + new Segment(tr.subtract(rx, 0), null, [hx, 0]), + new Segment(tr.add(0, ry), [0, -hy], null), + new Segment(br.subtract(0, ry), null, [0, hy]), + new Segment(br.subtract(rx, 0), [hx, 0]) + ]; + } + return createPath(segments, true, args); + }, + + RoundRectangle: '#Rectangle', + + Ellipse: function() { + var args = arguments, + ellipse = Shape._readEllipse(args); + return createEllipse(ellipse.center, ellipse.radius, args); + }, + + Oval: '#Ellipse', + + Arc: function() { + var args = arguments, + from = Point.readNamed(args, 'from'), + through = Point.readNamed(args, 'through'), + to = Point.readNamed(args, 'to'), + props = Base.getNamed(args), + path = new Path(props && props.insert == false + && Item.NO_INSERT); + path.moveTo(from); + path.arcTo(through, to); + return path.set(props); + }, + + RegularPolygon: function() { + var args = arguments, + center = Point.readNamed(args, 'center'), + sides = Base.readNamed(args, 'sides'), + radius = Base.readNamed(args, 'radius'), + step = 360 / sides, + three = sides % 3 === 0, + vector = new Point(0, three ? -radius : radius), + offset = three ? -1 : 0.5, + segments = new Array(sides); + for (var i = 0; i < sides; i++) + segments[i] = new Segment(center.add( + vector.rotate((i + offset) * step))); + return createPath(segments, true, args); + }, + + Star: function() { + var args = arguments, + center = Point.readNamed(args, 'center'), + points = Base.readNamed(args, 'points') * 2, + radius1 = Base.readNamed(args, 'radius1'), + radius2 = Base.readNamed(args, 'radius2'), + step = 360 / points, + vector = new Point(0, -1), + segments = new Array(points); + for (var i = 0; i < points; i++) + segments[i] = new Segment(center.add(vector.rotate(step * i) + .multiply(i % 2 ? radius2 : radius1))); + return createPath(segments, true, args); + } + }; +}}); + +var CompoundPath = PathItem.extend({ + _class: 'CompoundPath', + _serializeFields: { + children: [] + }, + beans: true, + + initialize: function CompoundPath(arg) { + this._children = []; + this._namedChildren = {}; + if (!this._initialize(arg)) { + if (typeof arg === 'string') { + this.setPathData(arg); + } else { + this.addChildren(Array.isArray(arg) ? arg : arguments); + } + } + }, + + insertChildren: function insertChildren(index, items) { + var list = items, + first = list[0]; + if (first && typeof first[0] === 'number') + list = [list]; + for (var i = items.length - 1; i >= 0; i--) { + var item = list[i]; + if (list === items && !(item instanceof Path)) + list = Base.slice(list); + if (Array.isArray(item)) { + list[i] = new Path({ segments: item, insert: false }); + } else if (item instanceof CompoundPath) { + list.splice.apply(list, [i, 1].concat(item.removeChildren())); + item.remove(); + } + } + return insertChildren.base.call(this, index, list); + }, + + reduce: function reduce(options) { + var children = this._children; + for (var i = children.length - 1; i >= 0; i--) { + var path = children[i].reduce(options); + if (path.isEmpty()) + path.remove(); + } + if (!children.length) { + var path = new Path(Item.NO_INSERT); + path.copyAttributes(this); + path.insertAbove(this); + this.remove(); + return path; + } + return reduce.base.call(this); + }, + + isClosed: function() { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + if (!children[i]._closed) + return false; + } + return true; + }, + + setClosed: function(closed) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + children[i].setClosed(closed); + } + }, + + getFirstSegment: function() { + var first = this.getFirstChild(); + return first && first.getFirstSegment(); + }, + + getLastSegment: function() { + var last = this.getLastChild(); + return last && last.getLastSegment(); + }, + + getCurves: function() { + var children = this._children, + curves = []; + for (var i = 0, l = children.length; i < l; i++) { + Base.push(curves, children[i].getCurves()); + } + return curves; + }, + + getFirstCurve: function() { + var first = this.getFirstChild(); + return first && first.getFirstCurve(); + }, + + getLastCurve: function() { + var last = this.getLastChild(); + return last && last.getLastCurve(); + }, + + getArea: function() { + var children = this._children, + area = 0; + for (var i = 0, l = children.length; i < l; i++) + area += children[i].getArea(); + return area; + }, + + getLength: function() { + var children = this._children, + length = 0; + for (var i = 0, l = children.length; i < l; i++) + length += children[i].getLength(); + return length; + }, + + getPathData: function(_matrix, _precision) { + var children = this._children, + paths = []; + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i], + mx = child._matrix; + paths.push(child.getPathData(_matrix && !mx.isIdentity() + ? _matrix.appended(mx) : _matrix, _precision)); + } + return paths.join(''); + }, + + _hitTestChildren: function _hitTestChildren(point, options, viewMatrix) { + return _hitTestChildren.base.call(this, point, + options.class === Path || options.type === 'path' ? options + : Base.set({}, options, { fill: false }), + viewMatrix); + }, + + _draw: function(ctx, param, viewMatrix, strokeMatrix) { + var children = this._children; + if (!children.length) + return; + + param = param.extend({ dontStart: true, dontFinish: true }); + ctx.beginPath(); + for (var i = 0, l = children.length; i < l; i++) + children[i].draw(ctx, param, strokeMatrix); + + if (!param.clip) { + this._setStyles(ctx, param, viewMatrix); + var style = this._style; + if (style.hasFill()) { + ctx.fill(style.getFillRule()); + ctx.shadowColor = 'rgba(0,0,0,0)'; + } + if (style.hasStroke()) + ctx.stroke(); + } + }, + + _drawSelected: function(ctx, matrix, selectionItems) { + var children = this._children; + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i], + mx = child._matrix; + if (!selectionItems[child._id]) { + child._drawSelected(ctx, mx.isIdentity() ? matrix + : matrix.appended(mx)); + } + } + } +}, +new function() { + function getCurrentPath(that, check) { + var children = that._children; + if (check && !children.length) + throw new Error('Use a moveTo() command first'); + return children[children.length - 1]; + } + + return Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', + 'arcTo', 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', + 'arcBy'], + function(key) { + this[key] = function() { + var path = getCurrentPath(this, true); + path[key].apply(path, arguments); + }; + }, { + moveTo: function() { + var current = getCurrentPath(this), + path = current && current.isEmpty() ? current + : new Path(Item.NO_INSERT); + if (path !== current) + this.addChild(path); + path.moveTo.apply(path, arguments); + }, + + moveBy: function() { + var current = getCurrentPath(this, true), + last = current && current.getLastSegment(), + point = Point.read(arguments); + this.moveTo(last ? point.add(last._point) : point); + }, + + closePath: function(tolerance) { + getCurrentPath(this, true).closePath(tolerance); + } + } + ); +}, Base.each(['reverse', 'flatten', 'simplify', 'smooth'], function(key) { + this[key] = function(param) { + var children = this._children, + res; + for (var i = 0, l = children.length; i < l; i++) { + res = children[i][key](param) || res; + } + return res; + }; +}, {})); + +PathItem.inject(new function() { + var min = Math.min, + max = Math.max, + abs = Math.abs, + operators = { + unite: { '1': true, '2': true }, + intersect: { '2': true }, + subtract: { '1': true }, + exclude: { '1': true, '-1': true } + }; + + function getPaths(path) { + return path._children || [path]; + } + + function preparePath(path, resolve) { + var res = path + .clone(false) + .reduce({ simplify: true }) + .transform(null, true, true); + if (resolve) { + var paths = getPaths(res); + for (var i = 0, l = paths.length; i < l; i++) { + var path = paths[i]; + if (!path._closed && !path.isEmpty()) { + path.closePath(1e-12); + path.getFirstSegment().setHandleIn(0, 0); + path.getLastSegment().setHandleOut(0, 0); + } + } + res = res + .resolveCrossings() + .reorient(res.getFillRule() === 'nonzero', true); + } + return res; + } + + function createResult(paths, simplify, path1, path2, options) { + var result = new CompoundPath(Item.NO_INSERT); + result.addChildren(paths, true); + result = result.reduce({ simplify: simplify }); + if (!(options && options.insert == false)) { + result.insertAbove(path2 && path1.isSibling(path2) + && path1.getIndex() < path2.getIndex() ? path2 : path1); + } + result.copyAttributes(path1, true); + return result; + } + + function filterIntersection(inter) { + return inter.hasOverlap() || inter.isCrossing(); + } + + function traceBoolean(path1, path2, operation, options) { + if (options && (options.trace == false || options.stroke) && + /^(subtract|intersect)$/.test(operation)) + return splitBoolean(path1, path2, operation); + var _path1 = preparePath(path1, true), + _path2 = path2 && path1 !== path2 && preparePath(path2, true), + operator = operators[operation]; + operator[operation] = true; + if (_path2 && (operator.subtract || operator.exclude) + ^ (_path2.isClockwise() ^ _path1.isClockwise())) + _path2.reverse(); + var crossings = divideLocations(CurveLocation.expand( + _path1.getIntersections(_path2, filterIntersection))), + paths1 = getPaths(_path1), + paths2 = _path2 && getPaths(_path2), + segments = [], + curves = [], + paths; + + function collectPaths(paths) { + for (var i = 0, l = paths.length; i < l; i++) { + var path = paths[i]; + Base.push(segments, path._segments); + Base.push(curves, path.getCurves()); + path._overlapsOnly = true; + } + } + + function getCurves(indices) { + var list = []; + for (var i = 0, l = indices && indices.length; i < l; i++) { + list.push(curves[indices[i]]); + } + return list; + } + + if (crossings.length) { + collectPaths(paths1); + if (paths2) + collectPaths(paths2); + + var curvesValues = new Array(curves.length); + for (var i = 0, l = curves.length; i < l; i++) { + curvesValues[i] = curves[i].getValues(); + } + var curveCollisions = CollisionDetection.findCurveBoundsCollisions( + curvesValues, curvesValues, 0, true); + var curveCollisionsMap = {}; + for (var i = 0; i < curves.length; i++) { + var curve = curves[i], + id = curve._path._id, + map = curveCollisionsMap[id] = curveCollisionsMap[id] || {}; + map[curve.getIndex()] = { + hor: getCurves(curveCollisions[i].hor), + ver: getCurves(curveCollisions[i].ver) + }; + } + + for (var i = 0, l = crossings.length; i < l; i++) { + propagateWinding(crossings[i]._segment, _path1, _path2, + curveCollisionsMap, operator); + } + for (var i = 0, l = segments.length; i < l; i++) { + var segment = segments[i], + inter = segment._intersection; + if (!segment._winding) { + propagateWinding(segment, _path1, _path2, + curveCollisionsMap, operator); + } + if (!(inter && inter._overlap)) + segment._path._overlapsOnly = false; + } + paths = tracePaths(segments, operator); + } else { + paths = reorientPaths( + paths2 ? paths1.concat(paths2) : paths1.slice(), + function(w) { + return !!operator[w]; + }); + } + return createResult(paths, true, path1, path2, options); + } + + function splitBoolean(path1, path2, operation) { + var _path1 = preparePath(path1), + _path2 = preparePath(path2), + crossings = _path1.getIntersections(_path2, filterIntersection), + subtract = operation === 'subtract', + divide = operation === 'divide', + added = {}, + paths = []; + + function addPath(path) { + if (!added[path._id] && (divide || + _path2.contains(path.getPointAt(path.getLength() / 2)) + ^ subtract)) { + paths.unshift(path); + return added[path._id] = true; + } + } + + for (var i = crossings.length - 1; i >= 0; i--) { + var path = crossings[i].split(); + if (path) { + if (addPath(path)) + path.getFirstSegment().setHandleIn(0, 0); + _path1.getLastSegment().setHandleOut(0, 0); + } + } + addPath(_path1); + return createResult(paths, false, path1, path2); + } + + function linkIntersections(from, to) { + var prev = from; + while (prev) { + if (prev === to) + return; + prev = prev._previous; + } + while (from._next && from._next !== to) + from = from._next; + if (!from._next) { + while (to._previous) + to = to._previous; + from._next = to; + to._previous = from; + } + } + + function clearCurveHandles(curves) { + for (var i = curves.length - 1; i >= 0; i--) + curves[i].clearHandles(); + } + + function reorientPaths(paths, isInside, clockwise) { + var length = paths && paths.length; + if (length) { + var lookup = Base.each(paths, function (path, i) { + this[path._id] = { + container: null, + winding: path.isClockwise() ? 1 : -1, + index: i + }; + }, {}), + sorted = paths.slice().sort(function (a, b) { + return abs(b.getArea()) - abs(a.getArea()); + }), + first = sorted[0]; + var collisions = CollisionDetection.findItemBoundsCollisions(sorted, + null, Numerical.GEOMETRIC_EPSILON); + if (clockwise == null) + clockwise = first.isClockwise(); + for (var i = 0; i < length; i++) { + var path1 = sorted[i], + entry1 = lookup[path1._id], + containerWinding = 0, + indices = collisions[i]; + if (indices) { + var point = null; + for (var j = indices.length - 1; j >= 0; j--) { + if (indices[j] < i) { + point = point || path1.getInteriorPoint(); + var path2 = sorted[indices[j]]; + if (path2.contains(point)) { + var entry2 = lookup[path2._id]; + containerWinding = entry2.winding; + entry1.winding += containerWinding; + entry1.container = entry2.exclude + ? entry2.container : path2; + break; + } + } + } + } + if (isInside(entry1.winding) === isInside(containerWinding)) { + entry1.exclude = true; + paths[entry1.index] = null; + } else { + var container = entry1.container; + path1.setClockwise( + container ? !container.isClockwise() : clockwise); + } + } + } + return paths; + } + + function divideLocations(locations, include, clearLater) { + var results = include && [], + tMin = 1e-8, + tMax = 1 - tMin, + clearHandles = false, + clearCurves = clearLater || [], + clearLookup = clearLater && {}, + renormalizeLocs, + prevCurve, + prevTime; + + function getId(curve) { + return curve._path._id + '.' + curve._segment1._index; + } + + for (var i = (clearLater && clearLater.length) - 1; i >= 0; i--) { + var curve = clearLater[i]; + if (curve._path) + clearLookup[getId(curve)] = true; + } + + for (var i = locations.length - 1; i >= 0; i--) { + var loc = locations[i], + time = loc._time, + origTime = time, + exclude = include && !include(loc), + curve = loc._curve, + segment; + if (curve) { + if (curve !== prevCurve) { + clearHandles = !curve.hasHandles() + || clearLookup && clearLookup[getId(curve)]; + renormalizeLocs = []; + prevTime = null; + prevCurve = curve; + } else if (prevTime >= tMin) { + time /= prevTime; + } + } + if (exclude) { + if (renormalizeLocs) + renormalizeLocs.push(loc); + continue; + } else if (include) { + results.unshift(loc); + } + prevTime = origTime; + if (time < tMin) { + segment = curve._segment1; + } else if (time > tMax) { + segment = curve._segment2; + } else { + var newCurve = curve.divideAtTime(time, true); + if (clearHandles) + clearCurves.push(curve, newCurve); + segment = newCurve._segment1; + for (var j = renormalizeLocs.length - 1; j >= 0; j--) { + var l = renormalizeLocs[j]; + l._time = (l._time - time) / (1 - time); + } + } + loc._setSegment(segment); + var inter = segment._intersection, + dest = loc._intersection; + if (inter) { + linkIntersections(inter, dest); + var other = inter; + while (other) { + linkIntersections(other._intersection, inter); + other = other._next; + } + } else { + segment._intersection = dest; + } + } + if (!clearLater) + clearCurveHandles(clearCurves); + return results || locations; + } + + function getWinding(point, curves, dir, closed, dontFlip) { + var curvesList = Array.isArray(curves) + ? curves + : curves[dir ? 'hor' : 'ver']; + var ia = dir ? 1 : 0, + io = ia ^ 1, + pv = [point.x, point.y], + pa = pv[ia], + po = pv[io], + windingEpsilon = 1e-9, + qualityEpsilon = 1e-6, + paL = pa - windingEpsilon, + paR = pa + windingEpsilon, + windingL = 0, + windingR = 0, + pathWindingL = 0, + pathWindingR = 0, + onPath = false, + onAnyPath = false, + quality = 1, + roots = [], + vPrev, + vClose; + + function addWinding(v) { + var o0 = v[io + 0], + o3 = v[io + 6]; + if (po < min(o0, o3) || po > max(o0, o3)) { + return; + } + var a0 = v[ia + 0], + a1 = v[ia + 2], + a2 = v[ia + 4], + a3 = v[ia + 6]; + if (o0 === o3) { + if (a0 < paR && a3 > paL || a3 < paR && a0 > paL) { + onPath = true; + } + return; + } + var t = po === o0 ? 0 + : po === o3 ? 1 + : paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3) + ? 1 + : Curve.solveCubic(v, io, po, roots, 0, 1) > 0 + ? roots[0] + : 1, + a = t === 0 ? a0 + : t === 1 ? a3 + : Curve.getPoint(v, t)[dir ? 'y' : 'x'], + winding = o0 > o3 ? 1 : -1, + windingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1, + a3Prev = vPrev[ia + 6]; + if (po !== o0) { + if (a < paL) { + pathWindingL += winding; + } else if (a > paR) { + pathWindingR += winding; + } else { + onPath = true; + } + if (a > pa - qualityEpsilon && a < pa + qualityEpsilon) + quality /= 2; + } else { + if (winding !== windingPrev) { + if (a0 < paL) { + pathWindingL += winding; + } else if (a0 > paR) { + pathWindingR += winding; + } + } else if (a0 != a3Prev) { + if (a3Prev < paR && a > paR) { + pathWindingR += winding; + onPath = true; + } else if (a3Prev > paL && a < paL) { + pathWindingL += winding; + onPath = true; + } + } + quality /= 4; + } + vPrev = v; + return !dontFlip && a > paL && a < paR + && Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0 + && getWinding(point, curves, !dir, closed, true); + } + + function handleCurve(v) { + var o0 = v[io + 0], + o1 = v[io + 2], + o2 = v[io + 4], + o3 = v[io + 6]; + if (po <= max(o0, o1, o2, o3) && po >= min(o0, o1, o2, o3)) { + var a0 = v[ia + 0], + a1 = v[ia + 2], + a2 = v[ia + 4], + a3 = v[ia + 6], + monoCurves = paL > max(a0, a1, a2, a3) || + paR < min(a0, a1, a2, a3) + ? [v] : Curve.getMonoCurves(v, dir), + res; + for (var i = 0, l = monoCurves.length; i < l; i++) { + if (res = addWinding(monoCurves[i])) + return res; + } + } + } + + for (var i = 0, l = curvesList.length; i < l; i++) { + var curve = curvesList[i], + path = curve._path, + v = curve.getValues(), + res; + if (!i || curvesList[i - 1]._path !== path) { + vPrev = null; + if (!path._closed) { + vClose = Curve.getValues( + path.getLastCurve().getSegment2(), + curve.getSegment1(), + null, !closed); + if (vClose[io] !== vClose[io + 6]) { + vPrev = vClose; + } + } + + if (!vPrev) { + vPrev = v; + var prev = path.getLastCurve(); + while (prev && prev !== curve) { + var v2 = prev.getValues(); + if (v2[io] !== v2[io + 6]) { + vPrev = v2; + break; + } + prev = prev.getPrevious(); + } + } + } + + if (res = handleCurve(v)) + return res; + + if (i + 1 === l || curvesList[i + 1]._path !== path) { + if (vClose && (res = handleCurve(vClose))) + return res; + if (onPath && !pathWindingL && !pathWindingR) { + pathWindingL = pathWindingR = path.isClockwise(closed) ^ dir + ? 1 : -1; + } + windingL += pathWindingL; + windingR += pathWindingR; + pathWindingL = pathWindingR = 0; + if (onPath) { + onAnyPath = true; + onPath = false; + } + vClose = null; + } + } + windingL = abs(windingL); + windingR = abs(windingR); + return { + winding: max(windingL, windingR), + windingL: windingL, + windingR: windingR, + quality: quality, + onPath: onAnyPath + }; + } + + function propagateWinding(segment, path1, path2, curveCollisionsMap, + operator) { + var chain = [], + start = segment, + totalLength = 0, + winding; + do { + var curve = segment.getCurve(); + if (curve) { + var length = curve.getLength(); + chain.push({ segment: segment, curve: curve, length: length }); + totalLength += length; + } + segment = segment.getNext(); + } while (segment && !segment._intersection && segment !== start); + var offsets = [0.5, 0.25, 0.75], + winding = { winding: 0, quality: -1 }, + tMin = 1e-3, + tMax = 1 - tMin; + for (var i = 0; i < offsets.length && winding.quality < 0.5; i++) { + var length = totalLength * offsets[i]; + for (var j = 0, l = chain.length; j < l; j++) { + var entry = chain[j], + curveLength = entry.length; + if (length <= curveLength) { + var curve = entry.curve, + path = curve._path, + parent = path._parent, + operand = parent instanceof CompoundPath ? parent : path, + t = Numerical.clamp(curve.getTimeAt(length), tMin, tMax), + pt = curve.getPointAtTime(t), + dir = abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2; + var wind = null; + if (operator.subtract && path2) { + var otherPath = operand === path1 ? path2 : path1, + pathWinding = otherPath._getWinding(pt, dir, true); + if (operand === path1 && pathWinding.winding || + operand === path2 && !pathWinding.winding) { + if (pathWinding.quality < 1) { + continue; + } else { + wind = { winding: 0, quality: 1 }; + } + } + } + wind = wind || getWinding( + pt, curveCollisionsMap[path._id][curve.getIndex()], + dir, true); + if (wind.quality > winding.quality) + winding = wind; + break; + } + length -= curveLength; + } + } + for (var j = chain.length - 1; j >= 0; j--) { + chain[j].segment._winding = winding; + } + } + + function tracePaths(segments, operator) { + var paths = [], + starts; + + function isValid(seg) { + var winding; + return !!(seg && !seg._visited && (!operator + || operator[(winding = seg._winding || {}).winding] + && !(operator.unite && winding.winding === 2 + && winding.windingL && winding.windingR))); + } + + function isStart(seg) { + if (seg) { + for (var i = 0, l = starts.length; i < l; i++) { + if (seg === starts[i]) + return true; + } + } + return false; + } + + function visitPath(path) { + var segments = path._segments; + for (var i = 0, l = segments.length; i < l; i++) { + segments[i]._visited = true; + } + } + + function getCrossingSegments(segment, collectStarts) { + var inter = segment._intersection, + start = inter, + crossings = []; + if (collectStarts) + starts = [segment]; + + function collect(inter, end) { + while (inter && inter !== end) { + var other = inter._segment, + path = other && other._path; + if (path) { + var next = other.getNext() || path.getFirstSegment(), + nextInter = next._intersection; + if (other !== segment && (isStart(other) + || isStart(next) + || next && (isValid(other) && (isValid(next) + || nextInter && isValid(nextInter._segment)))) + ) { + crossings.push(other); + } + if (collectStarts) + starts.push(other); + } + inter = inter._next; + } + } + + if (inter) { + collect(inter); + while (inter && inter._previous) + inter = inter._previous; + collect(inter, start); + } + return crossings; + } + + segments.sort(function(seg1, seg2) { + var inter1 = seg1._intersection, + inter2 = seg2._intersection, + over1 = !!(inter1 && inter1._overlap), + over2 = !!(inter2 && inter2._overlap), + path1 = seg1._path, + path2 = seg2._path; + return over1 ^ over2 + ? over1 ? 1 : -1 + : !inter1 ^ !inter2 + ? inter1 ? 1 : -1 + : path1 !== path2 + ? path1._id - path2._id + : seg1._index - seg2._index; + }); + + for (var i = 0, l = segments.length; i < l; i++) { + var seg = segments[i], + valid = isValid(seg), + path = null, + finished = false, + closed = true, + branches = [], + branch, + visited, + handleIn; + if (valid && seg._path._overlapsOnly) { + var path1 = seg._path, + path2 = seg._intersection._segment._path; + if (path1.compare(path2)) { + if (path1.getArea()) + paths.push(path1.clone(false)); + visitPath(path1); + visitPath(path2); + valid = false; + } + } + while (valid) { + var first = !path, + crossings = getCrossingSegments(seg, first), + other = crossings.shift(), + finished = !first && (isStart(seg) || isStart(other)), + cross = !finished && other; + if (first) { + path = new Path(Item.NO_INSERT); + branch = null; + } + if (finished) { + if (seg.isFirst() || seg.isLast()) + closed = seg._path._closed; + seg._visited = true; + break; + } + if (cross && branch) { + branches.push(branch); + branch = null; + } + if (!branch) { + if (cross) + crossings.push(seg); + branch = { + start: path._segments.length, + crossings: crossings, + visited: visited = [], + handleIn: handleIn + }; + } + if (cross) + seg = other; + if (!isValid(seg)) { + path.removeSegments(branch.start); + for (var j = 0, k = visited.length; j < k; j++) { + visited[j]._visited = false; + } + visited.length = 0; + do { + seg = branch && branch.crossings.shift(); + if (!seg || !seg._path) { + seg = null; + branch = branches.pop(); + if (branch) { + visited = branch.visited; + handleIn = branch.handleIn; + } + } + } while (branch && !isValid(seg)); + if (!seg) + break; + } + var next = seg.getNext(); + path.add(new Segment(seg._point, handleIn, + next && seg._handleOut)); + seg._visited = true; + visited.push(seg); + seg = next || seg._path.getFirstSegment(); + handleIn = next && next._handleIn; + } + if (finished) { + if (closed) { + path.getFirstSegment().setHandleIn(handleIn); + path.setClosed(closed); + } + if (path.getArea() !== 0) { + paths.push(path); + } + } + } + return paths; + } + + return { + _getWinding: function(point, dir, closed) { + return getWinding(point, this.getCurves(), dir, closed); + }, + + unite: function(path, options) { + return traceBoolean(this, path, 'unite', options); + }, + + intersect: function(path, options) { + return traceBoolean(this, path, 'intersect', options); + }, + + subtract: function(path, options) { + return traceBoolean(this, path, 'subtract', options); + }, + + exclude: function(path, options) { + return traceBoolean(this, path, 'exclude', options); + }, + + divide: function(path, options) { + return options && (options.trace == false || options.stroke) + ? splitBoolean(this, path, 'divide') + : createResult([ + this.subtract(path, options), + this.intersect(path, options) + ], true, this, path, options); + }, + + resolveCrossings: function() { + var children = this._children, + paths = children || [this]; + + function hasOverlap(seg, path) { + var inter = seg && seg._intersection; + return inter && inter._overlap && inter._path === path; + } + + var hasOverlaps = false, + hasCrossings = false, + intersections = this.getIntersections(null, function(inter) { + return inter.hasOverlap() && (hasOverlaps = true) || + inter.isCrossing() && (hasCrossings = true); + }), + clearCurves = hasOverlaps && hasCrossings && []; + intersections = CurveLocation.expand(intersections); + if (hasOverlaps) { + var overlaps = divideLocations(intersections, function(inter) { + return inter.hasOverlap(); + }, clearCurves); + for (var i = overlaps.length - 1; i >= 0; i--) { + var overlap = overlaps[i], + path = overlap._path, + seg = overlap._segment, + prev = seg.getPrevious(), + next = seg.getNext(); + if (hasOverlap(prev, path) && hasOverlap(next, path)) { + seg.remove(); + prev._handleOut._set(0, 0); + next._handleIn._set(0, 0); + if (prev !== seg && !prev.getCurve().hasLength()) { + next._handleIn.set(prev._handleIn); + prev.remove(); + } + } + } + } + if (hasCrossings) { + divideLocations(intersections, hasOverlaps && function(inter) { + var curve1 = inter.getCurve(), + seg1 = inter.getSegment(), + other = inter._intersection, + curve2 = other._curve, + seg2 = other._segment; + if (curve1 && curve2 && curve1._path && curve2._path) + return true; + if (seg1) + seg1._intersection = null; + if (seg2) + seg2._intersection = null; + }, clearCurves); + if (clearCurves) + clearCurveHandles(clearCurves); + paths = tracePaths(Base.each(paths, function(path) { + Base.push(this, path._segments); + }, [])); + } + var length = paths.length, + item; + if (length > 1 && children) { + if (paths !== children) + this.setChildren(paths); + item = this; + } else if (length === 1 && !children) { + if (paths[0] !== this) + this.setSegments(paths[0].removeSegments()); + item = this; + } + if (!item) { + item = new CompoundPath(Item.NO_INSERT); + item.addChildren(paths); + item = item.reduce(); + item.copyAttributes(this); + this.replaceWith(item); + } + return item; + }, + + reorient: function(nonZero, clockwise) { + var children = this._children; + if (children && children.length) { + this.setChildren(reorientPaths(this.removeChildren(), + function(w) { + return !!(nonZero ? w : w & 1); + }, + clockwise)); + } else if (clockwise !== undefined) { + this.setClockwise(clockwise); + } + return this; + }, + + getInteriorPoint: function() { + var bounds = this.getBounds(), + point = bounds.getCenter(true); + if (!this.contains(point)) { + var curves = this.getCurves(), + y = point.y, + intercepts = [], + roots = []; + for (var i = 0, l = curves.length; i < l; i++) { + var v = curves[i].getValues(), + o0 = v[1], + o1 = v[3], + o2 = v[5], + o3 = v[7]; + if (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) { + var monoCurves = Curve.getMonoCurves(v); + for (var j = 0, m = monoCurves.length; j < m; j++) { + var mv = monoCurves[j], + mo0 = mv[1], + mo3 = mv[7]; + if ((mo0 !== mo3) && + (y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){ + var x = y === mo0 ? mv[0] + : y === mo3 ? mv[6] + : Curve.solveCubic(mv, 1, y, roots, 0, 1) + === 1 + ? Curve.getPoint(mv, roots[0]).x + : (mv[0] + mv[6]) / 2; + intercepts.push(x); + } + } + } + } + if (intercepts.length > 1) { + intercepts.sort(function(a, b) { return a - b; }); + point.x = (intercepts[0] + intercepts[1]) / 2; + } + } + return point; + } + }; +}); + +var PathFlattener = Base.extend({ + _class: 'PathFlattener', + + initialize: function(path, flatness, maxRecursion, ignoreStraight, matrix) { + var curves = [], + parts = [], + length = 0, + minSpan = 1 / (maxRecursion || 32), + segments = path._segments, + segment1 = segments[0], + segment2; + + function addCurve(segment1, segment2) { + var curve = Curve.getValues(segment1, segment2, matrix); + curves.push(curve); + computeParts(curve, segment1._index, 0, 1); + } + + function computeParts(curve, index, t1, t2) { + if ((t2 - t1) > minSpan + && !(ignoreStraight && Curve.isStraight(curve)) + && !Curve.isFlatEnough(curve, flatness || 0.25)) { + var halves = Curve.subdivide(curve, 0.5), + tMid = (t1 + t2) / 2; + computeParts(halves[0], index, t1, tMid); + computeParts(halves[1], index, tMid, t2); + } else { + var dx = curve[6] - curve[0], + dy = curve[7] - curve[1], + dist = Math.sqrt(dx * dx + dy * dy); + if (dist > 0) { + length += dist; + parts.push({ + offset: length, + curve: curve, + index: index, + time: t2, + }); + } + } + } + + for (var i = 1, l = segments.length; i < l; i++) { + segment2 = segments[i]; + addCurve(segment1, segment2); + segment1 = segment2; + } + if (path._closed) + addCurve(segment2 || segment1, segments[0]); + this.curves = curves; + this.parts = parts; + this.length = length; + this.index = 0; + }, + + _get: function(offset) { + var parts = this.parts, + length = parts.length, + start, + i, j = this.index; + for (;;) { + i = j; + if (!j || parts[--j].offset < offset) + break; + } + for (; i < length; i++) { + var part = parts[i]; + if (part.offset >= offset) { + this.index = i; + var prev = parts[i - 1], + prevTime = prev && prev.index === part.index ? prev.time : 0, + prevOffset = prev ? prev.offset : 0; + return { + index: part.index, + time: prevTime + (part.time - prevTime) + * (offset - prevOffset) / (part.offset - prevOffset) + }; + } + } + return { + index: parts[length - 1].index, + time: 1 + }; + }, + + drawPart: function(ctx, from, to) { + var start = this._get(from), + end = this._get(to); + for (var i = start.index, l = end.index; i <= l; i++) { + var curve = Curve.getPart(this.curves[i], + i === start.index ? start.time : 0, + i === end.index ? end.time : 1); + if (i === start.index) + ctx.moveTo(curve[0], curve[1]); + ctx.bezierCurveTo.apply(ctx, curve.slice(2)); + } + } +}, Base.each(Curve._evaluateMethods, + function(name) { + this[name + 'At'] = function(offset) { + var param = this._get(offset); + return Curve[name](this.curves[param.index], param.time); + }; + }, {}) +); + +var PathFitter = Base.extend({ + initialize: function(path) { + var points = this.points = [], + segments = path._segments, + closed = path._closed; + for (var i = 0, prev, l = segments.length; i < l; i++) { + var point = segments[i].point; + if (!prev || !prev.equals(point)) { + points.push(prev = point.clone()); + } + } + if (closed) { + points.unshift(points[points.length - 1]); + points.push(points[1]); + } + this.closed = closed; + }, + + fit: function(error) { + var points = this.points, + length = points.length, + segments = null; + if (length > 0) { + segments = [new Segment(points[0])]; + if (length > 1) { + this.fitCubic(segments, error, 0, length - 1, + points[1].subtract(points[0]), + points[length - 2].subtract(points[length - 1])); + if (this.closed) { + segments.shift(); + segments.pop(); + } + } + } + return segments; + }, + + fitCubic: function(segments, error, first, last, tan1, tan2) { + var points = this.points; + if (last - first === 1) { + var pt1 = points[first], + pt2 = points[last], + dist = pt1.getDistance(pt2) / 3; + this.addCurve(segments, [pt1, pt1.add(tan1.normalize(dist)), + pt2.add(tan2.normalize(dist)), pt2]); + return; + } + var uPrime = this.chordLengthParameterize(first, last), + maxError = Math.max(error, error * error), + split, + parametersInOrder = true; + for (var i = 0; i <= 4; i++) { + var curve = this.generateBezier(first, last, uPrime, tan1, tan2); + var max = this.findMaxError(first, last, curve, uPrime); + if (max.error < error && parametersInOrder) { + this.addCurve(segments, curve); + return; + } + split = max.index; + if (max.error >= maxError) + break; + parametersInOrder = this.reparameterize(first, last, uPrime, curve); + maxError = max.error; + } + var tanCenter = points[split - 1].subtract(points[split + 1]); + this.fitCubic(segments, error, first, split, tan1, tanCenter); + this.fitCubic(segments, error, split, last, tanCenter.negate(), tan2); + }, + + addCurve: function(segments, curve) { + var prev = segments[segments.length - 1]; + prev.setHandleOut(curve[1].subtract(curve[0])); + segments.push(new Segment(curve[3], curve[2].subtract(curve[3]))); + }, + + generateBezier: function(first, last, uPrime, tan1, tan2) { + var epsilon = 1e-12, + abs = Math.abs, + points = this.points, + pt1 = points[first], + pt2 = points[last], + C = [[0, 0], [0, 0]], + X = [0, 0]; + + for (var i = 0, l = last - first + 1; i < l; i++) { + var u = uPrime[i], + t = 1 - u, + b = 3 * u * t, + b0 = t * t * t, + b1 = b * t, + b2 = b * u, + b3 = u * u * u, + a1 = tan1.normalize(b1), + a2 = tan2.normalize(b2), + tmp = points[first + i] + .subtract(pt1.multiply(b0 + b1)) + .subtract(pt2.multiply(b2 + b3)); + C[0][0] += a1.dot(a1); + C[0][1] += a1.dot(a2); + C[1][0] = C[0][1]; + C[1][1] += a2.dot(a2); + X[0] += a1.dot(tmp); + X[1] += a2.dot(tmp); + } + + var detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1], + alpha1, + alpha2; + if (abs(detC0C1) > epsilon) { + var detC0X = C[0][0] * X[1] - C[1][0] * X[0], + detXC1 = X[0] * C[1][1] - X[1] * C[0][1]; + alpha1 = detXC1 / detC0C1; + alpha2 = detC0X / detC0C1; + } else { + var c0 = C[0][0] + C[0][1], + c1 = C[1][0] + C[1][1]; + alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 + : abs(c1) > epsilon ? X[1] / c1 + : 0; + } + + var segLength = pt2.getDistance(pt1), + eps = epsilon * segLength, + handle1, + handle2; + if (alpha1 < eps || alpha2 < eps) { + alpha1 = alpha2 = segLength / 3; + } else { + var line = pt2.subtract(pt1); + handle1 = tan1.normalize(alpha1); + handle2 = tan2.normalize(alpha2); + if (handle1.dot(line) - handle2.dot(line) > segLength * segLength) { + alpha1 = alpha2 = segLength / 3; + handle1 = handle2 = null; + } + } + + return [pt1, + pt1.add(handle1 || tan1.normalize(alpha1)), + pt2.add(handle2 || tan2.normalize(alpha2)), + pt2]; + }, + + reparameterize: function(first, last, u, curve) { + for (var i = first; i <= last; i++) { + u[i - first] = this.findRoot(curve, this.points[i], u[i - first]); + } + for (var i = 1, l = u.length; i < l; i++) { + if (u[i] <= u[i - 1]) + return false; + } + return true; + }, + + findRoot: function(curve, point, u) { + var curve1 = [], + curve2 = []; + for (var i = 0; i <= 2; i++) { + curve1[i] = curve[i + 1].subtract(curve[i]).multiply(3); + } + for (var i = 0; i <= 1; i++) { + curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2); + } + var pt = this.evaluate(3, curve, u), + pt1 = this.evaluate(2, curve1, u), + pt2 = this.evaluate(1, curve2, u), + diff = pt.subtract(point), + df = pt1.dot(pt1) + diff.dot(pt2); + return Numerical.isMachineZero(df) ? u : u - diff.dot(pt1) / df; + }, + + evaluate: function(degree, curve, t) { + var tmp = curve.slice(); + for (var i = 1; i <= degree; i++) { + for (var j = 0; j <= degree - i; j++) { + tmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t)); + } + } + return tmp[0]; + }, + + chordLengthParameterize: function(first, last) { + var u = [0]; + for (var i = first + 1; i <= last; i++) { + u[i - first] = u[i - first - 1] + + this.points[i].getDistance(this.points[i - 1]); + } + for (var i = 1, m = last - first; i <= m; i++) { + u[i] /= u[m]; + } + return u; + }, + + findMaxError: function(first, last, curve, u) { + var index = Math.floor((last - first + 1) / 2), + maxDist = 0; + for (var i = first + 1; i < last; i++) { + var P = this.evaluate(3, curve, u[i - first]); + var v = P.subtract(this.points[i]); + var dist = v.x * v.x + v.y * v.y; + if (dist >= maxDist) { + maxDist = dist; + index = i; + } + } + return { + error: maxDist, + index: index + }; + } +}); + +var TextItem = Item.extend({ + _class: 'TextItem', + _applyMatrix: false, + _canApplyMatrix: false, + _serializeFields: { + content: null + }, + _boundsOptions: { stroke: false, handle: false }, + + initialize: function TextItem(arg) { + this._content = ''; + this._lines = []; + var hasProps = arg && Base.isPlainObject(arg) + && arg.x === undefined && arg.y === undefined; + this._initialize(hasProps && arg, !hasProps && Point.read(arguments)); + }, + + _equals: function(item) { + return this._content === item._content; + }, + + copyContent: function(source) { + this.setContent(source._content); + }, + + getContent: function() { + return this._content; + }, + + setContent: function(content) { + this._content = '' + content; + this._lines = this._content.split(/\r\n|\n|\r/mg); + this._changed(521); + }, + + isEmpty: function() { + return !this._content; + }, + + getCharacterStyle: '#getStyle', + setCharacterStyle: '#setStyle', + + getParagraphStyle: '#getStyle', + setParagraphStyle: '#setStyle' +}); + +var PointText = TextItem.extend({ + _class: 'PointText', + + initialize: function PointText() { + TextItem.apply(this, arguments); + }, + + getPoint: function() { + var point = this._matrix.getTranslation(); + return new LinkedPoint(point.x, point.y, this, 'setPoint'); + }, + + setPoint: function() { + var point = Point.read(arguments); + this.translate(point.subtract(this._matrix.getTranslation())); + }, + + _draw: function(ctx, param, viewMatrix) { + if (!this._content) + return; + this._setStyles(ctx, param, viewMatrix); + var lines = this._lines, + style = this._style, + hasFill = style.hasFill(), + hasStroke = style.hasStroke(), + leading = style.getLeading(), + shadowColor = ctx.shadowColor; + ctx.font = style.getFontStyle(); + ctx.textAlign = style.getJustification(); + for (var i = 0, l = lines.length; i < l; i++) { + ctx.shadowColor = shadowColor; + var line = lines[i]; + if (hasFill) { + ctx.fillText(line, 0, 0); + ctx.shadowColor = 'rgba(0,0,0,0)'; + } + if (hasStroke) + ctx.strokeText(line, 0, 0); + ctx.translate(0, leading); + } + }, + + _getBounds: function(matrix, options) { + var style = this._style, + lines = this._lines, + numLines = lines.length, + justification = style.getJustification(), + leading = style.getLeading(), + width = this.getView().getTextWidth(style.getFontStyle(), lines), + x = 0; + if (justification !== 'left') + x -= width / (justification === 'center' ? 2: 1); + var rect = new Rectangle(x, + numLines ? - 0.75 * leading : 0, + width, numLines * leading); + return matrix ? matrix._transformBounds(rect, rect) : rect; + } +}); + +var Color = Base.extend(new function() { + var types = { + gray: ['gray'], + rgb: ['red', 'green', 'blue'], + hsb: ['hue', 'saturation', 'brightness'], + hsl: ['hue', 'saturation', 'lightness'], + gradient: ['gradient', 'origin', 'destination', 'highlight'] + }; + + var componentParsers = {}, + namedColors = { + transparent: [0, 0, 0, 0] + }, + colorCtx; + + function fromCSS(string) { + var match = string.match( + /^#([\da-f]{2})([\da-f]{2})([\da-f]{2})([\da-f]{2})?$/i + ) || string.match( + /^#([\da-f])([\da-f])([\da-f])([\da-f])?$/i + ), + type = 'rgb', + components; + if (match) { + var amount = match[4] ? 4 : 3; + components = new Array(amount); + for (var i = 0; i < amount; i++) { + var value = match[i + 1]; + components[i] = parseInt(value.length == 1 + ? value + value : value, 16) / 255; + } + } else if (match = string.match(/^(rgb|hsl)a?\((.*)\)$/)) { + type = match[1]; + components = match[2].trim().split(/[,\s]+/g); + var isHSL = type === 'hsl'; + for (var i = 0, l = Math.min(components.length, 4); i < l; i++) { + var component = components[i]; + var value = parseFloat(component); + if (isHSL) { + if (i === 0) { + var unit = component.match(/([a-z]*)$/)[1]; + value *= ({ + turn: 360, + rad: 180 / Math.PI, + grad: 0.9 + }[unit] || 1); + } else if (i < 3) { + value /= 100; + } + } else if (i < 3) { + value /= /%$/.test(component) ? 100 : 255; + } + components[i] = value; + } + } else { + var color = namedColors[string]; + if (!color) { + if (window) { + if (!colorCtx) { + colorCtx = CanvasProvider.getContext(1, 1); + colorCtx.globalCompositeOperation = 'copy'; + } + colorCtx.fillStyle = 'rgba(0,0,0,0)'; + colorCtx.fillStyle = string; + colorCtx.fillRect(0, 0, 1, 1); + var data = colorCtx.getImageData(0, 0, 1, 1).data; + color = namedColors[string] = [ + data[0] / 255, + data[1] / 255, + data[2] / 255 + ]; + } else { + color = [0, 0, 0]; + } + } + components = color.slice(); + } + return [type, components]; + } + + var hsbIndices = [ + [0, 3, 1], + [2, 0, 1], + [1, 0, 3], + [1, 2, 0], + [3, 1, 0], + [0, 1, 2] + ]; + + var converters = { + 'rgb-hsb': function(r, g, b) { + var max = Math.max(r, g, b), + min = Math.min(r, g, b), + delta = max - min, + h = delta === 0 ? 0 + : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) + : max == g ? (b - r) / delta + 2 + : (r - g) / delta + 4) * 60; + return [h, max === 0 ? 0 : delta / max, max]; + }, + + 'hsb-rgb': function(h, s, b) { + h = (((h / 60) % 6) + 6) % 6; + var i = Math.floor(h), + f = h - i, + i = hsbIndices[i], + v = [ + b, + b * (1 - s), + b * (1 - s * f), + b * (1 - s * (1 - f)) + ]; + return [v[i[0]], v[i[1]], v[i[2]]]; + }, + + 'rgb-hsl': function(r, g, b) { + var max = Math.max(r, g, b), + min = Math.min(r, g, b), + delta = max - min, + achromatic = delta === 0, + h = achromatic ? 0 + : ( max == r ? (g - b) / delta + (g < b ? 6 : 0) + : max == g ? (b - r) / delta + 2 + : (r - g) / delta + 4) * 60, + l = (max + min) / 2, + s = achromatic ? 0 : l < 0.5 + ? delta / (max + min) + : delta / (2 - max - min); + return [h, s, l]; + }, + + 'hsl-rgb': function(h, s, l) { + h = (((h / 360) % 1) + 1) % 1; + if (s === 0) + return [l, l, l]; + var t3s = [ h + 1 / 3, h, h - 1 / 3 ], + t2 = l < 0.5 ? l * (1 + s) : l + s - l * s, + t1 = 2 * l - t2, + c = []; + for (var i = 0; i < 3; i++) { + var t3 = t3s[i]; + if (t3 < 0) t3 += 1; + if (t3 > 1) t3 -= 1; + c[i] = 6 * t3 < 1 + ? t1 + (t2 - t1) * 6 * t3 + : 2 * t3 < 1 + ? t2 + : 3 * t3 < 2 + ? t1 + (t2 - t1) * ((2 / 3) - t3) * 6 + : t1; + } + return c; + }, + + 'rgb-gray': function(r, g, b) { + return [r * 0.2989 + g * 0.587 + b * 0.114]; + }, + + 'gray-rgb': function(g) { + return [g, g, g]; + }, + + 'gray-hsb': function(g) { + return [0, 0, g]; + }, + + 'gray-hsl': function(g) { + return [0, 0, g]; + }, + + 'gradient-rgb': function() { + return []; + }, + + 'rgb-gradient': function() { + return []; + } + + }; + + return Base.each(types, function(properties, type) { + componentParsers[type] = []; + Base.each(properties, function(name, index) { + var part = Base.capitalize(name), + hasOverlap = /^(hue|saturation)$/.test(name), + parser = componentParsers[type][index] = type === 'gradient' + ? name === 'gradient' + ? function(value) { + var current = this._components[0]; + value = Gradient.read( + Array.isArray(value) + ? value + : arguments, 0, { readNull: true } + ); + if (current !== value) { + if (current) + current._removeOwner(this); + if (value) + value._addOwner(this); + } + return value; + } + : function() { + return Point.read(arguments, 0, { + readNull: name === 'highlight', + clone: true + }); + } + : function(value) { + return value == null || isNaN(value) ? 0 : +value; + }; + this['get' + part] = function() { + return this._type === type + || hasOverlap && /^hs[bl]$/.test(this._type) + ? this._components[index] + : this._convert(type)[index]; + }; + + this['set' + part] = function(value) { + if (this._type !== type + && !(hasOverlap && /^hs[bl]$/.test(this._type))) { + this._components = this._convert(type); + this._properties = types[type]; + this._type = type; + } + this._components[index] = parser.call(this, value); + this._changed(); + }; + }, this); + }, { + _class: 'Color', + _readIndex: true, + + initialize: function Color(arg) { + var args = arguments, + reading = this.__read, + read = 0, + type, + components, + alpha, + values; + if (Array.isArray(arg)) { + args = arg; + arg = args[0]; + } + var argType = arg != null && typeof arg; + if (argType === 'string' && arg in types) { + type = arg; + arg = args[1]; + if (Array.isArray(arg)) { + components = arg; + alpha = args[2]; + } else { + if (reading) + read = 1; + args = Base.slice(args, 1); + argType = typeof arg; + } + } + if (!components) { + values = argType === 'number' + ? args + : argType === 'object' && arg.length != null + ? arg + : null; + if (values) { + if (!type) + type = values.length >= 3 + ? 'rgb' + : 'gray'; + var length = types[type].length; + alpha = values[length]; + if (reading) { + read += values === arguments + ? length + (alpha != null ? 1 : 0) + : 1; + } + if (values.length > length) + values = Base.slice(values, 0, length); + } else if (argType === 'string') { + var converted = fromCSS(arg); + type = converted[0]; + components = converted[1]; + if (components.length === 4) { + alpha = components[3]; + components.length--; + } + } else if (argType === 'object') { + if (arg.constructor === Color) { + type = arg._type; + components = arg._components.slice(); + alpha = arg._alpha; + if (type === 'gradient') { + for (var i = 1, l = components.length; i < l; i++) { + var point = components[i]; + if (point) + components[i] = point.clone(); + } + } + } else if (arg.constructor === Gradient) { + type = 'gradient'; + values = args; + } else { + type = 'hue' in arg + ? 'lightness' in arg + ? 'hsl' + : 'hsb' + : 'gradient' in arg || 'stops' in arg + || 'radial' in arg + ? 'gradient' + : 'gray' in arg + ? 'gray' + : 'rgb'; + var properties = types[type], + parsers = componentParsers[type]; + this._components = components = []; + for (var i = 0, l = properties.length; i < l; i++) { + var value = arg[properties[i]]; + if (value == null && !i && type === 'gradient' + && 'stops' in arg) { + value = { + stops: arg.stops, + radial: arg.radial + }; + } + value = parsers[i].call(this, value); + if (value != null) + components[i] = value; + } + alpha = arg.alpha; + } + } + if (reading && type) + read = 1; + } + this._type = type || 'rgb'; + if (!components) { + this._components = components = []; + var parsers = componentParsers[this._type]; + for (var i = 0, l = parsers.length; i < l; i++) { + var value = parsers[i].call(this, values && values[i]); + if (value != null) + components[i] = value; + } + } + this._components = components; + this._properties = types[this._type]; + this._alpha = alpha; + if (reading) + this.__read = read; + return this; + }, + + set: '#initialize', + + _serialize: function(options, dictionary) { + var components = this.getComponents(); + return Base.serialize( + /^(gray|rgb)$/.test(this._type) + ? components + : [this._type].concat(components), + options, true, dictionary); + }, + + _changed: function() { + this._canvasStyle = null; + if (this._owner) { + if (this._setter) { + this._owner[this._setter](this); + } else { + this._owner._changed(129); + } + } + }, + + _convert: function(type) { + var converter; + return this._type === type + ? this._components.slice() + : (converter = converters[this._type + '-' + type]) + ? converter.apply(this, this._components) + : converters['rgb-' + type].apply(this, + converters[this._type + '-rgb'].apply(this, + this._components)); + }, + + convert: function(type) { + return new Color(type, this._convert(type), this._alpha); + }, + + getType: function() { + return this._type; + }, + + setType: function(type) { + this._components = this._convert(type); + this._properties = types[type]; + this._type = type; + }, + + getComponents: function() { + var components = this._components.slice(); + if (this._alpha != null) + components.push(this._alpha); + return components; + }, + + getAlpha: function() { + return this._alpha != null ? this._alpha : 1; + }, + + setAlpha: function(alpha) { + this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1); + this._changed(); + }, + + hasAlpha: function() { + return this._alpha != null; + }, + + equals: function(color) { + var col = Base.isPlainValue(color, true) + ? Color.read(arguments) + : color; + return col === this || col && this._class === col._class + && this._type === col._type + && this.getAlpha() === col.getAlpha() + && Base.equals(this._components, col._components) + || false; + }, + + toString: function() { + var properties = this._properties, + parts = [], + isGradient = this._type === 'gradient', + f = Formatter.instance; + for (var i = 0, l = properties.length; i < l; i++) { + var value = this._components[i]; + if (value != null) + parts.push(properties[i] + ': ' + + (isGradient ? value : f.number(value))); + } + if (this._alpha != null) + parts.push('alpha: ' + f.number(this._alpha)); + return '{ ' + parts.join(', ') + ' }'; + }, + + toCSS: function(hex) { + var components = this._convert('rgb'), + alpha = hex || this._alpha == null ? 1 : this._alpha; + function convert(val) { + return Math.round((val < 0 ? 0 : val > 1 ? 1 : val) * 255); + } + components = [ + convert(components[0]), + convert(components[1]), + convert(components[2]) + ]; + if (alpha < 1) + components.push(alpha < 0 ? 0 : alpha); + return hex + ? '#' + ((1 << 24) + (components[0] << 16) + + (components[1] << 8) + + components[2]).toString(16).slice(1) + : (components.length == 4 ? 'rgba(' : 'rgb(') + + components.join(',') + ')'; + }, + + toCanvasStyle: function(ctx, matrix) { + if (this._canvasStyle) + return this._canvasStyle; + if (this._type !== 'gradient') + return this._canvasStyle = this.toCSS(); + var components = this._components, + gradient = components[0], + stops = gradient._stops, + origin = components[1], + destination = components[2], + highlight = components[3], + inverse = matrix && matrix.inverted(), + canvasGradient; + if (inverse) { + origin = inverse._transformPoint(origin); + destination = inverse._transformPoint(destination); + if (highlight) + highlight = inverse._transformPoint(highlight); + } + if (gradient._radial) { + var radius = destination.getDistance(origin); + if (highlight) { + var vector = highlight.subtract(origin); + if (vector.getLength() > radius) + highlight = origin.add(vector.normalize(radius - 0.1)); + } + var start = highlight || origin; + canvasGradient = ctx.createRadialGradient(start.x, start.y, + 0, origin.x, origin.y, radius); + } else { + canvasGradient = ctx.createLinearGradient(origin.x, origin.y, + destination.x, destination.y); + } + for (var i = 0, l = stops.length; i < l; i++) { + var stop = stops[i], + offset = stop._offset; + canvasGradient.addColorStop( + offset == null ? i / (l - 1) : offset, + stop._color.toCanvasStyle()); + } + return this._canvasStyle = canvasGradient; + }, + + transform: function(matrix) { + if (this._type === 'gradient') { + var components = this._components; + for (var i = 1, l = components.length; i < l; i++) { + var point = components[i]; + matrix._transformPoint(point, point, true); + } + this._changed(); + } + }, + + statics: { + _types: types, + + random: function() { + var random = Math.random; + return new Color(random(), random(), random()); + }, + + _setOwner: function(color, owner, setter) { + if (color) { + if (color._owner && owner && color._owner !== owner) { + color = color.clone(); + } + if (!color._owner ^ !owner) { + color._owner = owner || null; + color._setter = setter || null; + } + } + return color; + } + } + }); +}, +new function() { + var operators = { + add: function(a, b) { + return a + b; + }, + + subtract: function(a, b) { + return a - b; + }, + + multiply: function(a, b) { + return a * b; + }, + + divide: function(a, b) { + return a / b; + } + }; + + return Base.each(operators, function(operator, name) { + this[name] = function(color) { + color = Color.read(arguments); + var type = this._type, + components1 = this._components, + components2 = color._convert(type); + for (var i = 0, l = components1.length; i < l; i++) + components2[i] = operator(components1[i], components2[i]); + return new Color(type, components2, + this._alpha != null + ? operator(this._alpha, color.getAlpha()) + : null); + }; + }, { + }); +}); + +var Gradient = Base.extend({ + _class: 'Gradient', + + initialize: function Gradient(stops, radial) { + this._id = UID.get(); + if (stops && Base.isPlainObject(stops)) { + this.set(stops); + stops = radial = null; + } + if (this._stops == null) { + this.setStops(stops || ['white', 'black']); + } + if (this._radial == null) { + this.setRadial(typeof radial === 'string' && radial === 'radial' + || radial || false); + } + }, + + _serialize: function(options, dictionary) { + return dictionary.add(this, function() { + return Base.serialize([this._stops, this._radial], + options, true, dictionary); + }); + }, + + _changed: function() { + for (var i = 0, l = this._owners && this._owners.length; i < l; i++) { + this._owners[i]._changed(); + } + }, + + _addOwner: function(color) { + if (!this._owners) + this._owners = []; + this._owners.push(color); + }, + + _removeOwner: function(color) { + var index = this._owners ? this._owners.indexOf(color) : -1; + if (index != -1) { + this._owners.splice(index, 1); + if (!this._owners.length) + this._owners = undefined; + } + }, + + clone: function() { + var stops = []; + for (var i = 0, l = this._stops.length; i < l; i++) { + stops[i] = this._stops[i].clone(); + } + return new Gradient(stops, this._radial); + }, + + getStops: function() { + return this._stops; + }, + + setStops: function(stops) { + if (stops.length < 2) { + throw new Error( + 'Gradient stop list needs to contain at least two stops.'); + } + var _stops = this._stops; + if (_stops) { + for (var i = 0, l = _stops.length; i < l; i++) + _stops[i]._owner = undefined; + } + _stops = this._stops = GradientStop.readList(stops, 0, { clone: true }); + for (var i = 0, l = _stops.length; i < l; i++) + _stops[i]._owner = this; + this._changed(); + }, + + getRadial: function() { + return this._radial; + }, + + setRadial: function(radial) { + this._radial = radial; + this._changed(); + }, + + equals: function(gradient) { + if (gradient === this) + return true; + if (gradient && this._class === gradient._class) { + var stops1 = this._stops, + stops2 = gradient._stops, + length = stops1.length; + if (length === stops2.length) { + for (var i = 0; i < length; i++) { + if (!stops1[i].equals(stops2[i])) + return false; + } + return true; + } + } + return false; + } +}); + +var GradientStop = Base.extend({ + _class: 'GradientStop', + + initialize: function GradientStop(arg0, arg1) { + var color = arg0, + offset = arg1; + if (typeof arg0 === 'object' && arg1 === undefined) { + if (Array.isArray(arg0) && typeof arg0[0] !== 'number') { + color = arg0[0]; + offset = arg0[1]; + } else if ('color' in arg0 || 'offset' in arg0 + || 'rampPoint' in arg0) { + color = arg0.color; + offset = arg0.offset || arg0.rampPoint || 0; + } + } + this.setColor(color); + this.setOffset(offset); + }, + + clone: function() { + return new GradientStop(this._color.clone(), this._offset); + }, + + _serialize: function(options, dictionary) { + var color = this._color, + offset = this._offset; + return Base.serialize(offset == null ? [color] : [color, offset], + options, true, dictionary); + }, + + _changed: function() { + if (this._owner) + this._owner._changed(129); + }, + + getOffset: function() { + return this._offset; + }, + + setOffset: function(offset) { + this._offset = offset; + this._changed(); + }, + + getRampPoint: '#getOffset', + setRampPoint: '#setOffset', + + getColor: function() { + return this._color; + }, + + setColor: function() { + Color._setOwner(this._color, null); + this._color = Color._setOwner(Color.read(arguments, 0), this, + 'setColor'); + this._changed(); + }, + + equals: function(stop) { + return stop === this || stop && this._class === stop._class + && this._color.equals(stop._color) + && this._offset == stop._offset + || false; + } +}); + +var Style = Base.extend(new function() { + var itemDefaults = { + fillColor: null, + fillRule: 'nonzero', + strokeColor: null, + strokeWidth: 1, + strokeCap: 'butt', + strokeJoin: 'miter', + strokeScaling: true, + miterLimit: 10, + dashOffset: 0, + dashArray: [], + shadowColor: null, + shadowBlur: 0, + shadowOffset: new Point(), + selectedColor: null + }, + groupDefaults = Base.set({}, itemDefaults, { + fontFamily: 'sans-serif', + fontWeight: 'normal', + fontSize: 12, + leading: null, + justification: 'left' + }), + textDefaults = Base.set({}, groupDefaults, { + fillColor: new Color() + }), + flags = { + strokeWidth: 193, + strokeCap: 193, + strokeJoin: 193, + strokeScaling: 201, + miterLimit: 193, + fontFamily: 9, + fontWeight: 9, + fontSize: 9, + font: 9, + leading: 9, + justification: 9 + }, + item = { + beans: true + }, + fields = { + _class: 'Style', + beans: true, + + initialize: function Style(style, _owner, _project) { + this._values = {}; + this._owner = _owner; + this._project = _owner && _owner._project || _project + || paper.project; + this._defaults = !_owner || _owner instanceof Group ? groupDefaults + : _owner instanceof TextItem ? textDefaults + : itemDefaults; + if (style) + this.set(style); + } + }; + + Base.each(groupDefaults, function(value, key) { + var isColor = /Color$/.test(key), + isPoint = key === 'shadowOffset', + part = Base.capitalize(key), + flag = flags[key], + set = 'set' + part, + get = 'get' + part; + + fields[set] = function(value) { + var owner = this._owner, + children = owner && owner._children, + applyToChildren = children && children.length > 0 + && !(owner instanceof CompoundPath); + if (applyToChildren) { + for (var i = 0, l = children.length; i < l; i++) + children[i]._style[set](value); + } + if ((key === 'selectedColor' || !applyToChildren) + && key in this._defaults) { + var old = this._values[key]; + if (old !== value) { + if (isColor) { + if (old) { + Color._setOwner(old, null); + old._canvasStyle = null; + } + if (value && value.constructor === Color) { + value = Color._setOwner(value, owner, + applyToChildren && set); + } + } + this._values[key] = value; + if (owner) + owner._changed(flag || 129); + } + } + }; + + fields[get] = function(_dontMerge) { + var owner = this._owner, + children = owner && owner._children, + applyToChildren = children && children.length > 0 + && !(owner instanceof CompoundPath), + value; + if (applyToChildren && !_dontMerge) { + for (var i = 0, l = children.length; i < l; i++) { + var childValue = children[i]._style[get](); + if (!i) { + value = childValue; + } else if (!Base.equals(value, childValue)) { + return undefined; + } + } + } else if (key in this._defaults) { + var value = this._values[key]; + if (value === undefined) { + value = this._defaults[key]; + if (value && value.clone) { + value = value.clone(); + } + } else { + var ctor = isColor ? Color : isPoint ? Point : null; + if (ctor && !(value && value.constructor === ctor)) { + this._values[key] = value = ctor.read([value], 0, + { readNull: true, clone: true }); + } + } + } + if (value && isColor) { + value = Color._setOwner(value, owner, applyToChildren && set); + } + return value; + }; + + item[get] = function(_dontMerge) { + return this._style[get](_dontMerge); + }; + + item[set] = function(value) { + this._style[set](value); + }; + }); + + Base.each({ + Font: 'FontFamily', + WindingRule: 'FillRule' + }, function(value, key) { + var get = 'get' + key, + set = 'set' + key; + fields[get] = item[get] = '#get' + value; + fields[set] = item[set] = '#set' + value; + }); + + Item.inject(item); + return fields; +}, { + set: function(style) { + var isStyle = style instanceof Style, + values = isStyle ? style._values : style; + if (values) { + for (var key in values) { + if (key in this._defaults) { + var value = values[key]; + this[key] = value && isStyle && value.clone + ? value.clone() : value; + } + } + } + }, + + equals: function(style) { + function compare(style1, style2, secondary) { + var values1 = style1._values, + values2 = style2._values, + defaults2 = style2._defaults; + for (var key in values1) { + var value1 = values1[key], + value2 = values2[key]; + if (!(secondary && key in values2) && !Base.equals(value1, + value2 === undefined ? defaults2[key] : value2)) + return false; + } + return true; + } + + return style === this || style && this._class === style._class + && compare(this, style) + && compare(style, this, true) + || false; + }, + + _dispose: function() { + var color; + color = this.getFillColor(); + if (color) color._canvasStyle = null; + color = this.getStrokeColor(); + if (color) color._canvasStyle = null; + color = this.getShadowColor(); + if (color) color._canvasStyle = null; + }, + + hasFill: function() { + var color = this.getFillColor(); + return !!color && color.alpha > 0; + }, + + hasStroke: function() { + var color = this.getStrokeColor(); + return !!color && color.alpha > 0 && this.getStrokeWidth() > 0; + }, + + hasShadow: function() { + var color = this.getShadowColor(); + return !!color && color.alpha > 0 && (this.getShadowBlur() > 0 + || !this.getShadowOffset().isZero()); + }, + + getView: function() { + return this._project._view; + }, + + getFontStyle: function() { + var fontSize = this.getFontSize(); + return this.getFontWeight() + + ' ' + fontSize + (/[a-z]/i.test(fontSize + '') ? ' ' : 'px ') + + this.getFontFamily(); + }, + + getFont: '#getFontFamily', + setFont: '#setFontFamily', + + getLeading: function getLeading() { + var leading = getLeading.base.call(this), + fontSize = this.getFontSize(); + if (/pt|em|%|px/.test(fontSize)) + fontSize = this.getView().getPixelSize(fontSize); + return leading != null ? leading : fontSize * 1.2; + } + +}); + +var DomElement = new function() { + function handlePrefix(el, name, set, value) { + var prefixes = ['', 'webkit', 'moz', 'Moz', 'ms', 'o'], + suffix = name[0].toUpperCase() + name.substring(1); + for (var i = 0; i < 6; i++) { + var prefix = prefixes[i], + key = prefix ? prefix + suffix : name; + if (key in el) { + if (set) { + el[key] = value; + } else { + return el[key]; + } + break; + } + } + } + + return { + getStyles: function(el) { + var doc = el && el.nodeType !== 9 ? el.ownerDocument : el, + view = doc && doc.defaultView; + return view && view.getComputedStyle(el, ''); + }, + + getBounds: function(el, viewport) { + var doc = el.ownerDocument, + body = doc.body, + html = doc.documentElement, + rect; + try { + rect = el.getBoundingClientRect(); + } catch (e) { + rect = { left: 0, top: 0, width: 0, height: 0 }; + } + var x = rect.left - (html.clientLeft || body.clientLeft || 0), + y = rect.top - (html.clientTop || body.clientTop || 0); + if (!viewport) { + var view = doc.defaultView; + x += view.pageXOffset || html.scrollLeft || body.scrollLeft; + y += view.pageYOffset || html.scrollTop || body.scrollTop; + } + return new Rectangle(x, y, rect.width, rect.height); + }, + + getViewportBounds: function(el) { + var doc = el.ownerDocument, + view = doc.defaultView, + html = doc.documentElement; + return new Rectangle(0, 0, + view.innerWidth || html.clientWidth, + view.innerHeight || html.clientHeight + ); + }, + + getOffset: function(el, viewport) { + return DomElement.getBounds(el, viewport).getPoint(); + }, + + getSize: function(el) { + return DomElement.getBounds(el, true).getSize(); + }, + + isInvisible: function(el) { + return DomElement.getSize(el).equals(new Size(0, 0)); + }, + + isInView: function(el) { + return !DomElement.isInvisible(el) + && DomElement.getViewportBounds(el).intersects( + DomElement.getBounds(el, true)); + }, + + isInserted: function(el) { + return document.body.contains(el); + }, + + getPrefixed: function(el, name) { + return el && handlePrefix(el, name); + }, + + setPrefixed: function(el, name, value) { + if (typeof name === 'object') { + for (var key in name) + handlePrefix(el, key, true, name[key]); + } else { + handlePrefix(el, name, true, value); + } + } + }; +}; + +var DomEvent = { + add: function(el, events) { + if (el) { + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) { + var name = parts[i]; + var options = ( + el === document + && (name === 'touchstart' || name === 'touchmove') + ) ? { passive: false } : false; + el.addEventListener(name, func, options); + } + } + } + }, + + remove: function(el, events) { + if (el) { + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.removeEventListener(parts[i], func, false); + } + } + }, + + getPoint: function(event) { + var pos = event.targetTouches + ? event.targetTouches.length + ? event.targetTouches[0] + : event.changedTouches[0] + : event; + return new Point( + pos.pageX || pos.clientX + document.documentElement.scrollLeft, + pos.pageY || pos.clientY + document.documentElement.scrollTop + ); + }, + + getTarget: function(event) { + return event.target || event.srcElement; + }, + + getRelatedTarget: function(event) { + return event.relatedTarget || event.toElement; + }, + + getOffset: function(event, target) { + return DomEvent.getPoint(event).subtract(DomElement.getOffset( + target || DomEvent.getTarget(event))); + } +}; + +DomEvent.requestAnimationFrame = new function() { + var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), + requested = false, + callbacks = [], + timer; + + function handleCallbacks() { + var functions = callbacks; + callbacks = []; + for (var i = 0, l = functions.length; i < l; i++) + functions[i](); + requested = nativeRequest && callbacks.length; + if (requested) + nativeRequest(handleCallbacks); + } + + return function(callback) { + callbacks.push(callback); + if (nativeRequest) { + if (!requested) { + nativeRequest(handleCallbacks); + requested = true; + } + } else if (!timer) { + timer = setInterval(handleCallbacks, 1000 / 60); + } + }; +}; + +var View = Base.extend(Emitter, { + _class: 'View', + + initialize: function View(project, element) { + + function getSize(name) { + return element[name] || parseInt(element.getAttribute(name), 10); + } + + function getCanvasSize() { + var size = DomElement.getSize(element); + return size.isNaN() || size.isZero() + ? new Size(getSize('width'), getSize('height')) + : size; + } + + var size; + if (window && element) { + this._id = element.getAttribute('id'); + if (this._id == null) + element.setAttribute('id', this._id = 'paper-view-' + View._id++); + DomEvent.add(element, this._viewEvents); + var none = 'none'; + DomElement.setPrefixed(element.style, { + userDrag: none, + userSelect: none, + touchCallout: none, + contentZooming: none, + tapHighlightColor: 'rgba(0,0,0,0)' + }); + + if (PaperScope.hasAttribute(element, 'resize')) { + var that = this; + DomEvent.add(window, this._windowEvents = { + resize: function() { + that.setViewSize(getCanvasSize()); + } + }); + } + + size = getCanvasSize(); + + if (PaperScope.hasAttribute(element, 'stats') + && typeof Stats !== 'undefined') { + this._stats = new Stats(); + var stats = this._stats.domElement, + style = stats.style, + offset = DomElement.getOffset(element); + style.position = 'absolute'; + style.left = offset.x + 'px'; + style.top = offset.y + 'px'; + document.body.appendChild(stats); + } + } else { + size = new Size(element); + element = null; + } + this._project = project; + this._scope = project._scope; + this._element = element; + if (!this._pixelRatio) + this._pixelRatio = window && window.devicePixelRatio || 1; + this._setElementSize(size.width, size.height); + this._viewSize = size; + View._views.push(this); + View._viewsById[this._id] = this; + (this._matrix = new Matrix())._owner = this; + if (!View._focused) + View._focused = this; + this._frameItems = {}; + this._frameItemCount = 0; + this._itemEvents = { native: {}, virtual: {} }; + this._autoUpdate = !paper.agent.node; + this._needsUpdate = false; + }, + + remove: function() { + if (!this._project) + return false; + if (View._focused === this) + View._focused = null; + View._views.splice(View._views.indexOf(this), 1); + delete View._viewsById[this._id]; + var project = this._project; + if (project._view === this) + project._view = null; + DomEvent.remove(this._element, this._viewEvents); + DomEvent.remove(window, this._windowEvents); + this._element = this._project = null; + this.off('frame'); + this._animate = false; + this._frameItems = {}; + return true; + }, + + _events: Base.each( + Item._itemHandlers.concat(['onResize', 'onKeyDown', 'onKeyUp']), + function(name) { + this[name] = {}; + }, { + onFrame: { + install: function() { + this.play(); + }, + + uninstall: function() { + this.pause(); + } + } + } + ), + + _animate: false, + _time: 0, + _count: 0, + + getAutoUpdate: function() { + return this._autoUpdate; + }, + + setAutoUpdate: function(autoUpdate) { + this._autoUpdate = autoUpdate; + if (autoUpdate) + this.requestUpdate(); + }, + + update: function() { + }, + + draw: function() { + this.update(); + }, + + requestUpdate: function() { + if (!this._requested) { + var that = this; + DomEvent.requestAnimationFrame(function() { + that._requested = false; + if (that._animate) { + that.requestUpdate(); + var element = that._element; + if ((!DomElement.getPrefixed(document, 'hidden') + || PaperScope.getAttribute(element, 'keepalive') + === 'true') && DomElement.isInView(element)) { + that._handleFrame(); + } + } + if (that._autoUpdate) + that.update(); + }); + this._requested = true; + } + }, + + play: function() { + this._animate = true; + this.requestUpdate(); + }, + + pause: function() { + this._animate = false; + }, + + _handleFrame: function() { + paper = this._scope; + var now = Date.now() / 1000, + delta = this._last ? now - this._last : 0; + this._last = now; + this.emit('frame', new Base({ + delta: delta, + time: this._time += delta, + count: this._count++ + })); + if (this._stats) + this._stats.update(); + }, + + _animateItem: function(item, animate) { + var items = this._frameItems; + if (animate) { + items[item._id] = { + item: item, + time: 0, + count: 0 + }; + if (++this._frameItemCount === 1) + this.on('frame', this._handleFrameItems); + } else { + delete items[item._id]; + if (--this._frameItemCount === 0) { + this.off('frame', this._handleFrameItems); + } + } + }, + + _handleFrameItems: function(event) { + for (var i in this._frameItems) { + var entry = this._frameItems[i]; + entry.item.emit('frame', new Base(event, { + time: entry.time += event.delta, + count: entry.count++ + })); + } + }, + + _changed: function() { + this._project._changed(4097); + this._bounds = this._decomposed = undefined; + }, + + getElement: function() { + return this._element; + }, + + getPixelRatio: function() { + return this._pixelRatio; + }, + + getResolution: function() { + return this._pixelRatio * 72; + }, + + getViewSize: function() { + var size = this._viewSize; + return new LinkedSize(size.width, size.height, this, 'setViewSize'); + }, + + setViewSize: function() { + var size = Size.read(arguments), + delta = size.subtract(this._viewSize); + if (delta.isZero()) + return; + this._setElementSize(size.width, size.height); + this._viewSize.set(size); + this._changed(); + this.emit('resize', { size: size, delta: delta }); + if (this._autoUpdate) { + this.update(); + } + }, + + _setElementSize: function(width, height) { + var element = this._element; + if (element) { + if (element.width !== width) + element.width = width; + if (element.height !== height) + element.height = height; + } + }, + + getBounds: function() { + if (!this._bounds) + this._bounds = this._matrix.inverted()._transformBounds( + new Rectangle(new Point(), this._viewSize)); + return this._bounds; + }, + + getSize: function() { + return this.getBounds().getSize(); + }, + + isVisible: function() { + return DomElement.isInView(this._element); + }, + + isInserted: function() { + return DomElement.isInserted(this._element); + }, + + getPixelSize: function(size) { + var element = this._element, + pixels; + if (element) { + var parent = element.parentNode, + temp = document.createElement('div'); + temp.style.fontSize = size; + parent.appendChild(temp); + pixels = parseFloat(DomElement.getStyles(temp).fontSize); + parent.removeChild(temp); + } else { + pixels = parseFloat(pixels); + } + return pixels; + }, + + getTextWidth: function(font, lines) { + return 0; + } +}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) { + var rotate = key === 'rotate'; + this[key] = function() { + var args = arguments, + value = (rotate ? Base : Point).read(args), + center = Point.read(args, 0, { readNull: true }); + return this.transform(new Matrix()[key](value, + center || this.getCenter(true))); + }; +}, { + _decompose: function() { + return this._decomposed || (this._decomposed = this._matrix.decompose()); + }, + + translate: function() { + var mx = new Matrix(); + return this.transform(mx.translate.apply(mx, arguments)); + }, + + getCenter: function() { + return this.getBounds().getCenter(); + }, + + setCenter: function() { + var center = Point.read(arguments); + this.translate(this.getCenter().subtract(center)); + }, + + getZoom: function() { + var scaling = this._decompose().scaling; + return (scaling.x + scaling.y) / 2; + }, + + setZoom: function(zoom) { + this.transform(new Matrix().scale(zoom / this.getZoom(), + this.getCenter())); + }, + + getRotation: function() { + return this._decompose().rotation; + }, + + setRotation: function(rotation) { + var current = this.getRotation(); + if (current != null && rotation != null) { + this.rotate(rotation - current); + } + }, + + getScaling: function() { + var scaling = this._decompose().scaling; + return new LinkedPoint(scaling.x, scaling.y, this, 'setScaling'); + }, + + setScaling: function() { + var current = this.getScaling(), + scaling = Point.read(arguments, 0, { clone: true, readNull: true }); + if (current && scaling) { + this.scale(scaling.x / current.x, scaling.y / current.y); + } + }, + + getMatrix: function() { + return this._matrix; + }, + + setMatrix: function() { + var matrix = this._matrix; + matrix.set.apply(matrix, arguments); + }, + + transform: function(matrix) { + this._matrix.append(matrix); + }, + + scrollBy: function() { + this.translate(Point.read(arguments).negate()); + } +}), { + + projectToView: function() { + return this._matrix._transformPoint(Point.read(arguments)); + }, + + viewToProject: function() { + return this._matrix._inverseTransform(Point.read(arguments)); + }, + + getEventPoint: function(event) { + return this.viewToProject(DomEvent.getOffset(event, this._element)); + }, + +}, { + statics: { + _views: [], + _viewsById: {}, + _id: 0, + + create: function(project, element) { + if (document && typeof element === 'string') + element = document.getElementById(element); + var ctor = window ? CanvasView : View; + return new ctor(project, element); + } + } +}, +new function() { + if (!window) + return; + var prevFocus, + tempFocus, + dragging = false, + mouseDown = false; + + function getView(event) { + var target = DomEvent.getTarget(event); + return target.getAttribute && View._viewsById[ + target.getAttribute('id')]; + } + + function updateFocus() { + var view = View._focused; + if (!view || !view.isVisible()) { + for (var i = 0, l = View._views.length; i < l; i++) { + if ((view = View._views[i]).isVisible()) { + View._focused = tempFocus = view; + break; + } + } + } + } + + function handleMouseMove(view, event, point) { + view._handleMouseEvent('mousemove', event, point); + } + + var navigator = window.navigator, + mousedown, mousemove, mouseup; + if (navigator.pointerEnabled || navigator.msPointerEnabled) { + mousedown = 'pointerdown MSPointerDown'; + mousemove = 'pointermove MSPointerMove'; + mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel'; + } else { + mousedown = 'touchstart'; + mousemove = 'touchmove'; + mouseup = 'touchend touchcancel'; + if (!('ontouchstart' in window && navigator.userAgent.match( + /mobile|tablet|ip(ad|hone|od)|android|silk/i))) { + mousedown += ' mousedown'; + mousemove += ' mousemove'; + mouseup += ' mouseup'; + } + } + + var viewEvents = {}, + docEvents = { + mouseout: function(event) { + var view = View._focused, + target = DomEvent.getRelatedTarget(event); + if (view && (!target || target.nodeName === 'HTML')) { + var offset = DomEvent.getOffset(event, view._element), + x = offset.x, + abs = Math.abs, + ax = abs(x), + max = 1 << 25, + diff = ax - max; + offset.x = abs(diff) < ax ? diff * (x < 0 ? -1 : 1) : x; + handleMouseMove(view, event, view.viewToProject(offset)); + } + }, + + scroll: updateFocus + }; + + viewEvents[mousedown] = function(event) { + var view = View._focused = getView(event); + if (!dragging) { + dragging = true; + view._handleMouseEvent('mousedown', event); + } + }; + + docEvents[mousemove] = function(event) { + var view = View._focused; + if (!mouseDown) { + var target = getView(event); + if (target) { + if (view !== target) { + if (view) + handleMouseMove(view, event); + if (!prevFocus) + prevFocus = view; + view = View._focused = tempFocus = target; + } + } else if (tempFocus && tempFocus === view) { + if (prevFocus && !prevFocus.isInserted()) + prevFocus = null; + view = View._focused = prevFocus; + prevFocus = null; + updateFocus(); + } + } + if (view) + handleMouseMove(view, event); + }; + + docEvents[mousedown] = function() { + mouseDown = true; + }; + + docEvents[mouseup] = function(event) { + var view = View._focused; + if (view && dragging) + view._handleMouseEvent('mouseup', event); + mouseDown = dragging = false; + }; + + DomEvent.add(document, docEvents); + + DomEvent.add(window, { + load: updateFocus + }); + + var called = false, + prevented = false, + fallbacks = { + doubleclick: 'click', + mousedrag: 'mousemove' + }, + wasInView = false, + overView, + downPoint, + lastPoint, + downItem, + overItem, + dragItem, + clickItem, + clickTime, + dblClick; + + function emitMouseEvent(obj, target, type, event, point, prevPoint, + stopItem) { + var stopped = false, + mouseEvent; + + function emit(obj, type) { + if (obj.responds(type)) { + if (!mouseEvent) { + mouseEvent = new MouseEvent(type, event, point, + target || obj, + prevPoint ? point.subtract(prevPoint) : null); + } + if (obj.emit(type, mouseEvent)) { + called = true; + if (mouseEvent.prevented) + prevented = true; + if (mouseEvent.stopped) + return stopped = true; + } + } else { + var fallback = fallbacks[type]; + if (fallback) + return emit(obj, fallback); + } + } + + while (obj && obj !== stopItem) { + if (emit(obj, type)) + break; + obj = obj._parent; + } + return stopped; + } + + function emitMouseEvents(view, hitItem, type, event, point, prevPoint) { + view._project.removeOn(type); + prevented = called = false; + return (dragItem && emitMouseEvent(dragItem, null, type, event, + point, prevPoint) + || hitItem && hitItem !== dragItem + && !hitItem.isDescendant(dragItem) + && emitMouseEvent(hitItem, null, type === 'mousedrag' ? + 'mousemove' : type, event, point, prevPoint, dragItem) + || emitMouseEvent(view, dragItem || hitItem || view, type, event, + point, prevPoint)); + } + + var itemEventsMap = { + mousedown: { + mousedown: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mouseup: { + mouseup: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mousemove: { + mousedrag: 1, + mousemove: 1, + mouseenter: 1, + mouseleave: 1 + } + }; + + return { + _viewEvents: viewEvents, + + _handleMouseEvent: function(type, event, point) { + var itemEvents = this._itemEvents, + hitItems = itemEvents.native[type], + nativeMove = type === 'mousemove', + tool = this._scope.tool, + view = this; + + function responds(type) { + return itemEvents.virtual[type] || view.responds(type) + || tool && tool.responds(type); + } + + if (nativeMove && dragging && responds('mousedrag')) + type = 'mousedrag'; + if (!point) + point = this.getEventPoint(event); + + var inView = this.getBounds().contains(point), + hit = hitItems && inView && view._project.hitTest(point, { + tolerance: 0, + fill: true, + stroke: true + }), + hitItem = hit && hit.item || null, + handle = false, + mouse = {}; + mouse[type.substr(5)] = true; + + if (hitItems && hitItem !== overItem) { + if (overItem) { + emitMouseEvent(overItem, null, 'mouseleave', event, point); + } + if (hitItem) { + emitMouseEvent(hitItem, null, 'mouseenter', event, point); + } + overItem = hitItem; + } + if (wasInView ^ inView) { + emitMouseEvent(this, null, inView ? 'mouseenter' : 'mouseleave', + event, point); + overView = inView ? this : null; + handle = true; + } + if ((inView || mouse.drag) && !point.equals(lastPoint)) { + emitMouseEvents(this, hitItem, nativeMove ? type : 'mousemove', + event, point, lastPoint); + handle = true; + } + wasInView = inView; + if (mouse.down && inView || mouse.up && downPoint) { + emitMouseEvents(this, hitItem, type, event, point, downPoint); + if (mouse.down) { + dblClick = hitItem === clickItem + && (Date.now() - clickTime < 300); + downItem = clickItem = hitItem; + if (!prevented && hitItem) { + var item = hitItem; + while (item && !item.responds('mousedrag')) + item = item._parent; + if (item) + dragItem = hitItem; + } + downPoint = point; + } else if (mouse.up) { + if (!prevented && hitItem === downItem) { + clickTime = Date.now(); + emitMouseEvents(this, hitItem, dblClick ? 'doubleclick' + : 'click', event, point, downPoint); + dblClick = false; + } + downItem = dragItem = null; + } + wasInView = false; + handle = true; + } + lastPoint = point; + if (handle && tool) { + called = tool._handleMouseEvent(type, event, point, mouse) + || called; + } + + if ( + event.cancelable !== false + && (called && !mouse.move || mouse.down && responds('mouseup')) + ) { + event.preventDefault(); + } + }, + + _handleKeyEvent: function(type, event, key, character) { + var scope = this._scope, + tool = scope.tool, + keyEvent; + + function emit(obj) { + if (obj.responds(type)) { + paper = scope; + obj.emit(type, keyEvent = keyEvent + || new KeyEvent(type, event, key, character)); + } + } + + if (this.isVisible()) { + emit(this); + if (tool && tool.responds(type)) + emit(tool); + } + }, + + _countItemEvent: function(type, sign) { + var itemEvents = this._itemEvents, + native = itemEvents.native, + virtual = itemEvents.virtual; + for (var key in itemEventsMap) { + native[key] = (native[key] || 0) + + (itemEventsMap[key][type] || 0) * sign; + } + virtual[type] = (virtual[type] || 0) + sign; + }, + + statics: { + updateFocus: updateFocus, + + _resetState: function() { + dragging = mouseDown = called = wasInView = false; + prevFocus = tempFocus = overView = downPoint = lastPoint = + downItem = overItem = dragItem = clickItem = clickTime = + dblClick = null; + } + } + }; +}); + +var CanvasView = View.extend({ + _class: 'CanvasView', + + initialize: function CanvasView(project, canvas) { + if (!(canvas instanceof window.HTMLCanvasElement)) { + var size = Size.read(arguments, 1); + if (size.isZero()) + throw new Error( + 'Cannot create CanvasView with the provided argument: ' + + Base.slice(arguments, 1)); + canvas = CanvasProvider.getCanvas(size); + } + var ctx = this._context = canvas.getContext('2d'); + ctx.save(); + this._pixelRatio = 1; + if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) { + var deviceRatio = window.devicePixelRatio || 1, + backingStoreRatio = DomElement.getPrefixed(ctx, + 'backingStorePixelRatio') || 1; + this._pixelRatio = deviceRatio / backingStoreRatio; + } + View.call(this, project, canvas); + this._needsUpdate = true; + }, + + remove: function remove() { + this._context.restore(); + return remove.base.call(this); + }, + + _setElementSize: function _setElementSize(width, height) { + var pixelRatio = this._pixelRatio; + _setElementSize.base.call(this, width * pixelRatio, height * pixelRatio); + if (pixelRatio !== 1) { + var element = this._element, + ctx = this._context; + if (!PaperScope.hasAttribute(element, 'resize')) { + var style = element.style; + style.width = width + 'px'; + style.height = height + 'px'; + } + ctx.restore(); + ctx.save(); + ctx.scale(pixelRatio, pixelRatio); + } + }, + + getContext: function() { + return this._context; + }, + + getPixelSize: function getPixelSize(size) { + var agent = paper.agent, + pixels; + if (agent && agent.firefox) { + pixels = getPixelSize.base.call(this, size); + } else { + var ctx = this._context, + prevFont = ctx.font; + ctx.font = size + ' serif'; + pixels = parseFloat(ctx.font); + ctx.font = prevFont; + } + return pixels; + }, + + getTextWidth: function(font, lines) { + var ctx = this._context, + prevFont = ctx.font, + width = 0; + ctx.font = font; + for (var i = 0, l = lines.length; i < l; i++) + width = Math.max(width, ctx.measureText(lines[i]).width); + ctx.font = prevFont; + return width; + }, + + update: function() { + if (!this._needsUpdate) + return false; + var project = this._project, + ctx = this._context, + size = this._viewSize; + ctx.clearRect(0, 0, size.width + 1, size.height + 1); + if (project) + project.draw(ctx, this._matrix, this._pixelRatio); + this._needsUpdate = false; + return true; + } +}); + +var Event = Base.extend({ + _class: 'Event', + + initialize: function Event(event) { + this.event = event; + this.type = event && event.type; + }, + + prevented: false, + stopped: false, + + preventDefault: function() { + this.prevented = true; + this.event.preventDefault(); + }, + + stopPropagation: function() { + this.stopped = true; + this.event.stopPropagation(); + }, + + stop: function() { + this.stopPropagation(); + this.preventDefault(); + }, + + getTimeStamp: function() { + return this.event.timeStamp; + }, + + getModifiers: function() { + return Key.modifiers; + } +}); + +var KeyEvent = Event.extend({ + _class: 'KeyEvent', + + initialize: function KeyEvent(type, event, key, character) { + this.type = type; + this.event = event; + this.key = key; + this.character = character; + }, + + toString: function() { + return "{ type: '" + this.type + + "', key: '" + this.key + + "', character: '" + this.character + + "', modifiers: " + this.getModifiers() + + " }"; + } +}); + +var Key = new function() { + var keyLookup = { + '\t': 'tab', + ' ': 'space', + '\b': 'backspace', + '\x7f': 'delete', + 'Spacebar': 'space', + 'Del': 'delete', + 'Win': 'meta', + 'Esc': 'escape' + }, + + charLookup = { + 'tab': '\t', + 'space': ' ', + 'enter': '\r' + }, + + keyMap = {}, + charMap = {}, + metaFixMap, + downKey, + + modifiers = new Base({ + shift: false, + control: false, + alt: false, + meta: false, + capsLock: false, + space: false + }).inject({ + option: { + get: function() { + return this.alt; + } + }, + + command: { + get: function() { + var agent = paper && paper.agent; + return agent && agent.mac ? this.meta : this.control; + } + } + }); + + function getKey(event) { + var key = event.key || event.keyIdentifier; + key = /^U\+/.test(key) + ? String.fromCharCode(parseInt(key.substr(2), 16)) + : /^Arrow[A-Z]/.test(key) ? key.substr(5) + : key === 'Unidentified' || key === undefined + ? String.fromCharCode(event.keyCode) + : key; + return keyLookup[key] || + (key.length > 1 ? Base.hyphenate(key) : key.toLowerCase()); + } + + function handleKey(down, key, character, event) { + var type = down ? 'keydown' : 'keyup', + view = View._focused, + name; + keyMap[key] = down; + if (down) { + charMap[key] = character; + } else { + delete charMap[key]; + } + if (key.length > 1 && (name = Base.camelize(key)) in modifiers) { + modifiers[name] = down; + var agent = paper && paper.agent; + if (name === 'meta' && agent && agent.mac) { + if (down) { + metaFixMap = {}; + } else { + for (var k in metaFixMap) { + if (k in charMap) + handleKey(false, k, metaFixMap[k], event); + } + metaFixMap = null; + } + } + } else if (down && metaFixMap) { + metaFixMap[key] = character; + } + if (view) { + view._handleKeyEvent(down ? 'keydown' : 'keyup', event, key, + character); + } + } + + DomEvent.add(document, { + keydown: function(event) { + var key = getKey(event), + agent = paper && paper.agent; + if (key.length > 1 || agent && (agent.chrome && (event.altKey + || agent.mac && event.metaKey + || !agent.mac && event.ctrlKey))) { + handleKey(true, key, + charLookup[key] || (key.length > 1 ? '' : key), event); + } else { + downKey = key; + } + }, + + keypress: function(event) { + if (downKey) { + var key = getKey(event), + code = event.charCode, + character = code >= 32 ? String.fromCharCode(code) + : key.length > 1 ? '' : key; + if (key !== downKey) { + key = character.toLowerCase(); + } + handleKey(true, key, character, event); + downKey = null; + } + }, + + keyup: function(event) { + var key = getKey(event); + if (key in charMap) + handleKey(false, key, charMap[key], event); + } + }); + + DomEvent.add(window, { + blur: function(event) { + for (var key in charMap) + handleKey(false, key, charMap[key], event); + } + }); + + return { + modifiers: modifiers, + + isDown: function(key) { + return !!keyMap[key]; + } + }; +}; + +var MouseEvent = Event.extend({ + _class: 'MouseEvent', + + initialize: function MouseEvent(type, event, point, target, delta) { + this.type = type; + this.event = event; + this.point = point; + this.target = target; + this.delta = delta; + }, + + toString: function() { + return "{ type: '" + this.type + + "', point: " + this.point + + ', target: ' + this.target + + (this.delta ? ', delta: ' + this.delta : '') + + ', modifiers: ' + this.getModifiers() + + ' }'; + } +}); + +var ToolEvent = Event.extend({ + _class: 'ToolEvent', + _item: null, + + initialize: function ToolEvent(tool, type, event) { + this.tool = tool; + this.type = type; + this.event = event; + }, + + _choosePoint: function(point, toolPoint) { + return point ? point : toolPoint ? toolPoint.clone() : null; + }, + + getPoint: function() { + return this._choosePoint(this._point, this.tool._point); + }, + + setPoint: function(point) { + this._point = point; + }, + + getLastPoint: function() { + return this._choosePoint(this._lastPoint, this.tool._lastPoint); + }, + + setLastPoint: function(lastPoint) { + this._lastPoint = lastPoint; + }, + + getDownPoint: function() { + return this._choosePoint(this._downPoint, this.tool._downPoint); + }, + + setDownPoint: function(downPoint) { + this._downPoint = downPoint; + }, + + getMiddlePoint: function() { + if (!this._middlePoint && this.tool._lastPoint) { + return this.tool._point.add(this.tool._lastPoint).divide(2); + } + return this._middlePoint; + }, + + setMiddlePoint: function(middlePoint) { + this._middlePoint = middlePoint; + }, + + getDelta: function() { + return !this._delta && this.tool._lastPoint + ? this.tool._point.subtract(this.tool._lastPoint) + : this._delta; + }, + + setDelta: function(delta) { + this._delta = delta; + }, + + getCount: function() { + return this.tool[/^mouse(down|up)$/.test(this.type) + ? '_downCount' : '_moveCount']; + }, + + setCount: function(count) { + this.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count'] + = count; + }, + + getItem: function() { + if (!this._item) { + var result = this.tool._scope.project.hitTest(this.getPoint()); + if (result) { + var item = result.item, + parent = item._parent; + while (/^(Group|CompoundPath)$/.test(parent._class)) { + item = parent; + parent = parent._parent; + } + this._item = item; + } + } + return this._item; + }, + + setItem: function(item) { + this._item = item; + }, + + toString: function() { + return '{ type: ' + this.type + + ', point: ' + this.getPoint() + + ', count: ' + this.getCount() + + ', modifiers: ' + this.getModifiers() + + ' }'; + } +}); + +var Tool = PaperScopeItem.extend({ + _class: 'Tool', + _list: 'tools', + _reference: 'tool', + _events: ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove', + 'onActivate', 'onDeactivate', 'onEditOptions', 'onKeyDown', + 'onKeyUp'], + + initialize: function Tool(props) { + PaperScopeItem.call(this); + this._moveCount = -1; + this._downCount = -1; + this.set(props); + }, + + getMinDistance: function() { + return this._minDistance; + }, + + setMinDistance: function(minDistance) { + this._minDistance = minDistance; + if (minDistance != null && this._maxDistance != null + && minDistance > this._maxDistance) { + this._maxDistance = minDistance; + } + }, + + getMaxDistance: function() { + return this._maxDistance; + }, + + setMaxDistance: function(maxDistance) { + this._maxDistance = maxDistance; + if (this._minDistance != null && maxDistance != null + && maxDistance < this._minDistance) { + this._minDistance = maxDistance; + } + }, + + getFixedDistance: function() { + return this._minDistance == this._maxDistance + ? this._minDistance : null; + }, + + setFixedDistance: function(distance) { + this._minDistance = this._maxDistance = distance; + }, + + _handleMouseEvent: function(type, event, point, mouse) { + paper = this._scope; + if (mouse.drag && !this.responds(type)) + type = 'mousemove'; + var move = mouse.move || mouse.drag, + responds = this.responds(type), + minDistance = this.minDistance, + maxDistance = this.maxDistance, + called = false, + tool = this; + function update(minDistance, maxDistance) { + var pt = point, + toolPoint = move ? tool._point : (tool._downPoint || pt); + if (move) { + if (tool._moveCount >= 0 && pt.equals(toolPoint)) { + return false; + } + if (toolPoint && (minDistance != null || maxDistance != null)) { + var vector = pt.subtract(toolPoint), + distance = vector.getLength(); + if (distance < (minDistance || 0)) + return false; + if (maxDistance) { + pt = toolPoint.add(vector.normalize( + Math.min(distance, maxDistance))); + } + } + tool._moveCount++; + } + tool._point = pt; + tool._lastPoint = toolPoint || pt; + if (mouse.down) { + tool._moveCount = -1; + tool._downPoint = pt; + tool._downCount++; + } + return true; + } + + function emit() { + if (responds) { + called = tool.emit(type, new ToolEvent(tool, type, event)) + || called; + } + } + + if (mouse.down) { + update(); + emit(); + } else if (mouse.up) { + update(null, maxDistance); + emit(); + } else if (responds) { + while (update(minDistance, maxDistance)) + emit(); + } + return called; + } + +}); + +var Tween = Base.extend(Emitter, { + _class: 'Tween', + + statics: { + easings: new Base({ + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return t * (2 - t); + }, + + easeInOutQuad: function(t) { + return t < 0.5 + ? 2 * t * t + : -1 + 2 * (2 - t) * t; + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return --t * t * t + 1; + }, + + easeInOutCubic: function(t) { + return t < 0.5 + ? 4 * t * t * t + : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return 1 - (--t) * t * t * t; + }, + + easeInOutQuart: function(t) { + return t < 0.5 + ? 8 * t * t * t * t + : 1 - 8 * (--t) * t * t * t; + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return 1 + --t * t * t * t * t; + }, + + easeInOutQuint: function(t) { + return t < 0.5 + ? 16 * t * t * t * t * t + : 1 + 16 * (--t) * t * t * t * t; + } + }) + }, + + initialize: function Tween(object, from, to, duration, easing, start) { + this.object = object; + var type = typeof easing; + var isFunction = type === 'function'; + this.type = isFunction + ? type + : type === 'string' + ? easing + : 'linear'; + this.easing = isFunction ? easing : Tween.easings[this.type]; + this.duration = duration; + this.running = false; + + this._then = null; + this._startTime = null; + var state = from || to; + this._keys = state ? Object.keys(state) : []; + this._parsedKeys = this._parseKeys(this._keys); + this._from = state && this._getState(from); + this._to = state && this._getState(to); + if (start !== false) { + this.start(); + } + }, + + then: function(then) { + this._then = then; + return this; + }, + + start: function() { + this._startTime = null; + this.running = true; + return this; + }, + + stop: function() { + this.running = false; + return this; + }, + + update: function(progress) { + if (this.running) { + if (progress >= 1) { + progress = 1; + this.running = false; + } + + var factor = this.easing(progress), + keys = this._keys, + getValue = function(value) { + return typeof value === 'function' + ? value(factor, progress) + : value; + }; + for (var i = 0, l = keys && keys.length; i < l; i++) { + var key = keys[i], + from = getValue(this._from[key]), + to = getValue(this._to[key]), + value = (from && to && from.__add && to.__add) + ? to.__subtract(from).__multiply(factor).__add(from) + : ((to - from) * factor) + from; + this._setProperty(this._parsedKeys[key], value); + } + + if (this.responds('update')) { + this.emit('update', new Base({ + progress: progress, + factor: factor + })); + } + if (!this.running && this._then) { + this._then(this.object); + } + } + return this; + }, + + _events: { + onUpdate: {} + }, + + _handleFrame: function(time) { + var startTime = this._startTime, + progress = startTime + ? (time - startTime) / this.duration + : 0; + if (!startTime) { + this._startTime = time; + } + this.update(progress); + }, + + _getState: function(state) { + var keys = this._keys, + result = {}; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i], + path = this._parsedKeys[key], + current = this._getProperty(path), + value; + if (state) { + var resolved = this._resolveValue(current, state[key]); + this._setProperty(path, resolved); + value = this._getProperty(path); + value = value && value.clone ? value.clone() : value; + this._setProperty(path, current); + } else { + value = current && current.clone ? current.clone() : current; + } + result[key] = value; + } + return result; + }, + + _resolveValue: function(current, value) { + if (value) { + if (Array.isArray(value) && value.length === 2) { + var operator = value[0]; + return ( + operator && + operator.match && + operator.match(/^[+\-\*\/]=/) + ) + ? this._calculate(current, operator[0], value[1]) + : value; + } else if (typeof value === 'string') { + var match = value.match(/^[+\-*/]=(.*)/); + if (match) { + var parsed = JSON.parse(match[1].replace( + /(['"])?([a-zA-Z0-9_]+)(['"])?:/g, + '"$2": ' + )); + return this._calculate(current, value[0], parsed); + } + } + } + return value; + }, + + _calculate: function(left, operator, right) { + return paper.PaperScript.calculateBinary(left, operator, right); + }, + + _parseKeys: function(keys) { + var parsed = {}; + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i], + path = key + .replace(/\.([^.]*)/g, '/$1') + .replace(/\[['"]?([^'"\]]*)['"]?\]/g, '/$1'); + parsed[key] = path.split('/'); + } + return parsed; + }, + + _getProperty: function(path, offset) { + var obj = this.object; + for (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) { + obj = obj[path[i]]; + } + return obj; + }, + + _setProperty: function(path, value) { + var dest = this._getProperty(path, 1); + if (dest) { + dest[path[path.length - 1]] = value; + } + } +}); + +var Http = { + request: function(options) { + var xhr = new self.XMLHttpRequest(); + xhr.open((options.method || 'get').toUpperCase(), options.url, + Base.pick(options.async, true)); + if (options.mimeType) + xhr.overrideMimeType(options.mimeType); + xhr.onload = function() { + var status = xhr.status; + if (status === 0 || status === 200) { + if (options.onLoad) { + options.onLoad.call(xhr, xhr.responseText); + } + } else { + xhr.onerror(); + } + }; + xhr.onerror = function() { + var status = xhr.status, + message = 'Could not load "' + options.url + '" (Status: ' + + status + ')'; + if (options.onError) { + options.onError(message, status); + } else { + throw new Error(message); + } + }; + return xhr.send(null); + } +}; + +var CanvasProvider = Base.exports.CanvasProvider = { + canvases: [], + + getCanvas: function(width, height) { + if (!window) + return null; + var canvas, + clear = true; + if (typeof width === 'object') { + height = width.height; + width = width.width; + } + if (this.canvases.length) { + canvas = this.canvases.pop(); + } else { + canvas = document.createElement('canvas'); + clear = false; + } + var ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('Canvas ' + canvas + + ' is unable to provide a 2D context.'); + } + if (canvas.width === width && canvas.height === height) { + if (clear) + ctx.clearRect(0, 0, width + 1, height + 1); + } else { + canvas.width = width; + canvas.height = height; + } + ctx.save(); + return canvas; + }, + + getContext: function(width, height) { + var canvas = this.getCanvas(width, height); + return canvas ? canvas.getContext('2d') : null; + }, + + release: function(obj) { + var canvas = obj && obj.canvas ? obj.canvas : obj; + if (canvas && canvas.getContext) { + canvas.getContext('2d').restore(); + this.canvases.push(canvas); + } + } +}; + +var BlendMode = new function() { + var min = Math.min, + max = Math.max, + abs = Math.abs, + sr, sg, sb, sa, + br, bg, bb, ba, + dr, dg, db; + + function getLum(r, g, b) { + return 0.2989 * r + 0.587 * g + 0.114 * b; + } + + function setLum(r, g, b, l) { + var d = l - getLum(r, g, b); + dr = r + d; + dg = g + d; + db = b + d; + var l = getLum(dr, dg, db), + mn = min(dr, dg, db), + mx = max(dr, dg, db); + if (mn < 0) { + var lmn = l - mn; + dr = l + (dr - l) * l / lmn; + dg = l + (dg - l) * l / lmn; + db = l + (db - l) * l / lmn; + } + if (mx > 255) { + var ln = 255 - l, + mxl = mx - l; + dr = l + (dr - l) * ln / mxl; + dg = l + (dg - l) * ln / mxl; + db = l + (db - l) * ln / mxl; + } + } + + function getSat(r, g, b) { + return max(r, g, b) - min(r, g, b); + } + + function setSat(r, g, b, s) { + var col = [r, g, b], + mx = max(r, g, b), + mn = min(r, g, b), + md; + mn = mn === r ? 0 : mn === g ? 1 : 2; + mx = mx === r ? 0 : mx === g ? 1 : 2; + md = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0; + if (col[mx] > col[mn]) { + col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]); + col[mx] = s; + } else { + col[md] = col[mx] = 0; + } + col[mn] = 0; + dr = col[0]; + dg = col[1]; + db = col[2]; + } + + var modes = { + multiply: function() { + dr = br * sr / 255; + dg = bg * sg / 255; + db = bb * sb / 255; + }, + + screen: function() { + dr = br + sr - (br * sr / 255); + dg = bg + sg - (bg * sg / 255); + db = bb + sb - (bb * sb / 255); + }, + + overlay: function() { + dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255; + dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255; + db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255; + }, + + 'soft-light': function() { + var t = sr * br / 255; + dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255; + t = sg * bg / 255; + dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255; + t = sb * bb / 255; + db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255; + }, + + 'hard-light': function() { + dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255; + dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255; + db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255; + }, + + 'color-dodge': function() { + dr = br === 0 ? 0 : sr === 255 ? 255 : min(255, 255 * br / (255 - sr)); + dg = bg === 0 ? 0 : sg === 255 ? 255 : min(255, 255 * bg / (255 - sg)); + db = bb === 0 ? 0 : sb === 255 ? 255 : min(255, 255 * bb / (255 - sb)); + }, + + 'color-burn': function() { + dr = br === 255 ? 255 : sr === 0 ? 0 : max(0, 255 - (255 - br) * 255 / sr); + dg = bg === 255 ? 255 : sg === 0 ? 0 : max(0, 255 - (255 - bg) * 255 / sg); + db = bb === 255 ? 255 : sb === 0 ? 0 : max(0, 255 - (255 - bb) * 255 / sb); + }, + + darken: function() { + dr = br < sr ? br : sr; + dg = bg < sg ? bg : sg; + db = bb < sb ? bb : sb; + }, + + lighten: function() { + dr = br > sr ? br : sr; + dg = bg > sg ? bg : sg; + db = bb > sb ? bb : sb; + }, + + difference: function() { + dr = br - sr; + if (dr < 0) + dr = -dr; + dg = bg - sg; + if (dg < 0) + dg = -dg; + db = bb - sb; + if (db < 0) + db = -db; + }, + + exclusion: function() { + dr = br + sr * (255 - br - br) / 255; + dg = bg + sg * (255 - bg - bg) / 255; + db = bb + sb * (255 - bb - bb) / 255; + }, + + hue: function() { + setSat(sr, sg, sb, getSat(br, bg, bb)); + setLum(dr, dg, db, getLum(br, bg, bb)); + }, + + saturation: function() { + setSat(br, bg, bb, getSat(sr, sg, sb)); + setLum(dr, dg, db, getLum(br, bg, bb)); + }, + + luminosity: function() { + setLum(br, bg, bb, getLum(sr, sg, sb)); + }, + + color: function() { + setLum(sr, sg, sb, getLum(br, bg, bb)); + }, + + add: function() { + dr = min(br + sr, 255); + dg = min(bg + sg, 255); + db = min(bb + sb, 255); + }, + + subtract: function() { + dr = max(br - sr, 0); + dg = max(bg - sg, 0); + db = max(bb - sb, 0); + }, + + average: function() { + dr = (br + sr) / 2; + dg = (bg + sg) / 2; + db = (bb + sb) / 2; + }, + + negation: function() { + dr = 255 - abs(255 - sr - br); + dg = 255 - abs(255 - sg - bg); + db = 255 - abs(255 - sb - bb); + } + }; + + var nativeModes = this.nativeModes = Base.each([ + 'source-over', 'source-in', 'source-out', 'source-atop', + 'destination-over', 'destination-in', 'destination-out', + 'destination-atop', 'lighter', 'darker', 'copy', 'xor' + ], function(mode) { + this[mode] = true; + }, {}); + + var ctx = CanvasProvider.getContext(1, 1); + if (ctx) { + Base.each(modes, function(func, mode) { + var darken = mode === 'darken', + ok = false; + ctx.save(); + try { + ctx.fillStyle = darken ? '#300' : '#a00'; + ctx.fillRect(0, 0, 1, 1); + ctx.globalCompositeOperation = mode; + if (ctx.globalCompositeOperation === mode) { + ctx.fillStyle = darken ? '#a00' : '#300'; + ctx.fillRect(0, 0, 1, 1); + ok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken + ? 170 : 51; + } + } catch (e) {} + ctx.restore(); + nativeModes[mode] = ok; + }); + CanvasProvider.release(ctx); + } + + this.process = function(mode, srcContext, dstContext, alpha, offset) { + var srcCanvas = srcContext.canvas, + normal = mode === 'normal'; + if (normal || nativeModes[mode]) { + dstContext.save(); + dstContext.setTransform(1, 0, 0, 1, 0, 0); + dstContext.globalAlpha = alpha; + if (!normal) + dstContext.globalCompositeOperation = mode; + dstContext.drawImage(srcCanvas, offset.x, offset.y); + dstContext.restore(); + } else { + var process = modes[mode]; + if (!process) + return; + var dstData = dstContext.getImageData(offset.x, offset.y, + srcCanvas.width, srcCanvas.height), + dst = dstData.data, + src = srcContext.getImageData(0, 0, + srcCanvas.width, srcCanvas.height).data; + for (var i = 0, l = dst.length; i < l; i += 4) { + sr = src[i]; + br = dst[i]; + sg = src[i + 1]; + bg = dst[i + 1]; + sb = src[i + 2]; + bb = dst[i + 2]; + sa = src[i + 3]; + ba = dst[i + 3]; + process(); + var a1 = sa * alpha / 255, + a2 = 1 - a1; + dst[i] = a1 * dr + a2 * br; + dst[i + 1] = a1 * dg + a2 * bg; + dst[i + 2] = a1 * db + a2 * bb; + dst[i + 3] = sa * alpha + a2 * ba; + } + dstContext.putImageData(dstData, offset.x, offset.y); + } + }; +}; + +var SvgElement = new function() { + var svg = 'http://www.w3.org/2000/svg', + xmlns = 'http://www.w3.org/2000/xmlns', + xlink = 'http://www.w3.org/1999/xlink', + attributeNamespace = { + href: xlink, + xlink: xmlns, + xmlns: xmlns + '/', + 'xmlns:xlink': xmlns + '/' + }; + + function create(tag, attributes, formatter) { + return set(document.createElementNS(svg, tag), attributes, formatter); + } + + function get(node, name) { + var namespace = attributeNamespace[name], + value = namespace + ? node.getAttributeNS(namespace, name) + : node.getAttribute(name); + return value === 'null' ? null : value; + } + + function set(node, attributes, formatter) { + for (var name in attributes) { + var value = attributes[name], + namespace = attributeNamespace[name]; + if (typeof value === 'number' && formatter) + value = formatter.number(value); + if (namespace) { + node.setAttributeNS(namespace, name, value); + } else { + node.setAttribute(name, value); + } + } + return node; + } + + return { + svg: svg, + xmlns: xmlns, + xlink: xlink, + + create: create, + get: get, + set: set + }; +}; + +var SvgStyles = Base.each({ + fillColor: ['fill', 'color'], + fillRule: ['fill-rule', 'string'], + strokeColor: ['stroke', 'color'], + strokeWidth: ['stroke-width', 'number'], + strokeCap: ['stroke-linecap', 'string'], + strokeJoin: ['stroke-linejoin', 'string'], + strokeScaling: ['vector-effect', 'lookup', { + true: 'none', + false: 'non-scaling-stroke' + }, function(item, value) { + return !value + && (item instanceof PathItem + || item instanceof Shape + || item instanceof TextItem); + }], + miterLimit: ['stroke-miterlimit', 'number'], + dashArray: ['stroke-dasharray', 'array'], + dashOffset: ['stroke-dashoffset', 'number'], + fontFamily: ['font-family', 'string'], + fontWeight: ['font-weight', 'string'], + fontSize: ['font-size', 'number'], + justification: ['text-anchor', 'lookup', { + left: 'start', + center: 'middle', + right: 'end' + }], + opacity: ['opacity', 'number'], + blendMode: ['mix-blend-mode', 'style'] +}, function(entry, key) { + var part = Base.capitalize(key), + lookup = entry[2]; + this[key] = { + type: entry[1], + property: key, + attribute: entry[0], + toSVG: lookup, + fromSVG: lookup && Base.each(lookup, function(value, name) { + this[value] = name; + }, {}), + exportFilter: entry[3], + get: 'get' + part, + set: 'set' + part + }; +}, {}); + +new function() { + var formatter; + + function getTransform(matrix, coordinates, center) { + var attrs = new Base(), + trans = matrix.getTranslation(); + if (coordinates) { + var point; + if (matrix.isInvertible()) { + matrix = matrix._shiftless(); + point = matrix._inverseTransform(trans); + trans = null; + } else { + point = new Point(); + } + attrs[center ? 'cx' : 'x'] = point.x; + attrs[center ? 'cy' : 'y'] = point.y; + } + if (!matrix.isIdentity()) { + var decomposed = matrix.decompose(); + if (decomposed) { + var parts = [], + angle = decomposed.rotation, + scale = decomposed.scaling, + skew = decomposed.skewing; + if (trans && !trans.isZero()) + parts.push('translate(' + formatter.point(trans) + ')'); + if (angle) + parts.push('rotate(' + formatter.number(angle) + ')'); + if (!Numerical.isZero(scale.x - 1) + || !Numerical.isZero(scale.y - 1)) + parts.push('scale(' + formatter.point(scale) +')'); + if (skew.x) + parts.push('skewX(' + formatter.number(skew.x) + ')'); + if (skew.y) + parts.push('skewY(' + formatter.number(skew.y) + ')'); + attrs.transform = parts.join(' '); + } else { + attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')'; + } + } + return attrs; + } + + function exportGroup(item, options) { + var attrs = getTransform(item._matrix), + children = item._children; + var node = SvgElement.create('g', attrs, formatter); + for (var i = 0, l = children.length; i < l; i++) { + var child = children[i]; + var childNode = exportSVG(child, options); + if (childNode) { + if (child.isClipMask()) { + var clip = SvgElement.create('clipPath'); + clip.appendChild(childNode); + setDefinition(child, clip, 'clip'); + SvgElement.set(node, { + 'clip-path': 'url(#' + clip.id + ')' + }); + } else { + node.appendChild(childNode); + } + } + } + return node; + } + + function exportRaster(item, options) { + var attrs = getTransform(item._matrix, true), + size = item.getSize(), + image = item.getImage(); + attrs.x -= size.width / 2; + attrs.y -= size.height / 2; + attrs.width = size.width; + attrs.height = size.height; + attrs.href = options.embedImages == false && image && image.src + || item.toDataURL(); + return SvgElement.create('image', attrs, formatter); + } + + function exportPath(item, options) { + var matchShapes = options.matchShapes; + if (matchShapes) { + var shape = item.toShape(false); + if (shape) + return exportShape(shape, options); + } + var segments = item._segments, + length = segments.length, + type, + attrs = getTransform(item._matrix); + if (matchShapes && length >= 2 && !item.hasHandles()) { + if (length > 2) { + type = item._closed ? 'polygon' : 'polyline'; + var parts = []; + for (var i = 0; i < length; i++) { + parts.push(formatter.point(segments[i]._point)); + } + attrs.points = parts.join(' '); + } else { + type = 'line'; + var start = segments[0]._point, + end = segments[1]._point; + attrs.set({ + x1: start.x, + y1: start.y, + x2: end.x, + y2: end.y + }); + } + } else { + type = 'path'; + attrs.d = item.getPathData(null, options.precision); + } + return SvgElement.create(type, attrs, formatter); + } + + function exportShape(item) { + var type = item._type, + radius = item._radius, + attrs = getTransform(item._matrix, true, type !== 'rectangle'); + if (type === 'rectangle') { + type = 'rect'; + var size = item._size, + width = size.width, + height = size.height; + attrs.x -= width / 2; + attrs.y -= height / 2; + attrs.width = width; + attrs.height = height; + if (radius.isZero()) + radius = null; + } + if (radius) { + if (type === 'circle') { + attrs.r = radius; + } else { + attrs.rx = radius.width; + attrs.ry = radius.height; + } + } + return SvgElement.create(type, attrs, formatter); + } + + function exportCompoundPath(item, options) { + var attrs = getTransform(item._matrix); + var data = item.getPathData(null, options.precision); + if (data) + attrs.d = data; + return SvgElement.create('path', attrs, formatter); + } + + function exportSymbolItem(item, options) { + var attrs = getTransform(item._matrix, true), + definition = item._definition, + node = getDefinition(definition, 'symbol'), + definitionItem = definition._item, + bounds = definitionItem.getStrokeBounds(); + if (!node) { + node = SvgElement.create('symbol', { + viewBox: formatter.rectangle(bounds) + }); + node.appendChild(exportSVG(definitionItem, options)); + setDefinition(definition, node, 'symbol'); + } + attrs.href = '#' + node.id; + attrs.x += bounds.x; + attrs.y += bounds.y; + attrs.width = bounds.width; + attrs.height = bounds.height; + attrs.overflow = 'visible'; + return SvgElement.create('use', attrs, formatter); + } + + function exportGradient(color) { + var gradientNode = getDefinition(color, 'color'); + if (!gradientNode) { + var gradient = color.getGradient(), + radial = gradient._radial, + origin = color.getOrigin(), + destination = color.getDestination(), + attrs; + if (radial) { + attrs = { + cx: origin.x, + cy: origin.y, + r: origin.getDistance(destination) + }; + var highlight = color.getHighlight(); + if (highlight) { + attrs.fx = highlight.x; + attrs.fy = highlight.y; + } + } else { + attrs = { + x1: origin.x, + y1: origin.y, + x2: destination.x, + y2: destination.y + }; + } + attrs.gradientUnits = 'userSpaceOnUse'; + gradientNode = SvgElement.create((radial ? 'radial' : 'linear') + + 'Gradient', attrs, formatter); + var stops = gradient._stops; + for (var i = 0, l = stops.length; i < l; i++) { + var stop = stops[i], + stopColor = stop._color, + alpha = stopColor.getAlpha(), + offset = stop._offset; + attrs = { + offset: offset == null ? i / (l - 1) : offset + }; + if (stopColor) + attrs['stop-color'] = stopColor.toCSS(true); + if (alpha < 1) + attrs['stop-opacity'] = alpha; + gradientNode.appendChild( + SvgElement.create('stop', attrs, formatter)); + } + setDefinition(color, gradientNode, 'color'); + } + return 'url(#' + gradientNode.id + ')'; + } + + function exportText(item) { + var node = SvgElement.create('text', getTransform(item._matrix, true), + formatter); + node.textContent = item._content; + return node; + } + + var exporters = { + Group: exportGroup, + Layer: exportGroup, + Raster: exportRaster, + Path: exportPath, + Shape: exportShape, + CompoundPath: exportCompoundPath, + SymbolItem: exportSymbolItem, + PointText: exportText + }; + + function applyStyle(item, node, isRoot) { + var attrs = {}, + parent = !isRoot && item.getParent(), + style = []; + + if (item._name != null) + attrs.id = item._name; + + Base.each(SvgStyles, function(entry) { + var get = entry.get, + type = entry.type, + value = item[get](); + if (entry.exportFilter + ? entry.exportFilter(item, value) + : !parent || !Base.equals(parent[get](), value)) { + if (type === 'color' && value != null) { + var alpha = value.getAlpha(); + if (alpha < 1) + attrs[entry.attribute + '-opacity'] = alpha; + } + if (type === 'style') { + style.push(entry.attribute + ': ' + value); + } else { + attrs[entry.attribute] = value == null ? 'none' + : type === 'color' ? value.gradient + ? exportGradient(value, item) + : value.toCSS(true) + : type === 'array' ? value.join(',') + : type === 'lookup' ? entry.toSVG[value] + : value; + } + } + }); + + if (style.length) + attrs.style = style.join(';'); + + if (attrs.opacity === 1) + delete attrs.opacity; + + if (!item._visible) + attrs.visibility = 'hidden'; + + return SvgElement.set(node, attrs, formatter); + } + + var definitions; + function getDefinition(item, type) { + if (!definitions) + definitions = { ids: {}, svgs: {} }; + return item && definitions.svgs[type + '-' + + (item._id || item.__id || (item.__id = UID.get('svg')))]; + } + + function setDefinition(item, node, type) { + if (!definitions) + getDefinition(); + var typeId = definitions.ids[type] = (definitions.ids[type] || 0) + 1; + node.id = type + '-' + typeId; + definitions.svgs[type + '-' + (item._id || item.__id)] = node; + } + + function exportDefinitions(node, options) { + var svg = node, + defs = null; + if (definitions) { + svg = node.nodeName.toLowerCase() === 'svg' && node; + for (var i in definitions.svgs) { + if (!defs) { + if (!svg) { + svg = SvgElement.create('svg'); + svg.appendChild(node); + } + defs = svg.insertBefore(SvgElement.create('defs'), + svg.firstChild); + } + defs.appendChild(definitions.svgs[i]); + } + definitions = null; + } + return options.asString + ? new self.XMLSerializer().serializeToString(svg) + : svg; + } + + function exportSVG(item, options, isRoot) { + var exporter = exporters[item._class], + node = exporter && exporter(item, options); + if (node) { + var onExport = options.onExport; + if (onExport) + node = onExport(item, node, options) || node; + var data = JSON.stringify(item._data); + if (data && data !== '{}' && data !== 'null') + node.setAttribute('data-paper-data', data); + } + return node && applyStyle(item, node, isRoot); + } + + function setOptions(options) { + if (!options) + options = {}; + formatter = new Formatter(options.precision); + return options; + } + + Item.inject({ + exportSVG: function(options) { + options = setOptions(options); + return exportDefinitions(exportSVG(this, options, true), options); + } + }); + + Project.inject({ + exportSVG: function(options) { + options = setOptions(options); + var children = this._children, + view = this.getView(), + bounds = Base.pick(options.bounds, 'view'), + mx = options.matrix || bounds === 'view' && view._matrix, + matrix = mx && Matrix.read([mx]), + rect = bounds === 'view' + ? new Rectangle([0, 0], view.getViewSize()) + : bounds === 'content' + ? Item._getBounds(children, matrix, { stroke: true }) + .rect + : Rectangle.read([bounds], 0, { readNull: true }), + attrs = { + version: '1.1', + xmlns: SvgElement.svg, + 'xmlns:xlink': SvgElement.xlink, + }; + if (rect) { + attrs.width = rect.width; + attrs.height = rect.height; + if (rect.x || rect.x === 0 || rect.y || rect.y === 0) + attrs.viewBox = formatter.rectangle(rect); + } + var node = SvgElement.create('svg', attrs, formatter), + parent = node; + if (matrix && !matrix.isIdentity()) { + parent = node.appendChild(SvgElement.create('g', + getTransform(matrix), formatter)); + } + for (var i = 0, l = children.length; i < l; i++) { + parent.appendChild(exportSVG(children[i], options, true)); + } + return exportDefinitions(node, options); + } + }); +}; + +new function() { + + var definitions = {}, + rootSize; + + function getValue(node, name, isString, allowNull, allowPercent, + defaultValue) { + var value = SvgElement.get(node, name) || defaultValue, + res = value == null + ? allowNull + ? null + : isString ? '' : 0 + : isString + ? value + : parseFloat(value); + return /%\s*$/.test(value) + ? (res / 100) * (allowPercent ? 1 + : rootSize[/x|^width/.test(name) ? 'width' : 'height']) + : res; + } + + function getPoint(node, x, y, allowNull, allowPercent, defaultX, defaultY) { + x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX); + y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY); + return allowNull && (x == null || y == null) ? null + : new Point(x, y); + } + + function getSize(node, w, h, allowNull, allowPercent) { + w = getValue(node, w || 'width', false, allowNull, allowPercent); + h = getValue(node, h || 'height', false, allowNull, allowPercent); + return allowNull && (w == null || h == null) ? null + : new Size(w, h); + } + + function convertValue(value, type, lookup) { + return value === 'none' ? null + : type === 'number' ? parseFloat(value) + : type === 'array' ? + value ? value.split(/[\s,]+/g).map(parseFloat) : [] + : type === 'color' ? getDefinition(value) || value + : type === 'lookup' ? lookup[value] + : value; + } + + function importGroup(node, type, options, isRoot) { + var nodes = node.childNodes, + isClip = type === 'clippath', + isDefs = type === 'defs', + item = new Group(), + project = item._project, + currentStyle = project._currentStyle, + children = []; + if (!isClip && !isDefs) { + item = applyAttributes(item, node, isRoot); + project._currentStyle = item._style.clone(); + } + if (isRoot) { + var defs = node.querySelectorAll('defs'); + for (var i = 0, l = defs.length; i < l; i++) { + importNode(defs[i], options, false); + } + } + for (var i = 0, l = nodes.length; i < l; i++) { + var childNode = nodes[i], + child; + if (childNode.nodeType === 1 + && !/^defs$/i.test(childNode.nodeName) + && (child = importNode(childNode, options, false)) + && !(child instanceof SymbolDefinition)) + children.push(child); + } + item.addChildren(children); + if (isClip) + item = applyAttributes(item.reduce(), node, isRoot); + project._currentStyle = currentStyle; + if (isClip || isDefs) { + item.remove(); + item = null; + } + return item; + } + + function importPoly(node, type) { + var coords = node.getAttribute('points').match( + /[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g), + points = []; + for (var i = 0, l = coords.length; i < l; i += 2) + points.push(new Point( + parseFloat(coords[i]), + parseFloat(coords[i + 1]))); + var path = new Path(points); + if (type === 'polygon') + path.closePath(); + return path; + } + + function importPath(node) { + return PathItem.create(node.getAttribute('d')); + } + + function importGradient(node, type) { + var id = (getValue(node, 'href', true) || '').substring(1), + radial = type === 'radialgradient', + gradient; + if (id) { + gradient = definitions[id].getGradient(); + if (gradient._radial ^ radial) { + gradient = gradient.clone(); + gradient._radial = radial; + } + } else { + var nodes = node.childNodes, + stops = []; + for (var i = 0, l = nodes.length; i < l; i++) { + var child = nodes[i]; + if (child.nodeType === 1) + stops.push(applyAttributes(new GradientStop(), child)); + } + gradient = new Gradient(stops, radial); + } + var origin, destination, highlight, + scaleToBounds = getValue(node, 'gradientUnits', true) !== + 'userSpaceOnUse'; + if (radial) { + origin = getPoint(node, 'cx', 'cy', false, scaleToBounds, + '50%', '50%'); + destination = origin.add( + getValue(node, 'r', false, false, scaleToBounds, '50%'), 0); + highlight = getPoint(node, 'fx', 'fy', true, scaleToBounds); + } else { + origin = getPoint(node, 'x1', 'y1', false, scaleToBounds, + '0%', '0%'); + destination = getPoint(node, 'x2', 'y2', false, scaleToBounds, + '100%', '0%'); + } + var color = applyAttributes( + new Color(gradient, origin, destination, highlight), node); + color._scaleToBounds = scaleToBounds; + return null; + } + + var importers = { + '#document': function (node, type, options, isRoot) { + var nodes = node.childNodes; + for (var i = 0, l = nodes.length; i < l; i++) { + var child = nodes[i]; + if (child.nodeType === 1) + return importNode(child, options, isRoot); + } + }, + g: importGroup, + svg: importGroup, + clippath: importGroup, + polygon: importPoly, + polyline: importPoly, + path: importPath, + lineargradient: importGradient, + radialgradient: importGradient, + + image: function (node) { + var raster = new Raster(getValue(node, 'href', true)); + raster.on('load', function() { + var size = getSize(node); + this.setSize(size); + var center = getPoint(node).add(size.divide(2)); + this._matrix.append(new Matrix().translate(center)); + }); + return raster; + }, + + symbol: function(node, type, options, isRoot) { + return new SymbolDefinition( + importGroup(node, type, options, isRoot), true); + }, + + defs: importGroup, + + use: function(node) { + var id = (getValue(node, 'href', true) || '').substring(1), + definition = definitions[id], + point = getPoint(node); + return definition + ? definition instanceof SymbolDefinition + ? definition.place(point) + : definition.clone().translate(point) + : null; + }, + + circle: function(node) { + return new Shape.Circle( + getPoint(node, 'cx', 'cy'), + getValue(node, 'r')); + }, + + ellipse: function(node) { + return new Shape.Ellipse({ + center: getPoint(node, 'cx', 'cy'), + radius: getSize(node, 'rx', 'ry') + }); + }, + + rect: function(node) { + return new Shape.Rectangle(new Rectangle( + getPoint(node), + getSize(node) + ), getSize(node, 'rx', 'ry')); + }, + + line: function(node) { + return new Path.Line( + getPoint(node, 'x1', 'y1'), + getPoint(node, 'x2', 'y2')); + }, + + text: function(node) { + var text = new PointText(getPoint(node).add( + getPoint(node, 'dx', 'dy'))); + text.setContent(node.textContent.trim() || ''); + return text; + }, + + switch: importGroup + }; + + function applyTransform(item, value, name, node) { + if (item.transform) { + var transforms = (node.getAttribute(name) || '').split(/\)\s*/g), + matrix = new Matrix(); + for (var i = 0, l = transforms.length; i < l; i++) { + var transform = transforms[i]; + if (!transform) + break; + var parts = transform.split(/\(\s*/), + command = parts[0], + v = parts[1].split(/[\s,]+/g); + for (var j = 0, m = v.length; j < m; j++) + v[j] = parseFloat(v[j]); + switch (command) { + case 'matrix': + matrix.append( + new Matrix(v[0], v[1], v[2], v[3], v[4], v[5])); + break; + case 'rotate': + matrix.rotate(v[0], v[1] || 0, v[2] || 0); + break; + case 'translate': + matrix.translate(v[0], v[1] || 0); + break; + case 'scale': + matrix.scale(v); + break; + case 'skewX': + matrix.skew(v[0], 0); + break; + case 'skewY': + matrix.skew(0, v[0]); + break; + } + } + item.transform(matrix); + } + } + + function applyOpacity(item, value, name) { + var key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor', + color = item[key] && item[key](); + if (color) + color.setAlpha(parseFloat(value)); + } + + var attributes = Base.set(Base.each(SvgStyles, function(entry) { + this[entry.attribute] = function(item, value) { + if (item[entry.set]) { + item[entry.set](convertValue(value, entry.type, entry.fromSVG)); + if (entry.type === 'color') { + var color = item[entry.get](); + if (color) { + if (color._scaleToBounds) { + var bounds = item.getBounds(); + color.transform(new Matrix() + .translate(bounds.getPoint()) + .scale(bounds.getSize())); + } + } + } + } + }; + }, {}), { + id: function(item, value) { + definitions[value] = item; + if (item.setName) + item.setName(value); + }, + + 'clip-path': function(item, value) { + var clip = getDefinition(value); + if (clip) { + clip = clip.clone(); + clip.setClipMask(true); + if (item instanceof Group) { + item.insertChild(0, clip); + } else { + return new Group(clip, item); + } + } + }, + + gradientTransform: applyTransform, + transform: applyTransform, + + 'fill-opacity': applyOpacity, + 'stroke-opacity': applyOpacity, + + visibility: function(item, value) { + if (item.setVisible) + item.setVisible(value === 'visible'); + }, + + display: function(item, value) { + if (item.setVisible) + item.setVisible(value !== null); + }, + + 'stop-color': function(item, value) { + if (item.setColor) + item.setColor(value); + }, + + 'stop-opacity': function(item, value) { + if (item._color) + item._color.setAlpha(parseFloat(value)); + }, + + offset: function(item, value) { + if (item.setOffset) { + var percent = value.match(/(.*)%$/); + item.setOffset(percent ? percent[1] / 100 : parseFloat(value)); + } + }, + + viewBox: function(item, value, name, node, styles) { + var rect = new Rectangle(convertValue(value, 'array')), + size = getSize(node, null, null, true), + group, + matrix; + if (item instanceof Group) { + var scale = size ? size.divide(rect.getSize()) : 1, + matrix = new Matrix().scale(scale) + .translate(rect.getPoint().negate()); + group = item; + } else if (item instanceof SymbolDefinition) { + if (size) + rect.setSize(size); + group = item._item; + } + if (group) { + if (getAttribute(node, 'overflow', styles) !== 'visible') { + var clip = new Shape.Rectangle(rect); + clip.setClipMask(true); + group.addChild(clip); + } + if (matrix) + group.transform(matrix); + } + } + }); + + function getAttribute(node, name, styles) { + var attr = node.attributes[name], + value = attr && attr.value; + if (!value && node.style) { + var style = Base.camelize(name); + value = node.style[style]; + if (!value && styles.node[style] !== styles.parent[style]) + value = styles.node[style]; + } + return !value ? undefined + : value === 'none' ? null + : value; + } + + function applyAttributes(item, node, isRoot) { + var parent = node.parentNode, + styles = { + node: DomElement.getStyles(node) || {}, + parent: !isRoot && !/^defs$/i.test(parent.tagName) + && DomElement.getStyles(parent) || {} + }; + Base.each(attributes, function(apply, name) { + var value = getAttribute(node, name, styles); + item = value !== undefined + && apply(item, value, name, node, styles) || item; + }); + return item; + } + + function getDefinition(value) { + var match = value && value.match(/\((?:["'#]*)([^"')]+)/), + name = match && match[1], + res = name && definitions[window + ? name.replace(window.location.href.split('#')[0] + '#', '') + : name]; + if (res && res._scaleToBounds) { + res = res.clone(); + res._scaleToBounds = true; + } + return res; + } + + function importNode(node, options, isRoot) { + var type = node.nodeName.toLowerCase(), + isElement = type !== '#document', + body = document.body, + container, + parent, + next; + if (isRoot && isElement) { + rootSize = paper.getView().getSize(); + rootSize = getSize(node, null, null, true) || rootSize; + container = SvgElement.create('svg', { + style: 'stroke-width: 1px; stroke-miterlimit: 10' + }); + parent = node.parentNode; + next = node.nextSibling; + container.appendChild(node); + body.appendChild(container); + } + var settings = paper.settings, + applyMatrix = settings.applyMatrix, + insertItems = settings.insertItems; + settings.applyMatrix = false; + settings.insertItems = false; + var importer = importers[type], + item = importer && importer(node, type, options, isRoot) || null; + settings.insertItems = insertItems; + settings.applyMatrix = applyMatrix; + if (item) { + if (isElement && !(item instanceof Group)) + item = applyAttributes(item, node, isRoot); + var onImport = options.onImport, + data = isElement && node.getAttribute('data-paper-data'); + if (onImport) + item = onImport(node, item, options) || item; + if (options.expandShapes && item instanceof Shape) { + item.remove(); + item = item.toPath(); + } + if (data) + item._data = JSON.parse(data); + } + if (container) { + body.removeChild(container); + if (parent) { + if (next) { + parent.insertBefore(node, next); + } else { + parent.appendChild(node); + } + } + } + if (isRoot) { + definitions = {}; + if (item && Base.pick(options.applyMatrix, applyMatrix)) + item.matrix.apply(true, true); + } + return item; + } + + function importSVG(source, options, owner) { + if (!source) + return null; + options = typeof options === 'function' ? { onLoad: options } + : options || {}; + var scope = paper, + item = null; + + function onLoad(svg) { + try { + var node = typeof svg === 'object' + ? svg + : new self.DOMParser().parseFromString( + svg.trim(), + 'image/svg+xml' + ); + if (!node.nodeName) { + node = null; + throw new Error('Unsupported SVG source: ' + source); + } + paper = scope; + item = importNode(node, options, true); + if (!options || options.insert !== false) { + owner._insertItem(undefined, item); + } + var onLoad = options.onLoad; + if (onLoad) + onLoad(item, svg); + } catch (e) { + onError(e); + } + } + + function onError(message, status) { + var onError = options.onError; + if (onError) { + onError(message, status); + } else { + throw new Error(message); + } + } + + if (typeof source === 'string' && !/^[\s\S]* 3) { + cats.sort(function(a, b) {return b.length - a.length;}); + f += "switch(str.length){"; + for (var i = 0; i < cats.length; ++i) { + var cat = cats[i]; + f += "case " + cat[0].length + ":"; + compareTo(cat); + } + f += "}"; + + } else { + compareTo(words); + } + return new Function("str", f); + } + + var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); + + var isReservedWord5 = makePredicate("class enum extends super const export import"); + + var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); + + var isStrictBadIdWord = makePredicate("eval arguments"); + + var isKeyword = makePredicate("break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"); + + var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; + var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; + var nonASCIIidentifierChars = "\u0300-\u036f\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u0620-\u0649\u0672-\u06d3\u06e7-\u06e8\u06fb-\u06fc\u0730-\u074a\u0800-\u0814\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0840-\u0857\u08e4-\u08fe\u0900-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962-\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09d7\u09df-\u09e0\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5f-\u0b60\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2-\u0ce3\u0ce6-\u0cef\u0d02\u0d03\u0d46-\u0d48\u0d57\u0d62-\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e34-\u0e3a\u0e40-\u0e45\u0e50-\u0e59\u0eb4-\u0eb9\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f41-\u0f47\u0f71-\u0f84\u0f86-\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1029\u1040-\u1049\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u170e-\u1710\u1720-\u1730\u1740-\u1750\u1772\u1773\u1780-\u17b2\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1920-\u192b\u1930-\u193b\u1951-\u196d\u19b0-\u19c0\u19c8-\u19c9\u19d0-\u19d9\u1a00-\u1a15\u1a20-\u1a53\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1b46-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1bb0-\u1bb9\u1be6-\u1bf3\u1c00-\u1c22\u1c40-\u1c49\u1c5b-\u1c7d\u1cd0-\u1cd2\u1d00-\u1dbe\u1e01-\u1f15\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2d81-\u2d96\u2de0-\u2dff\u3021-\u3028\u3099\u309a\ua640-\ua66d\ua674-\ua67d\ua69f\ua6f0-\ua6f1\ua7f8-\ua800\ua806\ua80b\ua823-\ua827\ua880-\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8f3-\ua8f7\ua900-\ua909\ua926-\ua92d\ua930-\ua945\ua980-\ua983\ua9b3-\ua9c0\uaa00-\uaa27\uaa40-\uaa41\uaa4c-\uaa4d\uaa50-\uaa59\uaa7b\uaae0-\uaae9\uaaf2-\uaaf3\uabc0-\uabe1\uabec\uabed\uabf0-\uabf9\ufb20-\ufb28\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); + var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + + var newline = /[\n\r\u2028\u2029]/; + + var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; + + var isIdentifierStart = exports.isIdentifierStart = function(code) { + if (code < 65) return code === 36; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + }; + + var isIdentifierChar = exports.isIdentifierChar = function(code) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123)return true; + return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + }; + + function line_loc_t() { + this.line = tokCurLine; + this.column = tokPos - tokLineStart; + } + + function initTokenState() { + tokCurLine = 1; + tokPos = tokLineStart = 0; + tokRegexpAllowed = true; + skipSpace(); + } + + function finishToken(type, val) { + tokEnd = tokPos; + if (options.locations) tokEndLoc = new line_loc_t; + tokType = type; + skipSpace(); + tokVal = val; + tokRegexpAllowed = type.beforeExpr; + } + + function skipBlockComment() { + var startLoc = options.onComment && options.locations && new line_loc_t; + var start = tokPos, end = input.indexOf("*/", tokPos += 2); + if (end === -1) raise(tokPos - 2, "Unterminated comment"); + tokPos = end + 2; + if (options.locations) { + lineBreak.lastIndex = start; + var match; + while ((match = lineBreak.exec(input)) && match.index < tokPos) { + ++tokCurLine; + tokLineStart = match.index + match[0].length; + } + } + if (options.onComment) + options.onComment(true, input.slice(start + 2, end), start, tokPos, + startLoc, options.locations && new line_loc_t); + } + + function skipLineComment() { + var start = tokPos; + var startLoc = options.onComment && options.locations && new line_loc_t; + var ch = input.charCodeAt(tokPos+=2); + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { + ++tokPos; + ch = input.charCodeAt(tokPos); + } + if (options.onComment) + options.onComment(false, input.slice(start + 2, tokPos), start, tokPos, + startLoc, options.locations && new line_loc_t); + } + + function skipSpace() { + while (tokPos < inputLen) { + var ch = input.charCodeAt(tokPos); + if (ch === 32) { + ++tokPos; + } else if (ch === 13) { + ++tokPos; + var next = input.charCodeAt(tokPos); + if (next === 10) { + ++tokPos; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch === 10 || ch === 8232 || ch === 8233) { + ++tokPos; + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch > 8 && ch < 14) { + ++tokPos; + } else if (ch === 47) { + var next = input.charCodeAt(tokPos + 1); + if (next === 42) { + skipBlockComment(); + } else if (next === 47) { + skipLineComment(); + } else break; + } else if (ch === 160) { + ++tokPos; + } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++tokPos; + } else { + break; + } + } + } + + function readToken_dot() { + var next = input.charCodeAt(tokPos + 1); + if (next >= 48 && next <= 57) return readNumber(true); + ++tokPos; + return finishToken(_dot); + } + + function readToken_slash() { + var next = input.charCodeAt(tokPos + 1); + if (tokRegexpAllowed) {++tokPos; return readRegexp();} + if (next === 61) return finishOp(_assign, 2); + return finishOp(_slash, 1); + } + + function readToken_mult_modulo() { + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_multiplyModulo, 1); + } + + function readToken_pipe_amp(code) { + var next = input.charCodeAt(tokPos + 1); + if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2); + if (next === 61) return finishOp(_assign, 2); + return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1); + } + + function readToken_caret() { + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_bitwiseXOR, 1); + } + + function readToken_plus_min(code) { + var next = input.charCodeAt(tokPos + 1); + if (next === code) { + if (next == 45 && input.charCodeAt(tokPos + 2) == 62 && + newline.test(input.slice(lastEnd, tokPos))) { + tokPos += 3; + skipLineComment(); + skipSpace(); + return readToken(); + } + return finishOp(_incDec, 2); + } + if (next === 61) return finishOp(_assign, 2); + return finishOp(_plusMin, 1); + } + + function readToken_lt_gt(code) { + var next = input.charCodeAt(tokPos + 1); + var size = 1; + if (next === code) { + size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2; + if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); + return finishOp(_bitShift, size); + } + if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 && + input.charCodeAt(tokPos + 3) == 45) { + tokPos += 4; + skipLineComment(); + skipSpace(); + return readToken(); + } + if (next === 61) + size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2; + return finishOp(_relational, size); + } + + function readToken_eq_excl(code) { + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2); + return finishOp(code === 61 ? _eq : _prefix, 1); + } + + function getTokenFromCode(code) { + switch(code) { + case 46: + return readToken_dot(); + + case 40: ++tokPos; return finishToken(_parenL); + case 41: ++tokPos; return finishToken(_parenR); + case 59: ++tokPos; return finishToken(_semi); + case 44: ++tokPos; return finishToken(_comma); + case 91: ++tokPos; return finishToken(_bracketL); + case 93: ++tokPos; return finishToken(_bracketR); + case 123: ++tokPos; return finishToken(_braceL); + case 125: ++tokPos; return finishToken(_braceR); + case 58: ++tokPos; return finishToken(_colon); + case 63: ++tokPos; return finishToken(_question); + + case 48: + var next = input.charCodeAt(tokPos + 1); + if (next === 120 || next === 88) return readHexNumber(); + case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: + return readNumber(false); + + case 34: case 39: + return readString(code); + + case 47: + return readToken_slash(code); + + case 37: case 42: + return readToken_mult_modulo(); + + case 124: case 38: + return readToken_pipe_amp(code); + + case 94: + return readToken_caret(); + + case 43: case 45: + return readToken_plus_min(code); + + case 60: case 62: + return readToken_lt_gt(code); + + case 61: case 33: + return readToken_eq_excl(code); + + case 126: + return finishOp(_prefix, 1); + } + + return false; + } + + function readToken(forceRegexp) { + if (!forceRegexp) tokStart = tokPos; + else tokPos = tokStart + 1; + if (options.locations) tokStartLoc = new line_loc_t; + if (forceRegexp) return readRegexp(); + if (tokPos >= inputLen) return finishToken(_eof); + + var code = input.charCodeAt(tokPos); + if (isIdentifierStart(code) || code === 92 ) return readWord(); + + var tok = getTokenFromCode(code); + + if (tok === false) { + var ch = String.fromCharCode(code); + if (ch === "\\" || nonASCIIidentifierStart.test(ch)) return readWord(); + raise(tokPos, "Unexpected character '" + ch + "'"); + } + return tok; + } + + function finishOp(type, size) { + var str = input.slice(tokPos, tokPos + size); + tokPos += size; + finishToken(type, str); + } + + function readRegexp() { + var content = "", escaped, inClass, start = tokPos; + for (;;) { + if (tokPos >= inputLen) raise(start, "Unterminated regular expression"); + var ch = input.charAt(tokPos); + if (newline.test(ch)) raise(start, "Unterminated regular expression"); + if (!escaped) { + if (ch === "[") inClass = true; + else if (ch === "]" && inClass) inClass = false; + else if (ch === "/" && !inClass) break; + escaped = ch === "\\"; + } else escaped = false; + ++tokPos; + } + var content = input.slice(start, tokPos); + ++tokPos; + var mods = readWord1(); + if (mods && !/^[gmsiy]*$/.test(mods)) raise(start, "Invalid regexp flag"); + try { + var value = new RegExp(content, mods); + } catch (e) { + if (e instanceof SyntaxError) raise(start, e.message); + raise(e); + } + return finishToken(_regexp, value); + } + + function readInt(radix, len) { + var start = tokPos, total = 0; + for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) { + var code = input.charCodeAt(tokPos), val; + if (code >= 97) val = code - 97 + 10; + else if (code >= 65) val = code - 65 + 10; + else if (code >= 48 && code <= 57) val = code - 48; + else val = Infinity; + if (val >= radix) break; + ++tokPos; + total = total * radix + val; + } + if (tokPos === start || len != null && tokPos - start !== len) return null; + + return total; + } + + function readHexNumber() { + tokPos += 2; + var val = readInt(16); + if (val == null) raise(tokStart + 2, "Expected hexadecimal number"); + if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); + return finishToken(_num, val); + } + + function readNumber(startsWithDot) { + var start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48; + if (!startsWithDot && readInt(10) === null) raise(start, "Invalid number"); + if (input.charCodeAt(tokPos) === 46) { + ++tokPos; + readInt(10); + isFloat = true; + } + var next = input.charCodeAt(tokPos); + if (next === 69 || next === 101) { + next = input.charCodeAt(++tokPos); + if (next === 43 || next === 45) ++tokPos; + if (readInt(10) === null) raise(start, "Invalid number"); + isFloat = true; + } + if (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, "Identifier directly after number"); + + var str = input.slice(start, tokPos), val; + if (isFloat) val = parseFloat(str); + else if (!octal || str.length === 1) val = parseInt(str, 10); + else if (/[89]/.test(str) || strict) raise(start, "Invalid number"); + else val = parseInt(str, 8); + return finishToken(_num, val); + } + + function readString(quote) { + tokPos++; + var out = ""; + for (;;) { + if (tokPos >= inputLen) raise(tokStart, "Unterminated string constant"); + var ch = input.charCodeAt(tokPos); + if (ch === quote) { + ++tokPos; + return finishToken(_string, out); + } + if (ch === 92) { + ch = input.charCodeAt(++tokPos); + var octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3)); + if (octal) octal = octal[0]; + while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1); + if (octal === "0") octal = null; + ++tokPos; + if (octal) { + if (strict) raise(tokPos - 2, "Octal literal in strict mode"); + out += String.fromCharCode(parseInt(octal, 8)); + tokPos += octal.length - 1; + } else { + switch (ch) { + case 110: out += "\n"; break; + case 114: out += "\r"; break; + case 120: out += String.fromCharCode(readHexChar(2)); break; + case 117: out += String.fromCharCode(readHexChar(4)); break; + case 85: out += String.fromCharCode(readHexChar(8)); break; + case 116: out += "\t"; break; + case 98: out += "\b"; break; + case 118: out += "\u000b"; break; + case 102: out += "\f"; break; + case 48: out += "\0"; break; + case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos; + case 10: + if (options.locations) { tokLineStart = tokPos; ++tokCurLine; } + break; + default: out += String.fromCharCode(ch); break; + } + } + } else { + if (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, "Unterminated string constant"); + out += String.fromCharCode(ch); + ++tokPos; + } + } + } + + function readHexChar(len) { + var n = readInt(16, len); + if (n === null) raise(tokStart, "Bad character escape sequence"); + return n; + } + + var containsEsc; + + function readWord1() { + containsEsc = false; + var word, first = true, start = tokPos; + for (;;) { + var ch = input.charCodeAt(tokPos); + if (isIdentifierChar(ch)) { + if (containsEsc) word += input.charAt(tokPos); + ++tokPos; + } else if (ch === 92) { + if (!containsEsc) word = input.slice(start, tokPos); + containsEsc = true; + if (input.charCodeAt(++tokPos) != 117) + raise(tokPos, "Expecting Unicode escape sequence \\uXXXX"); + ++tokPos; + var esc = readHexChar(4); + var escStr = String.fromCharCode(esc); + if (!escStr) raise(tokPos - 1, "Invalid Unicode escape"); + if (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc))) + raise(tokPos - 4, "Invalid Unicode escape"); + word += escStr; + } else { + break; + } + first = false; + } + return containsEsc ? word : input.slice(start, tokPos); + } + + function readWord() { + var word = readWord1(); + var type = _name; + if (!containsEsc && isKeyword(word)) + type = keywordTypes[word]; + return finishToken(type, word); + } + + function next() { + lastStart = tokStart; + lastEnd = tokEnd; + lastEndLoc = tokEndLoc; + readToken(); + } + + function setStrict(strct) { + strict = strct; + tokPos = tokStart; + if (options.locations) { + while (tokPos < tokLineStart) { + tokLineStart = input.lastIndexOf("\n", tokLineStart - 2) + 1; + --tokCurLine; + } + } + skipSpace(); + readToken(); + } + + function node_t() { + this.type = null; + this.start = tokStart; + this.end = null; + } + + function node_loc_t() { + this.start = tokStartLoc; + this.end = null; + if (sourceFile !== null) this.source = sourceFile; + } + + function startNode() { + var node = new node_t(); + if (options.locations) + node.loc = new node_loc_t(); + if (options.directSourceFile) + node.sourceFile = options.directSourceFile; + if (options.ranges) + node.range = [tokStart, 0]; + return node; + } + + function startNodeFrom(other) { + var node = new node_t(); + node.start = other.start; + if (options.locations) { + node.loc = new node_loc_t(); + node.loc.start = other.loc.start; + } + if (options.ranges) + node.range = [other.range[0], 0]; + + return node; + } + + function finishNode(node, type) { + node.type = type; + node.end = lastEnd; + if (options.locations) + node.loc.end = lastEndLoc; + if (options.ranges) + node.range[1] = lastEnd; + return node; + } + + function isUseStrict(stmt) { + return options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && + stmt.expression.type === "Literal" && stmt.expression.value === "use strict"; + } + + function eat(type) { + if (tokType === type) { + next(); + return true; + } + } + + function canInsertSemicolon() { + return !options.strictSemicolons && + (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart))); + } + + function semicolon() { + if (!eat(_semi) && !canInsertSemicolon()) unexpected(); + } + + function expect(type) { + if (tokType === type) next(); + else unexpected(); + } + + function unexpected() { + raise(tokStart, "Unexpected token"); + } + + function checkLVal(expr) { + if (expr.type !== "Identifier" && expr.type !== "MemberExpression") + raise(expr.start, "Assigning to rvalue"); + if (strict && expr.type === "Identifier" && isStrictBadIdWord(expr.name)) + raise(expr.start, "Assigning to " + expr.name + " in strict mode"); + } + + function parseTopLevel(program) { + lastStart = lastEnd = tokPos; + if (options.locations) lastEndLoc = new line_loc_t; + inFunction = strict = null; + labels = []; + readToken(); + + var node = program || startNode(), first = true; + if (!program) node.body = []; + while (tokType !== _eof) { + var stmt = parseStatement(); + node.body.push(stmt); + if (first && isUseStrict(stmt)) setStrict(true); + first = false; + } + return finishNode(node, "Program"); + } + + var loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}; + + function parseStatement() { + if (tokType === _slash || tokType === _assign && tokVal == "/=") + readToken(true); + + var starttype = tokType, node = startNode(); + + switch (starttype) { + case _break: case _continue: + next(); + var isBreak = starttype === _break; + if (eat(_semi) || canInsertSemicolon()) node.label = null; + else if (tokType !== _name) unexpected(); + else { + node.label = parseIdent(); + semicolon(); + } + + for (var i = 0; i < labels.length; ++i) { + var lab = labels[i]; + if (node.label == null || lab.name === node.label.name) { + if (lab.kind != null && (isBreak || lab.kind === "loop")) break; + if (node.label && isBreak) break; + } + } + if (i === labels.length) raise(node.start, "Unsyntactic " + starttype.keyword); + return finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); + + case _debugger: + next(); + semicolon(); + return finishNode(node, "DebuggerStatement"); + + case _do: + next(); + labels.push(loopLabel); + node.body = parseStatement(); + labels.pop(); + expect(_while); + node.test = parseParenExpression(); + semicolon(); + return finishNode(node, "DoWhileStatement"); + + case _for: + next(); + labels.push(loopLabel); + expect(_parenL); + if (tokType === _semi) return parseFor(node, null); + if (tokType === _var) { + var init = startNode(); + next(); + parseVar(init, true); + finishNode(init, "VariableDeclaration"); + if (init.declarations.length === 1 && eat(_in)) + return parseForIn(node, init); + return parseFor(node, init); + } + var init = parseExpression(false, true); + if (eat(_in)) {checkLVal(init); return parseForIn(node, init);} + return parseFor(node, init); + + case _function: + next(); + return parseFunction(node, true); + + case _if: + next(); + node.test = parseParenExpression(); + node.consequent = parseStatement(); + node.alternate = eat(_else) ? parseStatement() : null; + return finishNode(node, "IfStatement"); + + case _return: + if (!inFunction && !options.allowReturnOutsideFunction) + raise(tokStart, "'return' outside of function"); + next(); + + if (eat(_semi) || canInsertSemicolon()) node.argument = null; + else { node.argument = parseExpression(); semicolon(); } + return finishNode(node, "ReturnStatement"); + + case _switch: + next(); + node.discriminant = parseParenExpression(); + node.cases = []; + expect(_braceL); + labels.push(switchLabel); + + for (var cur, sawDefault; tokType != _braceR;) { + if (tokType === _case || tokType === _default) { + var isCase = tokType === _case; + if (cur) finishNode(cur, "SwitchCase"); + node.cases.push(cur = startNode()); + cur.consequent = []; + next(); + if (isCase) cur.test = parseExpression(); + else { + if (sawDefault) raise(lastStart, "Multiple default clauses"); sawDefault = true; + cur.test = null; + } + expect(_colon); + } else { + if (!cur) unexpected(); + cur.consequent.push(parseStatement()); + } + } + if (cur) finishNode(cur, "SwitchCase"); + next(); + labels.pop(); + return finishNode(node, "SwitchStatement"); + + case _throw: + next(); + if (newline.test(input.slice(lastEnd, tokStart))) + raise(lastEnd, "Illegal newline after throw"); + node.argument = parseExpression(); + semicolon(); + return finishNode(node, "ThrowStatement"); + + case _try: + next(); + node.block = parseBlock(); + node.handler = null; + if (tokType === _catch) { + var clause = startNode(); + next(); + expect(_parenL); + clause.param = parseIdent(); + if (strict && isStrictBadIdWord(clause.param.name)) + raise(clause.param.start, "Binding " + clause.param.name + " in strict mode"); + expect(_parenR); + clause.guard = null; + clause.body = parseBlock(); + node.handler = finishNode(clause, "CatchClause"); + } + node.guardedHandlers = empty; + node.finalizer = eat(_finally) ? parseBlock() : null; + if (!node.handler && !node.finalizer) + raise(node.start, "Missing catch or finally clause"); + return finishNode(node, "TryStatement"); + + case _var: + next(); + parseVar(node); + semicolon(); + return finishNode(node, "VariableDeclaration"); + + case _while: + next(); + node.test = parseParenExpression(); + labels.push(loopLabel); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "WhileStatement"); + + case _with: + if (strict) raise(tokStart, "'with' in strict mode"); + next(); + node.object = parseParenExpression(); + node.body = parseStatement(); + return finishNode(node, "WithStatement"); + + case _braceL: + return parseBlock(); + + case _semi: + next(); + return finishNode(node, "EmptyStatement"); + + default: + var maybeName = tokVal, expr = parseExpression(); + if (starttype === _name && expr.type === "Identifier" && eat(_colon)) { + for (var i = 0; i < labels.length; ++i) + if (labels[i].name === maybeName) raise(expr.start, "Label '" + maybeName + "' is already declared"); + var kind = tokType.isLoop ? "loop" : tokType === _switch ? "switch" : null; + labels.push({name: maybeName, kind: kind}); + node.body = parseStatement(); + labels.pop(); + node.label = expr; + return finishNode(node, "LabeledStatement"); + } else { + node.expression = expr; + semicolon(); + return finishNode(node, "ExpressionStatement"); + } + } + } + + function parseParenExpression() { + expect(_parenL); + var val = parseExpression(); + expect(_parenR); + return val; + } + + function parseBlock(allowStrict) { + var node = startNode(), first = true, strict = false, oldStrict; + node.body = []; + expect(_braceL); + while (!eat(_braceR)) { + var stmt = parseStatement(); + node.body.push(stmt); + if (first && allowStrict && isUseStrict(stmt)) { + oldStrict = strict; + setStrict(strict = true); + } + first = false; + } + if (strict && !oldStrict) setStrict(false); + return finishNode(node, "BlockStatement"); + } + + function parseFor(node, init) { + node.init = init; + expect(_semi); + node.test = tokType === _semi ? null : parseExpression(); + expect(_semi); + node.update = tokType === _parenR ? null : parseExpression(); + expect(_parenR); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "ForStatement"); + } + + function parseForIn(node, init) { + node.left = init; + node.right = parseExpression(); + expect(_parenR); + node.body = parseStatement(); + labels.pop(); + return finishNode(node, "ForInStatement"); + } + + function parseVar(node, noIn) { + node.declarations = []; + node.kind = "var"; + for (;;) { + var decl = startNode(); + decl.id = parseIdent(); + if (strict && isStrictBadIdWord(decl.id.name)) + raise(decl.id.start, "Binding " + decl.id.name + " in strict mode"); + decl.init = eat(_eq) ? parseExpression(true, noIn) : null; + node.declarations.push(finishNode(decl, "VariableDeclarator")); + if (!eat(_comma)) break; + } + return node; + } + + function parseExpression(noComma, noIn) { + var expr = parseMaybeAssign(noIn); + if (!noComma && tokType === _comma) { + var node = startNodeFrom(expr); + node.expressions = [expr]; + while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn)); + return finishNode(node, "SequenceExpression"); + } + return expr; + } + + function parseMaybeAssign(noIn) { + var left = parseMaybeConditional(noIn); + if (tokType.isAssign) { + var node = startNodeFrom(left); + node.operator = tokVal; + node.left = left; + next(); + node.right = parseMaybeAssign(noIn); + checkLVal(left); + return finishNode(node, "AssignmentExpression"); + } + return left; + } + + function parseMaybeConditional(noIn) { + var expr = parseExprOps(noIn); + if (eat(_question)) { + var node = startNodeFrom(expr); + node.test = expr; + node.consequent = parseExpression(true); + expect(_colon); + node.alternate = parseExpression(true, noIn); + return finishNode(node, "ConditionalExpression"); + } + return expr; + } + + function parseExprOps(noIn) { + return parseExprOp(parseMaybeUnary(), -1, noIn); + } + + function parseExprOp(left, minPrec, noIn) { + var prec = tokType.binop; + if (prec != null && (!noIn || tokType !== _in)) { + if (prec > minPrec) { + var node = startNodeFrom(left); + node.left = left; + node.operator = tokVal; + var op = tokType; + next(); + node.right = parseExprOp(parseMaybeUnary(), prec, noIn); + var exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? "LogicalExpression" : "BinaryExpression"); + return parseExprOp(exprNode, minPrec, noIn); + } + } + return left; + } + + function parseMaybeUnary() { + if (tokType.prefix) { + var node = startNode(), update = tokType.isUpdate; + node.operator = tokVal; + node.prefix = true; + tokRegexpAllowed = true; + next(); + node.argument = parseMaybeUnary(); + if (update) checkLVal(node.argument); + else if (strict && node.operator === "delete" && + node.argument.type === "Identifier") + raise(node.start, "Deleting local variable in strict mode"); + return finishNode(node, update ? "UpdateExpression" : "UnaryExpression"); + } + var expr = parseExprSubscripts(); + while (tokType.postfix && !canInsertSemicolon()) { + var node = startNodeFrom(expr); + node.operator = tokVal; + node.prefix = false; + node.argument = expr; + checkLVal(expr); + next(); + expr = finishNode(node, "UpdateExpression"); + } + return expr; + } + + function parseExprSubscripts() { + return parseSubscripts(parseExprAtom()); + } + + function parseSubscripts(base, noCalls) { + if (eat(_dot)) { + var node = startNodeFrom(base); + node.object = base; + node.property = parseIdent(true); + node.computed = false; + return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); + } else if (eat(_bracketL)) { + var node = startNodeFrom(base); + node.object = base; + node.property = parseExpression(); + node.computed = true; + expect(_bracketR); + return parseSubscripts(finishNode(node, "MemberExpression"), noCalls); + } else if (!noCalls && eat(_parenL)) { + var node = startNodeFrom(base); + node.callee = base; + node.arguments = parseExprList(_parenR, false); + return parseSubscripts(finishNode(node, "CallExpression"), noCalls); + } else return base; + } + + function parseExprAtom() { + switch (tokType) { + case _this: + var node = startNode(); + next(); + return finishNode(node, "ThisExpression"); + case _name: + return parseIdent(); + case _num: case _string: case _regexp: + var node = startNode(); + node.value = tokVal; + node.raw = input.slice(tokStart, tokEnd); + next(); + return finishNode(node, "Literal"); + + case _null: case _true: case _false: + var node = startNode(); + node.value = tokType.atomValue; + node.raw = tokType.keyword; + next(); + return finishNode(node, "Literal"); + + case _parenL: + var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart; + next(); + var val = parseExpression(); + val.start = tokStart1; + val.end = tokEnd; + if (options.locations) { + val.loc.start = tokStartLoc1; + val.loc.end = tokEndLoc; + } + if (options.ranges) + val.range = [tokStart1, tokEnd]; + expect(_parenR); + return val; + + case _bracketL: + var node = startNode(); + next(); + node.elements = parseExprList(_bracketR, true, true); + return finishNode(node, "ArrayExpression"); + + case _braceL: + return parseObj(); + + case _function: + var node = startNode(); + next(); + return parseFunction(node, false); + + case _new: + return parseNew(); + + default: + unexpected(); + } + } + + function parseNew() { + var node = startNode(); + next(); + node.callee = parseSubscripts(parseExprAtom(), true); + if (eat(_parenL)) node.arguments = parseExprList(_parenR, false); + else node.arguments = empty; + return finishNode(node, "NewExpression"); + } + + function parseObj() { + var node = startNode(), first = true, sawGetSet = false; + node.properties = []; + next(); + while (!eat(_braceR)) { + if (!first) { + expect(_comma); + if (options.allowTrailingCommas && eat(_braceR)) break; + } else first = false; + + var prop = {key: parsePropertyName()}, isGetSet = false, kind; + if (eat(_colon)) { + prop.value = parseExpression(true); + kind = prop.kind = "init"; + } else if (options.ecmaVersion >= 5 && prop.key.type === "Identifier" && + (prop.key.name === "get" || prop.key.name === "set")) { + isGetSet = sawGetSet = true; + kind = prop.kind = prop.key.name; + prop.key = parsePropertyName(); + if (tokType !== _parenL) unexpected(); + prop.value = parseFunction(startNode(), false); + } else unexpected(); + + if (prop.key.type === "Identifier" && (strict || sawGetSet)) { + for (var i = 0; i < node.properties.length; ++i) { + var other = node.properties[i]; + if (other.key.name === prop.key.name) { + var conflict = kind == other.kind || isGetSet && other.kind === "init" || + kind === "init" && (other.kind === "get" || other.kind === "set"); + if (conflict && !strict && kind === "init" && other.kind === "init") conflict = false; + if (conflict) raise(prop.key.start, "Redefinition of property"); + } + } + } + node.properties.push(prop); + } + return finishNode(node, "ObjectExpression"); + } + + function parsePropertyName() { + if (tokType === _num || tokType === _string) return parseExprAtom(); + return parseIdent(true); + } + + function parseFunction(node, isStatement) { + if (tokType === _name) node.id = parseIdent(); + else if (isStatement) unexpected(); + else node.id = null; + node.params = []; + var first = true; + expect(_parenL); + while (!eat(_parenR)) { + if (!first) expect(_comma); else first = false; + node.params.push(parseIdent()); + } + + var oldInFunc = inFunction, oldLabels = labels; + inFunction = true; labels = []; + node.body = parseBlock(true); + inFunction = oldInFunc; labels = oldLabels; + + if (strict || node.body.body.length && isUseStrict(node.body.body[0])) { + for (var i = node.id ? -1 : 0; i < node.params.length; ++i) { + var id = i < 0 ? node.id : node.params[i]; + if (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name)) + raise(id.start, "Defining '" + id.name + "' in strict mode"); + if (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name) + raise(id.start, "Argument name clash in strict mode"); + } + } + + return finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression"); + } + + function parseExprList(close, allowTrailingComma, allowEmpty) { + var elts = [], first = true; + while (!eat(close)) { + if (!first) { + expect(_comma); + if (allowTrailingComma && options.allowTrailingCommas && eat(close)) break; + } else first = false; + + if (allowEmpty && tokType === _comma) elts.push(null); + else elts.push(parseExpression(true)); + } + return elts; + } + + function parseIdent(liberal) { + var node = startNode(); + if (liberal && options.forbidReserved == "everywhere") liberal = false; + if (tokType === _name) { + if (!liberal && + (options.forbidReserved && + (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) || + strict && isStrictReservedWord(tokVal)) && + input.slice(tokStart, tokEnd).indexOf("\\") == -1) + raise(tokStart, "The keyword '" + tokVal + "' is reserved"); + node.name = tokVal; + } else if (liberal && tokType.keyword) { + node.name = tokType.keyword; + } else { + unexpected(); + } + tokRegexpAllowed = false; + next(); + return finishNode(node, "Identifier"); + } + +}); + + if (!acorn.version) + acorn = null; + } + + function parse(code, options) { + return (global.acorn || acorn).parse(code, options); + } + + var binaryOperators = { + '+': '__add', + '-': '__subtract', + '*': '__multiply', + '/': '__divide', + '%': '__modulo', + '==': '__equals', + '!=': '__equals' + }; + + var unaryOperators = { + '-': '__negate', + '+': '__self' + }; + + var fields = Base.each( + ['add', 'subtract', 'multiply', 'divide', 'modulo', 'equals', 'negate'], + function(name) { + this['__' + name] = '#' + name; + }, + { + __self: function() { + return this; + } + } + ); + Point.inject(fields); + Size.inject(fields); + Color.inject(fields); + + function __$__(left, operator, right) { + var handler = binaryOperators[operator]; + if (left && left[handler]) { + var res = left[handler](right); + return operator === '!=' ? !res : res; + } + switch (operator) { + case '+': return left + right; + case '-': return left - right; + case '*': return left * right; + case '/': return left / right; + case '%': return left % right; + case '==': return left == right; + case '!=': return left != right; + } + } + + function $__(operator, value) { + var handler = unaryOperators[operator]; + if (value && value[handler]) + return value[handler](); + switch (operator) { + case '+': return +value; + case '-': return -value; + } + } + + function compile(code, options) { + if (!code) + return ''; + options = options || {}; + + var insertions = []; + + function getOffset(offset) { + for (var i = 0, l = insertions.length; i < l; i++) { + var insertion = insertions[i]; + if (insertion[0] >= offset) + break; + offset += insertion[1]; + } + return offset; + } + + function getCode(node) { + return code.substring(getOffset(node.range[0]), + getOffset(node.range[1])); + } + + function getBetween(left, right) { + return code.substring(getOffset(left.range[1]), + getOffset(right.range[0])); + } + + function replaceCode(node, str) { + var start = getOffset(node.range[0]), + end = getOffset(node.range[1]), + insert = 0; + for (var i = insertions.length - 1; i >= 0; i--) { + if (start > insertions[i][0]) { + insert = i + 1; + break; + } + } + insertions.splice(insert, 0, [start, str.length - end + start]); + code = code.substring(0, start) + str + code.substring(end); + } + + function handleOverloading(node, parent) { + switch (node.type) { + case 'UnaryExpression': + if (node.operator in unaryOperators + && node.argument.type !== 'Literal') { + var arg = getCode(node.argument); + replaceCode(node, '$__("' + node.operator + '", ' + + arg + ')'); + } + break; + case 'BinaryExpression': + if (node.operator in binaryOperators + && node.left.type !== 'Literal') { + var left = getCode(node.left), + right = getCode(node.right), + between = getBetween(node.left, node.right), + operator = node.operator; + replaceCode(node, '__$__(' + left + ',' + + between.replace(new RegExp('\\' + operator), + '"' + operator + '"') + + ', ' + right + ')'); + } + break; + case 'UpdateExpression': + case 'AssignmentExpression': + var parentType = parent && parent.type; + if (!( + parentType === 'ForStatement' + || parentType === 'BinaryExpression' + && /^[=!<>]/.test(parent.operator) + || parentType === 'MemberExpression' && parent.computed + )) { + if (node.type === 'UpdateExpression') { + var arg = getCode(node.argument), + exp = '__$__(' + arg + ', "' + node.operator[0] + + '", 1)', + str = arg + ' = ' + exp; + if (node.prefix) { + str = '(' + str + ')'; + } else if ( + parentType === 'AssignmentExpression' || + parentType === 'VariableDeclarator' || + parentType === 'BinaryExpression' + ) { + if (getCode(parent.left || parent.id) === arg) + str = exp; + str = arg + '; ' + str; + } + replaceCode(node, str); + } else { + if (/^.=$/.test(node.operator) + && node.left.type !== 'Literal') { + var left = getCode(node.left), + right = getCode(node.right), + exp = left + ' = __$__(' + left + ', "' + + node.operator[0] + '", ' + right + ')'; + replaceCode(node, /^\(.*\)$/.test(getCode(node)) + ? '(' + exp + ')' : exp); + } + } + } + break; + } + } + + function handleExports(node) { + switch (node.type) { + case 'ExportDefaultDeclaration': + replaceCode({ + range: [node.start, node.declaration.start] + }, 'module.exports = '); + break; + case 'ExportNamedDeclaration': + var declaration = node.declaration; + var specifiers = node.specifiers; + if (declaration) { + var declarations = declaration.declarations; + if (declarations) { + declarations.forEach(function(dec) { + replaceCode(dec, 'module.exports.' + getCode(dec)); + }); + replaceCode({ + range: [ + node.start, + declaration.start + declaration.kind.length + ] + }, ''); + } + } else if (specifiers) { + var exports = specifiers.map(function(specifier) { + var name = getCode(specifier); + return 'module.exports.' + name + ' = ' + name + '; '; + }).join(''); + if (exports) { + replaceCode(node, exports); + } + } + break; + } + } + + function walkAST(node, parent, paperFeatures) { + if (node) { + for (var key in node) { + if (key !== 'range' && key !== 'loc') { + var value = node[key]; + if (Array.isArray(value)) { + for (var i = 0, l = value.length; i < l; i++) { + walkAST(value[i], node, paperFeatures); + } + } else if (value && typeof value === 'object') { + walkAST(value, node, paperFeatures); + } + } + } + if (paperFeatures.operatorOverloading !== false) { + handleOverloading(node, parent); + } + if (paperFeatures.moduleExports !== false) { + handleExports(node); + } + } + } + + function encodeVLQ(value) { + var res = '', + base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + value = (Math.abs(value) << 1) + (value < 0 ? 1 : 0); + while (value || !res) { + var next = value & (32 - 1); + value >>= 5; + if (value) + next |= 32; + res += base64[next]; + } + return res; + } + + var url = options.url || '', + sourceMaps = options.sourceMaps, + paperFeatures = options.paperFeatures || {}, + source = options.source || code, + offset = options.offset || 0, + agent = paper.agent, + version = agent.versionNumber, + offsetCode = false, + lineBreaks = /\r\n|\n|\r/mg, + map; + if (sourceMaps && (agent.chrome && version >= 30 + || agent.webkit && version >= 537.76 + || agent.firefox && version >= 23 + || agent.node)) { + if (agent.node) { + offset -= 2; + } else if (window && url && !window.location.href.indexOf(url)) { + var html = document.getElementsByTagName('html')[0].innerHTML; + offset = html.substr(0, html.indexOf(code) + 1).match( + lineBreaks).length + 1; + } + offsetCode = offset > 0 && !( + agent.chrome && version >= 36 || + agent.safari && version >= 600 || + agent.firefox && version >= 40 || + agent.node); + var mappings = ['AA' + encodeVLQ(offsetCode ? 0 : offset) + 'A']; + mappings.length = (code.match(lineBreaks) || []).length + 1 + + (offsetCode ? offset : 0); + map = { + version: 3, + file: url, + names:[], + mappings: mappings.join(';AACA'), + sourceRoot: '', + sources: [url], + sourcesContent: [source] + }; + } + if ( + paperFeatures.operatorOverloading !== false || + paperFeatures.moduleExports !== false + ) { + walkAST(parse(code, { + ranges: true, + preserveParens: true, + sourceType: 'module' + }), null, paperFeatures); + } + if (map) { + if (offsetCode) { + code = new Array(offset + 1).join('\n') + code; + } + if (/^(inline|both)$/.test(sourceMaps)) { + code += "\n//# sourceMappingURL=data:application/json;base64," + + self.btoa(unescape(encodeURIComponent( + JSON.stringify(map)))); + } + code += "\n//# sourceURL=" + (url || 'paperscript'); + } + return { + url: url, + source: source, + code: code, + map: map + }; + } + + function execute(code, scope, options) { + paper = scope; + var view = scope.getView(), + tool = /\btool\.\w+|\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)\b/ + .test(code) && !/\bnew\s+Tool\b/.test(code) + ? new Tool() : null, + toolHandlers = tool ? tool._events : [], + handlers = ['onFrame', 'onResize'].concat(toolHandlers), + params = [], + args = [], + func, + compiled = typeof code === 'object' ? code : compile(code, options); + code = compiled.code; + function expose(scope, hidden) { + for (var key in scope) { + if ((hidden || !/^_/.test(key)) && new RegExp('([\\b\\s\\W]|^)' + + key.replace(/\$/g, '\\$') + '\\b').test(code)) { + params.push(key); + args.push(scope[key]); + } + } + } + expose({ __$__: __$__, $__: $__, paper: scope, tool: tool }, + true); + expose(scope); + code = 'var module = { exports: {} }; ' + code; + var exports = Base.each(handlers, function(key) { + if (new RegExp('\\s+' + key + '\\b').test(code)) { + params.push(key); + this.push('module.exports.' + key + ' = ' + key + ';'); + } + }, []).join('\n'); + if (exports) { + code += '\n' + exports; + } + code += '\nreturn module.exports;'; + var agent = paper.agent; + if (document && (agent.chrome + || agent.firefox && agent.versionNumber < 40)) { + var script = document.createElement('script'), + head = document.head || document.getElementsByTagName('head')[0]; + if (agent.firefox) + code = '\n' + code; + script.appendChild(document.createTextNode( + 'document.__paperscript__ = function(' + params + ') {' + + code + + '\n}' + )); + head.appendChild(script); + func = document.__paperscript__; + delete document.__paperscript__; + head.removeChild(script); + } else { + func = Function(params, code); + } + var exports = func && func.apply(scope, args); + var obj = exports || {}; + Base.each(toolHandlers, function(key) { + var value = obj[key]; + if (value) + tool[key] = value; + }); + if (view) { + if (obj.onResize) + view.setOnResize(obj.onResize); + view.emit('resize', { + size: view.size, + delta: new Point() + }); + if (obj.onFrame) + view.setOnFrame(obj.onFrame); + view.requestUpdate(); + } + return exports; + } + + function loadScript(script) { + if (/^text\/(?:x-|)paperscript$/.test(script.type) + && PaperScope.getAttribute(script, 'ignore') !== 'true') { + var canvasId = PaperScope.getAttribute(script, 'canvas'), + canvas = document.getElementById(canvasId), + src = script.src || script.getAttribute('data-src'), + async = PaperScope.hasAttribute(script, 'async'), + scopeAttribute = 'data-paper-scope'; + if (!canvas) + throw new Error('Unable to find canvas with id "' + + canvasId + '"'); + var scope = PaperScope.get(canvas.getAttribute(scopeAttribute)) + || new PaperScope().setup(canvas); + canvas.setAttribute(scopeAttribute, scope._id); + if (src) { + Http.request({ + url: src, + async: async, + mimeType: 'text/plain', + onLoad: function(code) { + execute(code, scope, src); + } + }); + } else { + execute(script.innerHTML, scope, script.baseURI); + } + script.setAttribute('data-paper-ignore', 'true'); + return scope; + } + } + + function loadAll() { + Base.each(document && document.getElementsByTagName('script'), + loadScript); + } + + function load(script) { + return script ? loadScript(script) : loadAll(); + } + + if (window) { + if (document.readyState === 'complete') { + setTimeout(loadAll); + } else { + DomEvent.add(window, { load: loadAll }); + } + } + + return { + compile: compile, + execute: execute, + load: load, + parse: parse, + calculateBinary: __$__, + calculateUnary: $__ + }; + +}.call(this); + +var paper = new (PaperScope.inject(Base.exports, { + Base: Base, + Numerical: Numerical, + Key: Key, + DomEvent: DomEvent, + DomElement: DomElement, + document: document, + window: window, + Symbol: SymbolDefinition, + PlacedSymbol: SymbolItem +}))(); + +if (paper.agent.node) { + require('./node/extend.js')(paper); +} + +if (typeof define === 'function' && define.amd) { + define('paper', paper); +} else if (typeof module === 'object' && module) { + module.exports = paper; +} + +return paper; +}.call(this, typeof self === 'object' ? self : null); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..f59b8af --- /dev/null +++ b/static/style.css @@ -0,0 +1,19 @@ +*{ + margin:0; + padding:0; + box-sizing: border-box; +} + +body{ + background-color: lightgray; +} + +canvas{ + background-color: white; +} + +img{ + max-width: 700px; +} + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5ee8c86 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,32 @@ + + + + + + + + + JSFeat - JavaScript Computer Vision Library. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/photo.html b/templates/photo.html new file mode 100644 index 0000000..58f9128 --- /dev/null +++ b/templates/photo.html @@ -0,0 +1,34 @@ + + + + + + + + + JSFeat - JavaScript Computer Vision Library. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/train.html b/templates/train.html new file mode 100644 index 0000000..a97dda2 --- /dev/null +++ b/templates/train.html @@ -0,0 +1,21 @@ + + + + + + + title + + + + + + +

GraphDraw

+ + + + + + +