Browse Source

first commit :)

master
manetta 4 years ago
commit
a1fa705474
  1. 3
      .gitignore.txt
  2. 34
      LICENSE.txt
  3. 106
      README.md
  4. BIN
      __pycache__/create_index.cpython-37.pyc
  5. BIN
      __pycache__/functions.cpython-37.pyc
  6. BIN
      __pycache__/readings.cpython-37.pyc
  7. BIN
      __pycache__/text.cpython-37.pyc
  8. BIN
      __pycache__/tfidf.cpython-37.pyc
  9. 0
      colors.json
  10. 0
      curves.json
  11. 38
      functions.py
  12. 131
      readings.py
  13. 148
      start.py
  14. 2
      static/contributions/BACK.md
  15. 1
      static/contributions/COLOPHON.md
  16. 3
      static/contributions/COVER.md
  17. 0
      static/contributions/EMPTY.md
  18. 34
      static/contributions/INDEX.md
  19. 107
      static/contributions/README.md
  20. 12
      static/contributions/TOC.md
  21. 585
      static/contributions/collective-conditions.md
  22. 90
      static/contributions/collective-intro-for-publication.md
  23. 132
      static/contributions/holding-spell.md
  24. 80
      static/contributions/iteration-5.md
  25. 394
      static/contributions/rica-rickson.md
  26. 27
      static/contributions/score-esc.md
  27. 320
      static/contributions/spideralex.md
  28. 157
      static/css/curves.css
  29. 162
      static/css/stylesheet.css
  30. 44
      static/css/text-scan.css
  31. BIN
      static/curves/014-Loren-Britton-Disclaimer_5000.jpg
  32. 7
      static/curves/1.svg
  33. 8
      static/curves/10.svg
  34. 8
      static/curves/11.svg
  35. 8
      static/curves/12.svg
  36. 8
      static/curves/13.svg
  37. 8
      static/curves/14.svg
  38. 8
      static/curves/15.svg
  39. 8
      static/curves/16.svg
  40. 8
      static/curves/17.svg
  41. 8
      static/curves/18.svg
  42. 7
      static/curves/2.svg
  43. 7
      static/curves/3.svg
  44. 7
      static/curves/4.svg
  45. 8
      static/curves/5.svg
  46. 8
      static/curves/6.svg
  47. 8
      static/curves/7.svg
  48. 8
      static/curves/8.svg
  49. 8
      static/curves/9.svg
  50. 8
      static/curves/test.svg
  51. 1
      static/fonts/Brazil/.uuid
  52. BIN
      static/fonts/Brazil/BRAZIL BERN 18.11.2010 SHP.png
  53. 23
      static/fonts/Brazil/BRAZIL description and license.rtf
  54. BIN
      static/fonts/Brazil/Brazil-Regular.otf
  55. 95
      static/fonts/Brazil/OFL Brazil.txt
  56. 427
      static/fonts/Brazil/OFL-FAQ.txt
  57. BIN
      static/fonts/go-mono/Go-Mono-Bold-Italic.ttf
  58. BIN
      static/fonts/go-mono/Go-Mono-Bold.ttf
  59. BIN
      static/fonts/go-mono/Go-Mono-Italic.ttf
  60. BIN
      static/fonts/go-mono/Go-Mono.ttf
  61. 36
      static/fonts/go-mono/README
  62. 137
      static/js/bezpath.js
  63. 770
      static/js/curves.js
  64. 860
      static/js/splineui.js
  65. BIN
      static/visual-traces/0033d7f98e0a5c733b16013ecd8005fa.png
  66. BIN
      static/visual-traces/diversity-2019.png
  67. BIN
      static/visual-traces/etherpump.png
  68. 40
      templates/base.html
  69. 48
      templates/color.html
  70. 78
      templates/curves.bak.html
  71. 99
      templates/curves.html
  72. 58
      templates/index.html
  73. 52
      templates/text-scans.html
  74. 38
      templates/text-specifics.html
  75. 56
      templates/text.html
  76. 50
      templates/visual-traces.html
  77. 0
      text.json
  78. 30
      text.py
  79. 131
      tfidf.py
  80. 0
      visual-traces.json

3
.gitignore.txt

@ -0,0 +1,3 @@
__pycache__
__pycache__/*

34
LICENSE.txt

@ -0,0 +1,34 @@
Iterative Annotation Toolkit:
----------------------------
...
Shapes Interface:
----------------
Copyright (c) 2019 Raph Levien
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

106
README.md

@ -0,0 +1,106 @@
# README
## Handles
*Handle number 1: on the systematics of working together at a time (a
temporary togetherness):*
\- What are/imply the **systematics of working together** that need to
be established before a process is even set in motion (like the boot
system in Linux)?
\- How does the **temporality**/chronological evolution of a process
have a role in establishing *befores*, *durings* and *afters* for a
groupal endeavor? How do durabilities, recursions, circularities,
persistences, repetitions and legacies affect the systematics of the
process themselves? How does **recursiveness, feedbacklooping and
crooked circularity of collective practices** become systematic, in
order to hand over of problematics, sensibilities, methods, tools etc.?
*Handle number 2: on the value and problematization of using the pronoun
\"we\" while inhabiting shared structures*:
\- **Enmeshed and engaged**: What if the we can mean **a bigger
*****we*** than the people we actually know? Is the problematization of
the rigid use of \"we\"s a cultural generation, as well as the potential
of situated revisable collective declarations of being implicated,
enmeshed or engaged? What are the implications of limiting the use of
\"we\" in collaborative processes?
\- If the entanglement of persons, collectives, infrastructures,
institutions and geopgraphies produce temporary structures for work,
what are th**e degrees of freedom** of such structures? (in other words:
does this affirmation apply?: \"it can not be that when one person is
missing the whole system collapses\"
\- Did Iterations provoke a comment/render/actualisation on the notion
of **the tyranny of structurelessness** proposed years ago by Jo
Freeman? <https://www.jofreeman.com/joreen/tyranny.htm>
*Handle number 3: material conditions of possibility (on how matters of
measurement, situatedness, location, age, social class, funding,
geopolitics, linguistics, communication skills, queerness,
racialization, ability, sustainability and/or any differential degree in
relation to the \"normal\" -not necessarily from an identitarist lens-
affect collective works)*:
\- On engagement, affordances and **material conditions of
possibility:** Who can afford what, where, why and for how long? what is
it that makes certain works -and not others- materially possible?
\- What are the **units of measurement for a *****successful***
**entanglement?** And how to not read this question with a
*goodist/*benevolent tone? // this one would require a playful/creative
problematisation of the notion of \"sucess\" to then stay with it for a
consideration of its indicators and/or units of measurements at a
collective scale (knowing that this collectivity will for sure be
mutant, changing sizes-places and also never taken for granted)
\- \"**How to co-exist, basically**\" How could explicit or implicit
how-to\'s that were unfolded during all the meetings, exchanges and
inbetweens be shared for others to try and/or modify? // this one comes
with a focus on formats.
*Handle number 4: on the moments of transformation, change and mutation:
handing over, transitional zones, trouble making, etc.*
\- **generative troublematics**: how to make of trouble a generative
force? // this option would imply to both provisionally
define/understand what troble is (or not) in cultural creations, and to
list and unfold its generative forces/potential.
\- **transitional zones**:  there have been lots of iterations before
the project, and hence this one happens as one more transitional zone //
What does ot mean, to put the focus on the transitional momentum of
cultural practices, extitutions, the landscape of contemporary
collaboration with the subjectivities and projectualities that emerged
with the combination of EU projects and low cost travelling\... and so
on and so forth?
\- **handover hanging**: \"you should take it but you don\'t have to use
it\": what is to handover? Are there any thoughts provoked when
considering the practice of collective response-ability?
**- partial reparation**: what are the needed transformations to change,
mutate, let go and/or transition by making troubles in order to
partially repaire the micro, meso and macro structures that produce our
\"we\"s?
## Annotation tools
`<<iterations documentation from where the contributions were extracted>handles> annotation tools <contributions<handles>>`
\*1 curves \
\*2 colors \
\*3 text \
\*4 visual traces
first, making markers:
* **curves**: What curves are used in this contribution to engage with \*1?
* **colors**: What colors/color-combinations are used in this contribution to engage with \*2?
* **text**: Which anecdotes OR questions OR vocabulary/glossary OR tags in this contribution engage with \*3?
* **visual traces**: Which visual traces can we "cut out" from this contribution, that engage with \*4?
then, annotate: insert/inject markers throughout the publication

BIN
__pycache__/create_index.cpython-37.pyc

Binary file not shown.

BIN
__pycache__/functions.cpython-37.pyc

Binary file not shown.

BIN
__pycache__/readings.cpython-37.pyc

Binary file not shown.

BIN
__pycache__/text.cpython-37.pyc

Binary file not shown.

BIN
__pycache__/tfidf.cpython-37.pyc

Binary file not shown.

0
colors.json

0
curves.json

38
functions.py

@ -0,0 +1,38 @@
import json
# different useful functions
# line = '-----------'
# def write_to_file(filename, string):
# path = './'+filename
# f = open(path,'a+')
# f.write(string)
# f.write('\n')
# if 'mark' in filename:
# f.write(line)
# f.write('\n')
# f.close()
def load_db(filename):
try:
open(filename,'r')
except:
open(filename,'w+')
f = open(filename,'r').read()
if f == '':
f = '{}'
db = json.loads(f)
# find last_id used in db
keys = [int(x) for x in db.keys()]
keys.sort()
if len(keys) == 0:
x = 0
else:
x = int(keys[-1])
return db, x
def write_db(filename, db):
f = open(filename, 'w')
f.write(json.dumps(db, indent=4))
f.close()

131
readings.py

@ -0,0 +1,131 @@
import os, json
from flask import Markup
import tfidf
from nltk import tokenize, pos_tag, RegexpTokenizer
tokenizer = RegexpTokenizer(r'\w+') # initialize tokenizer
import pprint
pp = pprint.PrettyPrinter(indent=4)
def load_index():
if os.path.isfile('index.json') == False:
tfidf.create_index()
f = open('index.json').read()
index = json.loads(f)
return index
def get_random(x, y):
from random import randint
return randint(x, y)
def generate_random_rgb():
r = get_random(0, 255)
g = get_random(0, 255)
b = get_random(0, 255)
return r, g, b
def get_pos():
# ---
# Note: NLTK provides documentation for each tag,
# which can be queried using the tag, e.g.
# nltk.help.upenn_tagset('RB'), or a regular expression,
# e.g. nltk.help.upenn_tagset('NN.*'). Some corpora
# have README files with tagset documentation,
# see nltk.corpus.???.readme(), substituting in the name
# of the corpus. -- http://www.nltk.org/book/ch05.html
# ---
# data {
# 'word' : {
# 'count' : 8,
# 'sentences' : {
# 'filename' : [
# 'This is a sentence.',
# 'This is another sentence.'
# ]
# }
# }
# }
index = load_index()
sentences_all = [index[document]['sentences'] for document, _ in index.items()]
data = {}
data['ADJ'] = {}
data['PRE'] = {}
filenames = [filename for filename, _ in index.items()]
# print(filenames)
for i, sentences in enumerate(sentences_all):
r, g, b = generate_random_rgb()
for sentence in sentences:
pos = pos_tag(tokenizer.tokenize(sentence))
# print(pos)
for word, tag in pos:
if 'JJ' in tag:
# ---
# JJ: adjective or numeral, ordinal
# For example: third ill-mannered pre-war regrettable oiled calamitous first separable
# ectoplasmic battery-powered participatory fourth still-to-be-named
# multilingual multi-disciplinary ...
if word.lower() not in data['ADJ']:
data['ADJ'][word.lower()] = {}
if 'sentences' not in data['ADJ'][word.lower()]:
data['ADJ'][word.lower()]['sentences'] = {}
if filenames[i] not in data['ADJ'][word.lower()].keys():
data['ADJ'][word.lower()]['sentences'][filenames[i]] = []
s = Markup(sentence.replace(word, '<strong class="query" style="color:rgba({r},{g},{b},1); background-image: radial-gradient(ellipse, rgba({r},{g},{b},0.4), rgba({r},{g},{b},0.2), transparent, transparent);">{word}</strong>'.format(r=r, b=b, g=g, word=word)))
if s not in data['ADJ'][word.lower()]['sentences'][filenames[i]]:
data['ADJ'][word.lower()]['sentences'][filenames[i]].append(s)
if 'TO' in tag or 'IN' in tag:
# ---
# TO: "to" as preposition (voorzetsel) or infinitive marker (oneindige beïnvloeder?)
# For example: to
# ---
# IN: preposition or conjunction (voegwoord, samenstelling, verbinding), subordinating (ondergeschikt)
# For example: astride among uppon whether out inside pro despite on by throughout
# below within for towards near behind atop around if like until below
# next into if beside ...
# ---
if word.lower() not in data['PRE']:
data['PRE'][word.lower()] = {}
if 'sentences' not in data['PRE'][word.lower()]:
data['PRE'][word.lower()]['sentences'] = {}
if filenames[i] not in data['PRE'][word.lower()]['sentences'].keys():
data['PRE'][word.lower()]['sentences'][filenames[i]] = []
s = Markup(sentence.replace(word, '<strong class="query" style="color:rgba({r},{g},{b},1); background-image: radial-gradient(ellipse, rgba({r},{g},{b},0.4), rgba({r},{g},{b},0.2), transparent, transparent);">{word}</strong>'.format(r=r, b=b, g=g, word=word)))
if s not in data['PRE'][word.lower()]['sentences'][filenames[i]]:
data['PRE'][word.lower()]['sentences'][filenames[i]].append(s)
# count number of results for each word
for word_type, words in data.items():
for word, _ in words.items():
# print(filenames)
count = 0
for filename, sentences in data[word_type][word]['sentences'].items():
# print(filename)
count += len(sentences)
data[word_type][word.lower()]['count'] = count
count = 0
data_sorted = {}
for word_type, words in data.items():
tmp = []
for word, _ in words.items():
count = data[word_type][word.lower()]['count']
tmp.append([count, word])
i = 0
tmp.sort(reverse=True)
print('tmp', tmp)
for count, word in tmp:
if word_type not in data_sorted:
data_sorted[word_type] = {}
data_sorted[word_type][i] = {}
data_sorted[word_type][i][word.lower()] = data[word_type][word.lower()]
i += 1
print(data_sorted)
return data_sorted, index
# data, index = get_pos()
# pp.pprint(data)

148
start.py

@ -0,0 +1,148 @@
#!/usr/bin/env python3
import sys, os
import flask
from flask import request, redirect, url_for
from werkzeug.utils import secure_filename
from functions import *
import readings
# import create_index
import tfidf
from flaskext.markdown import Markdown
# Upload settings
UPLOAD_FOLDER_TRACES = 'static/visual-traces/'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
# Create the application.
APP = flask.Flask(__name__)
APP.config['UPLOAD_FOLDER_TRACES'] = UPLOAD_FOLDER_TRACES
Markdown(APP)
tools = [
"curves",
"color",
"text",
"visual-traces"
]
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@APP.route('/', methods=['GET'])
def index():
return flask.render_template('index.html', tools=tools)
@APP.route('/text/', methods=['GET', 'POST'])
def text():
marker = request.args.get('marker', '').strip()
markertype = request.args.get('marker-type', '').strip()
db, x = load_db('text.json')
markers = [db[x]['marker'] for x in db.keys()]
if marker:
if marker not in markers:
x = str(x + 1)
db[x] = {}
db[x]['marker'] = marker
db[x]['type'] = markertype
write_db('text.json', db)
markers.append(marker)
return flask.render_template('text.html', tools=tools, tool='text', db=db)
@APP.route('/color/', methods=['GET', 'POST'])
def color():
color = request.args.get('color', '').strip()
db, x = load_db('colors.json')
colors = [db[x]['color'] for x in db.keys()]
if color:
if color not in colors:
x = str(x + 1)
db[x] = {}
db[x]['color'] = color
write_db('colors.json', db)
colors.append(color)
return flask.render_template('color.html', tools=tools, tool='color',db=db)
@APP.route('/curves/', methods=['GET', 'POST'])
def curves():
curve = request.args.get('curve', '').strip()
db, x = load_db('curves.json')
curves = [db[x]['curve'] for x in db.keys()]
if curve:
if curve not in curves:
x = str(x + 1)
filename = '{}.svg'.format(x)
db[x] = {}
db[x]['curve'] = curve
db[x]['filename'] = filename
write_db('curves.json', db)
curves.append(curve)
with open('static/curves/{}'.format(filename), 'w+') as f:
f.write(curve)
return flask.render_template('curves.html', tools=tools, tool='curves', db=db)
@APP.route('/visual-traces/', methods=['GET', 'POST'])
def traces():
db, x = load_db('visual-traces.json')
traces = [db[x]['trace'] for x in db.keys()]
if request.method == 'POST':
if 'trace' not in request.files:
return redirect(request.url)
trace = request.files['trace']
if trace.filename == '':
return redirect(request.url)
if trace and allowed_file(trace.filename):
filename = secure_filename(trace.filename)
trace.save(os.path.join(APP.config['UPLOAD_FOLDER_TRACES'], filename))
if filename not in traces:
x = str(x + 1)
db[x] = {}
db[x]['trace'] = filename
write_db('visual-traces.json', db)
traces.append(filename)
return flask.render_template('visual-traces.html', tools=tools, tool='visual-traces', db=db)
from flask import send_from_directory
@APP.route('/visual-traces/<filename>')
def uploaded_file(filename):
return send_from_directory(APP.config['UPLOAD_FOLDER_TRACES'], filename)
@APP.route('/text-scans/', methods=['GET', 'POST'])
def textscan():
results = {}
data, index = readings.get_pos()
return flask.render_template('text-scans.html', tools=tools, data=data, index=index, results=results)
@APP.route('/text-scans/<word_type>/<word>', methods=['GET', 'POST'])
def textscanresults(word_type, word):
data, index = readings.get_pos()
for ranking, words in data[word_type].items():
for w in words.keys():
if w == word:
results = data[word_type][ranking][word.lower()]
filenames = [document for document, _ in index.items()]
return flask.render_template('text-scans.html', tools=tools, data=data, results=results, filenames=filenames, word=word, word_type=word_type)
# @APP.route('/text-specifics/', methods=['GET', 'POST'])
# def specifics():
# index = readings.load_index()
# return flask.render_template('text-specifics.html', tools=tools, index=index)
if __name__ == '__main__':
APP.debug=True
APP.run(port=5000)

2
static/contributions/BACK.md

@ -0,0 +1,2 @@
# (backcover) {.hidden}

1
static/contributions/COLOPHON.md

@ -0,0 +1 @@
# COLOPHON {.hidden}

3
static/contributions/COVER.md

@ -0,0 +1,3 @@
# Iterations {.hidden}
![](images/cover.png)

0
static/contributions/EMPTY.md

34
static/contributions/INDEX.md

@ -0,0 +1,34 @@
# INDEX {.hidden}
## curves
|marker|description|used on page|
|------|-----------|------------|
|<svg class="curve" width="640" height="480" pointer-events="all"><path id="ctrlpoly" d="" stroke="none" fill="none"></path> <path id="spline" d="M163 338.1333312988281C166.3390650589426 377.33764866760094 185.8241288302834 419.2715210141457 223 436.1333312988281C290.8955766536773 466.92863741221936 366.4172533672779 390.7910761939944 405 331.1333312988281C405.6666666666667 284.46666463216144 406.3333333333333 237.79999796549478 407 191.13333129882812C359.3333333333333 170.46666463216147 311.66666666666663 149.79999796549478 264 129.13333129882812C250.33333333333334 161.13333129882812 236.66666666666666 193.13333129882812 223 225.13333129882812" stroke="black" fill="none" stroke-width="2"></path> <path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path> <g id="handles"><g class="handle" transform="translate(163 338.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(223 436.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(405 331.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(407 191.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(264 129.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(223 225.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="4.713153967092374" y2="-11.035677581484583"></line><circle r="3" class="tanhandle computed" cx="5.891442458865469" cy="-13.794596976855729"></circle></g></g> <g id="plots"></g></svg>|curved voices|14|
## colors
|marker|description|used on page|
|------|-----------|------------|
|<span class="color marker" style="background-color: yellow;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: red;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: red;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: red;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: red;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: blue;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: pink;"></span>|moments of friction|3|
|<span class="color marker" style="background-color: magenta;"></span>|moments of friction|3|
## text
|marker|description|used on page|
|------|-----------|------------|
|(tag) infrastructure|relating to the infrastructures used .....|5|
## visual traces
|marker|description|used on page|
|------|-----------|------------|
|![](markers/visual-trace-1-cut.png)|amplification of voice|14|
|![](markers/visual-trace-1-cut.png)|amplification of voice|14|
|![](markers/visual-trace-1-cut.png)|amplification of voice|14|

107
static/contributions/README.md

@ -0,0 +1,107 @@
# README {.hidden}
## Handles
\*
*Handle number 1: on the systematics of working together at a time (a
temporary togetherness):*
\- What are/imply the **systematics of working together** that need to
be established before a process is even set in motion (like the boot
system in Linux)?
\- How does the **temporality**/chronological evolution of a process
have a role in establishing *befores*, *durings* and *afters* for a
groupal endeavor? How do durabilities, recursions, circularities,
persistences, repetitions and legacies affect the systematics of the
process themselves? How does **recursiveness, feedbacklooping and
crooked circularity of collective practices** become systematic, in
order to hand over of problematics, sensibilities, methods, tools etc.?
*Handle number 2: on the value and problematization of using the pronoun
\"we\" while inhabiting shared structures*:
\- **Enmeshed and engaged**: What if the we can mean **a bigger
*****we*** than the people we actually know? Is the problematization of
the rigid use of \"we\"s a cultural generation, as well as the potential
of situated revisable collective declarations of being implicated,
enmeshed or engaged? What are the implications of limiting the use of
\"we\" in collaborative processes?
\- If the entanglement of persons, collectives, infrastructures,
institutions and geopgraphies produce temporary structures for work,
what are th**e degrees of freedom** of such structures? (in other words:
does this affirmation apply?: \"it can not be that when one person is
missing the whole system collapses\"
\- Did Iterations provoke a comment/render/actualisation on the notion
of **the tyranny of structurelessness** proposed years ago by Jo
Freeman? <https://www.jofreeman.com/joreen/tyranny.htm>
*Handle number 3: material conditions of possibility (on how matters of
measurement, situatedness, location, age, social class, funding,
geopolitics, linguistics, communication skills, queerness,
racialization, ability, sustainability and/or any differential degree in
relation to the \"normal\" -not necessarily from an identitarist lens-
affect collective works)*:
\- On engagement, affordances and **material conditions of
possibility:** Who can afford what, where, why and for how long? what is
it that makes certain works -and not others- materially possible?
\- What are the **units of measurement for a *****successful***
**entanglement?** And how to not read this question with a
*goodist/*benevolent tone? // this one would require a playful/creative
problematisation of the notion of \"sucess\" to then stay with it for a
consideration of its indicators and/or units of measurements at a
collective scale (knowing that this collectivity will for sure be
mutant, changing sizes-places and also never taken for granted)
\- \"**How to co-exist, basically**\" How could explicit or implicit
how-to\'s that were unfolded during all the meetings, exchanges and
inbetweens be shared for others to try and/or modify? // this one comes
with a focus on formats.
*Handle number 4: on the moments of transformation, change and mutation:
handing over, transitional zones, trouble making, etc.*
\- **generative troublematics**: how to make of trouble a generative
force? // this option would imply to both provisionally
define/understand what troble is (or not) in cultural creations, and to
list and unfold its generative forces/potential.
\- **transitional zones**:  there have been lots of iterations before
the project, and hence this one happens as one more transitional zone //
What does ot mean, to put the focus on the transitional momentum of
cultural practices, extitutions, the landscape of contemporary
collaboration with the subjectivities and projectualities that emerged
with the combination of EU projects and low cost travelling\... and so
on and so forth?
\- **handover hanging**: \"you should take it but you don\'t have to use
it\": what is to handover? Are there any thoughts provoked when
considering the practice of collective response-ability?
**- partial reparation**: what are the needed transformations to change,
mutate, let go and/or transition by making troubles in order to
partially repaire the micro, meso and macro structures that produce our
\"we\"s?
## Annotation tools
`<<iterations documentation from where the contributions were extracted>handles> annotation tools <contributions<handles>>`
\*1 curves \
\*2 colors \
\*3 text \
\*4 visual traces
first, making markers:
* **curves**: What curves are used in this contribution to engage with \*1?
* **colors**: What colors/color-combinations are used in this contribution to engage with \*2?
* **text**: Which anecdotes OR questions OR vocabulary/glossary OR tags in this contribution engage with \*3?
* **visual traces**: Which visual traces can we "cut out" from this contribution, that engage with \*4?
then, annotate: insert/inject markers throughout the publication

12
static/contributions/TOC.md

@ -0,0 +1,12 @@
# TOC {.hidden}
| |page|
|------|----|
|README|2|
|INTRODUCTION|2|
|Iteration #5|2|
|Rica Rickson|2|
|score esc|2|
|Collective Conditions: three documents|2|
|spideralex|2|
|INDEX|2|

585
static/contributions/collective-conditions.md

@ -0,0 +1,585 @@
Collective Conditions: three documents
======================================
This contribution consists of three documents which emerged from the
worksession *Collective Conditions*, an experiment with the generative
potential of socio-technical protocols such as codes of conduct,
complaint procedures, bug reports and copyleft licenses. Constant's
worksessions are temporary research labs, intensive collective
environments where different types of expertise can come into contact
with each other. To these otherwise-disciplined situations, artists,
software developers, theorists, activists and others are invited to
contribute.[^1]
*Collective Conditions* was activated by the work of trans\*feminist
collectives on ally-ship, non-violent communication, score-making,
anti-colonial and intersectional activism, but also by ways of doing
developed within Free Culture and Free, Libre and Open Source software.
Out of commitment to the socio-technical protocols that these
collectives propose to intervene in cultures of harassment, we wanted to
take serious the role they might play in the (different) imagination of
complex collectivities.
The need to formulate protocols is felt urgently within on-line
collectives, that operate on the intersection of social practice and
technological infrastructures and run up against the limits of
'openness' and 'freedom'. Protocols have a tendency to focus energy on
discursive processes, rather than building concrete skills and practices
within groups and across participants. This worksession therefore
introduced different modes of 'writing' to challenge, make stumble and
collide; that posed questions and problems. We experimented with
feminist tango, self-determined objects, translanguaging, Tango
Thermique, fragile community scores, micro-lectures and an acapella
proto-anarcho post-punk operetta featuring Queering Damage songs.
Participants shared various degrees of concern with the possibility that
we could be re-instating a 'new normal'. The figure of 'complex
collectivity' helped to articulate these concerns in a way that we could
imagine ally-ship as a non-equalizing form of togetherness. What
protocols would for example work for non-normative human constellations,
or collectives where participants with radically different needs,
backgrounds and agencies come together? What different needs do 'complex
collectives' have when they are the result of structural forces such as
laws, racism, technology, wars, austerity, queerphobia and ecological
conditions?
We also wondered about how our self-invented protocols might too easily
repeat law-like ways of doing. We felt we needed to pay attention to how
they are implemented, to not eventually reiterate the carceral logic of
victimization and punishment which we felt perpetuates and multiplies
harm. We made a start with working through non-divisive ways of dealing
with codes of conduct, complaints procedures, bug reports and copyleft
licenses, trying to go beyond ostracization and exclusion.
The three documents that follow respond each in their own way to the
complex challenge of collective conditions. The first document is the
***Manifesto of Cares*** which emerged as a response to a proposition by
Elodie Mugrefya and Olave Nduwanje, *Writing manifesto of rest/care*.
The text was partially written during the worksession, and finished in
the months after. This first version of the manifesto aims to reclaim
care as a critical practice, and "reaches out to people who haven\'t
thought about the social economical racial gendered implications of
cares, and is also for the people who wish to reinvigorate their cares
patterns.". The second document is a splash page for ***Bibliostrike***,
a situated version of the Bibliotecha local digital library set-up.
During the worksession, a version of Bibliothecha was installed together
with etherbox[^2]. The shared infrastructure was adjusted,
re-articulated and weaponized to join the picketline of the University
and college strikes in the UK. The third and last documents are related
to the ongoing discussions around the ***Constant Collaboration
Guidelines***[^3]. The guidelines were tested for the first time during
*Collective Conditions* and discussed before and after with several
groups. Constant committed to keep adjusting these guidelines; the
included document traces the different problems and questions that have
came up.
## Manifesto of cares
**[Commitment to Cares, Why to commit to care?]{.underline}**
We are a group of intersectional trans\*feminists coming together in the
context of Collective Conditions, a worksession organised by Constant,
in Brussels the winter of 2019. We gathered around the notion of cares
to further understand our relationship to cares in our shifting
contexts. We are concerned with the urgency of this moment because we
have noticed that many in positions of power are doing little to enact
cares. As for us, we are in positions where we can enact contextualized
cares and we want to revive our commitment towards cares as critical
practices. We need to talk about cares, not because they don't exist,
but because cares are not distributed equally, and because to 'take
care' can have multiple meanings depending on positions of privilege,
context and who and what we're dealing with. This manifesto is a demand
towards those subjects within institutional frameworks to reflect on
their relationship towards the giving, taking, receiving cares, to
question well worn patterns of cares, and how they can be re-inscribed.
This manifesto reaches out to people who haven\'t thought about the
social economical racial gendered implications of cares, and is also for
the people who wish to reinvigorate their cares patterns.
[**\
Caring** **requires**]{.underline}
- active concern, mindfulness and commitment towards all living and
non living entities.
- attention to entangled environments: physical, digital,
interpersonal, ecological, and those we share them with.
- attention towards individuals and communities.
- not presuming and asking for different needs.
- time and the understanding of time\'s multidimensional nature.
- actions, not expected services.
- deep listening.[^4]
- patience.
- courage and responsibility.
- multiple interpretations and possibilities.
- \... not knowing what caring might mean.
- to be taken seriously and to take seriously.
- Acknowledgment.
[]{.underline}
**[The state of cares]{.underline}**
[]{.underline}
Caring is:
- Caring is deeply entwined in power relations and not equally
distributed.
- Caring is currently hegemonic: some have the agency to impose a
constricting meaning and practice of cares, on behalf of others.
- Caring is subsumed within capitalist frameworks that create the
conditions for care workers to have less agency and not be
represented in what they are employed to do.
- Caring is in a false dichotomy of productive/reproductive labour.
- Caring is chronically undervalued.
- Caring is deeply entwined in institutional engagement or lack
thereof.
- Our society produces vacuums of cares in places where they are
needed.
- Caring is underdeveloped in institutional contexts.
- Caring is something that cannot be outsourced.
Caring is not a tool.\
Cares cannot be instrumentalised.\
Cares cannot be generalized.\
Cares cannot be constricted by normativity.\
Caring cannot be conditional.\
Cares are antimanifesto.
**[Outlook of cares]{.underline}**
[]{.underline}
If things stay this way ...
- the tyranny of self-help models of survival will undermine the
formation of solidarity and collective power.
- we\'ll be suspended in precarious modes of surviving which
capitalizes on our failures.
- caring will continue to be performed by those who are exploited by
contemporary slavery.
- slavery will continue to be reproduced as the insidious institution
that it has always been.
- the continued burning out of those who do the caring will exhaust
the possibilities of cares that still currently exist.
- there will be a proliferation of ego-centrist hedonists and hoarders
of goods, wealth, status and power.
- we risk to all become facsimiles of whiteness: greedy, extractive
and exploitative.
- the logics of extractivism will continue to intensify, spurring
man-made ecological disasters disproportionately impacting certain
geographies, geosystems and ecosystems.
- persons, groups, ecosystems, identities will continue to be
subjected to the violence of categorisation, victimization,
commodification and marginalisation.
- the end of humanity and the destruction of the ecosystems is
inevitable.
**[Envisioning Cares]{.underline}**
[]{.underline}
Challenging the current trajectory of cares, we demand now a future in
which ...
- all living and non living entities are acknowledged as valuable and
worthy of care.
- the emotional, physical, philosophical and social realities of all
living beings have abundant expressions.
- living matters will have space to discover their own agencies.
- trees, plants, lands, oceans, river, water, rocks, mountains are
acknowledged in reciprocal caring relationships
- there is commitment to the work of making ecologically conscious
decisions
- nation states, borders and advertisement will be abolished in the
service of equal opportunity for all living and non living entities.
- value is shaped in do-it-together (DIT) collective bottom-up modes.
- institutional, governmental, corporate agents of power will be
transformed into self-determined agencies.
- desire, love, sex, relationships and cares are liberated with modes
of enthusiastic consent and freedom to abstain.
- consent is not presumed and crucial to all relationships
- touch implies addressing consent
- cares are mutual, shared across groups, individuals, beings and
entities according to varying needs, skills, capacities and
possibilities.
- caring roles exist outside normalised power dynamics
- notions of cares continue to be discussed, to change and to be
adapted.
**[Crafts of Cares]{.underline}**
[]{.underline}
Possible practices:
- Cultivate curiosity; to care is to be curious, curious enough to
ask.
- Be humble, humble enough to abandon assumptions.
- Cultivate gentleness, gentle enough to be asked
- Move towards disassembled and loosely structured, interchangeable
meshwork of enacted communities.
- Acknowledge and deconstruct privileges.
- Be open about failures.
- Cultivating practices of abundance rather than hoarding.
- Value those who do the caring.
- Pay salaries to those who do the caring.
- Value / pay for restorative care to heal deficits of cares.
- Value/pay those who do domestic work.
- Value/pay those who do the child rearing.
- Value/pay those who do the teaching.
- Value/pay those who are the caretakers of spaces.
- Illegalise shareholder models and corporations.
- Denormalise existing structures and power relationships.
- Root power directly within communities to counter historic hoardings
of power.
- Multiply agencies for new interpretations of cares.
- Commit to activist, sensitive lives; nurturing and facilitating each
other\'s talents and dreams.
## B I B L I O S T R I K E
Welcome to the picket line and bibliostrike!
(╯°□°)╯︵ ┻━┻
A bibliostrike is a place to make acts of solidarity, dreaming,
counter-optimisation, futuring, past-ing and present-ing at the
picket-line.
It is a potential place for meeting on the picket line through
liberating texts from behind paywalls, walled gardens and isolated
libraries.
Why not, it is also a place to create your own documentation of your
greatest chats, hottest book recommendations and most subversive lines
of thought?
It is an invitation and the result of collective conditions with the
intention of (in)forming strategies of resistance, including with
respect to the (academic) publication industries.
As academics and their allies, we benefit from these neoliberal sites of
publication, as well as the open or liberated sites which are
illegalized, with great costs and personal risks to those who maintain
those infrastructures.
Bibliostrike is a re-orientation towards different infrastructures of
distribution that resist the many ways in which current
biblio-technological practices come to optimise knowledge resource
allocation to benefit a few.
For instance, the new business model of academic publishers is pivoting
towards "knowledge products" and "information analytics" based on the
capture of trends on publication "sharing" sites like Researchgate as
well as Mendeley and SSRN (the latter owned by Elsevier which now
defines itself as a tech company).
Bibliostrike allows you to access and distribute texts without the nasty
analytics, free of the bubbly promises of narrow minded predictions, and
exploitative business models. To do so you can use the link above. To
read texts, you can just use your browser, if you wish you can download
the resources, and if you want you can upload your own to share with
others on the picket-line. You can also curate your special shelves for
tech-outs, protests, and campaigns.
There is a legal regime which casts a long shadow over the practices we
promote here. "To be clear", the upload and download of materials that
fall under the copyright regime is not legal. For those of you for whom
this risk is concerning, we encourage you to inform yourself about the
different liberating struggles like copyfight, open access, free
licenses of distribution, the public domain and orphan books, among
others. These struggles come with many ways in which you can come to
resist neoliberal publication practices and industries within legal
boundaries.
Bibliotecha is a framework built by queer feminist activist groups which
see technical justice a inseparable part of social justice. It relies on
a microcomputer running open-source software to serve books over a local
wifi hotspot. Using the browser to connect to the library one can
retrieve or donate texts.
## Constant Collaboration Guidelines: Questions and problems
**26 September → 25 November 2019 / Constant\_V: Collaboration
Guidelines**
- Various comments, graffiti, corrections, appreciations, suggestions on the window of Constant\'s office.[^5]
`Look at the images of the comments on the window, read the texts and see if anything is important to implement in the guidelines.`
**\
08 → 16 November 2019 / worksession: Collective Conditions**
*Discussions on consent:*[^6]
- Repeating issues with/discussions on the need for making time for consent, assumptions of agency/control/empowerment and the ability to re-commit, continued consent or changing your mind. Different practices/genealogies of consent: as a notion used by activists and legislators. Implicit, explicit and associated consent.
`Insert a line about the variation of consent, a reminder that consent is not a given, but that it needs (re)affirmation, thinking about variation and evolution of consent (to be worked on).`
*From discussions on transformative justice + anti-carceral activism +\
Queering Damage:*[^7]
- The last part of the guidelines ('What if these Guidelines are not met?') does not convince anyone. Several discussions throughout the week on how to deal with this. If there are Guidelines, they need to address responsibility and consequences in some way? At the same time, we feel that we might unwillingly reproduce carceral politics (rules → punishment). But how to approach this? We understand such guidelines as performative documents, so what you write about consequences becomes performative in itself?
`Something in the last section on accountability.`
`- Rename the last bit “What if the guidelines are not met” to: “This is what we do”`
`- Turn away from ‘rules and punishment’ logic. This also might deal with the concern that people are instrumentalised to functionaries ...`
`- Insert the possibility to have listeners as a method.`
- Experiments with having every day two other 'listeners' (otherwise known as whisper-bots) in the room; volonteers (not: organisers) that amplify, deflect, gather or document signals of transgression. Interesting experience of de-centralisation and collectivised responsibility.
`See above!`
- Assuming that crime/harassment is not just other people\'s problems. Attempt to work with practices from Transformative Justice.[^8]
- Another discussion thread on the differences of 'calling out' vs. 'calling in': How to take care of someone who doesn't care?[^9]
- Concern about proceduralisation of guidelines, whereby people become functionaries and not part of the situation: who we are is entangled with how we see problems. Timing matters: processes for immediate action and safety, and ones for long term response. In general, understanding that guidelines cannot be (left) alone: it needs a lot more than precise formulations. Need for developing practices, shared experiences, continuous work.[^10]
*A Critical COC discussion:*
- Most COC we dealt with did not deal with class/economic differences (aside for maybe mentioning class as one of possible discrimination axis)
`Under : "Exchange information, experiences and knowledge,"inequalities created by class: absence / presence: We could insert an example , highlighting the fact that absence can be the result of economic/class differences.`
- Very few COCs are simple enough in text-density / language level / terminology used (at least to be widely accessible or offer way to catch-up and enter as an outsider)
- What is the space/time for checking-in/confirmations of commitments
`Mention the method of coming together and address everyone's involvement and commitment, practically like the moments we have each morning during worksessions.`
*Discussions on audio-visual gymnastics:*[^11]
- Relates to this part of the guideline: \'Do not share photographs or recordings on proprietary social networks unless explicit consent by all involved.\'
- How to not respond to surveillance capitalism by stopping to make and share pictures? How to keep abundance, pleasure, possibilities in documentation; what ways to become visible as collectives, especially when we refuse to be visible as identifiable individuals. Or is there no way out of the dominant regime of exploited visibility?
- Experiments with audio-visual gymnastics. Other imaging practices that re-orient cameras away from faces and ways to sollicit on-the-spot consent of participants, rather than as a general rule or consent beforehand. Protocols for sorting through images afterwards (**no immediate publishing).**
`Thinking about publishing (pictures, videos, ...) that has to be discussed, negotiated and debated, it's not immediate. For the short version somethig sloganesque can work: 'We endorse discursive publishing 'This paragraph can be inserted with some discussion into the long version of the guidelines after: “No immediate publishing”.`
- Also: watch out for discrimination/exclusion based on use of specific technologies: explain what is the problem with these structures (fb, pomme, \...) and how to or not relate this to the use of apps, un.st-a(ble)gram etc.
*Notes from the Modes d\'emploi tables / on arrival:*
- Conflating \"maternalise\" and \"paternalise\" is a problematic linguistic move because it is conceptually putting them at the same level in tems of patterns of oppression and violence; even though they have historically never been equal. So not to say that maternalising doesn\'t exist, but whether that it doesn\'t have the same weight than paternalising.
`Change the line on matronise and patronise with smth like this: “We pay attention to the way informations, experiences and knowledges are shared between us with “possible asymmetries”; need better sentence. Maternalising and paternalisation could be examples, such as now there is teacher /student?`
- These guidelines are very good at creating a sense of community, common sense and will but we have to be careful that if someone doesn\'t comply to it, this person actually falls out of the "community".
- It is good to know that for instance here Constant people are mindful towards the compliance of these guidelines but a good way to involve participants so to share the responsibility towards each other would be to decide on a rotating team of participants who are not only very mindful during exchanges but are also the persons to go to if something went wrong in regards to the guidelines.
`Let's find a wording that doesn't fit within constraints or “not to do” vocabulary, so not using transgression for instance. Example of using extra-legal in the context of Authors of the future so not to go into il-legality but instead referring to another frame of legality.`
**8 November 2019 / HP**
- I would suggest using "informed by" or "found useful" rather than "inspired by" because of the content of the text, somehow "inspired by" for me feels a bit uncomfortable and I think its because of the affective qualities of the word inspire (which include excite) and maybe because so often (and so wrongly) harassment complaints become fetishised as \"exciting\" in complaint processes.
`Yes, let's change it!`
**14 October 2019 / General Assembly Constant**
- It might help people understand what Constant is and does; a kind of manual for those who do not already know.
`But we're not trying to make Constant more “intelligible” with the guidelines, this is not the point...`
- To 'come closer' to Constant what takes time. This is a social process, and guidelines might nog help or be in the way even.
**26 September 2019 / vernissage Constant\_V: Collaboration Guidelines**
- When writing dossiers, we try to avoid negative statements and negative language. Is there a way to approach the guidelines with a similar attitude?
`The principle is interesting, we can do a scan of the guidelines and see what turns up. Let's discuss the two examples that are annotated. Just want to be careful with sugar coating language avoiding negativity (joy and freedom for instance); we want to take a stance.`
This would give things like:
Constant is committed to environments where possible futures, complex
collectivities and desired technologies can be experimented without
fear. → *Constant is committed to environments where possible futures,
complex collectivities and desired technologies can be experimented
[with joy and freedom]{.underline}.*
The spaces that we initiate are therefore explicitly opposed to sexism,
racism, queerphobia, ableism and other kinds of hatefulness. → *The
spaces that we initiate are therefore explicitly [inviting everything
but]{.underline} sexism, racism, queerphobia, ableism and other kinds of
hatefulness.*
Because of the intensity of exchanges and interactions during
worksessions, there are moments of disagreement and discomfort which are
not to be avoided at any cost, but instead need to be acknowledged and
discussed within the limits of your own safety and sanity. → *Because of
the intensity of exchanges and interactions during worksessions, there
[can be]{.underline} moments of disagreement and discomfort which ~~are
not to be avoided at any cost, but instead~~ need to be acknowledged and
discussed within the limits of your own safety and sanity.*
Even if some of the below guidelines sound obvious, we have experienced
that being together is not always as self-evident as it might seem. →
*Even if some of the below guidelines sound obvious, we have experienced
that being together [can be more complicated]{.underline} as it might
seem.*
Don't speak for others. Do not intrude or impose yourself. → S*peak for
yourself only. Give/leave* (to be discussed: imply that someone has the
space - yes but it is the case,some ppl will occupy more space than
others for various reasons) *other people space.* (leave space for
others?)
Exchange information, experiences and knowledge. Don't patronise nor
matronise. → *Exchange information, experiences and knowledge. Others
always know as much as you, it might take more time to discover it.*
etc.
**20 September 2019 / Constant aan Zee: Discussion with members +\
Constant team**
- The point of guidelines is maybe to **de-naturalize behaviors.** Does this interfere with making space for precarity @ Constant?
- Proposal to **reorganise document**; for example: bring short version to the front before the \'commitment\' which contains some complicated language. In general: document is long and structure complex? Things get easily overlooked.
`For every occasion we have to look at the formatting. Clear and short if necessary, and long and informative when needed ...`
- **Some of the examples** (for example: \'dead names\' in the \'no harassment\'-section) are **hard to understand** if you are not already aware, so misses the point
- This might be a courageous step; to make such issues/guidelines explicit, given the current times.
- 'Support and foreground the use of Free Libre and Open Source software' sounds like a prerequisite. It should not be that someone who does not know about FLOSS already, cannot discover it with Constant.
- The 'we' in this document is ambiguous, on purpose. But how does that really work? Many questions about how to take **collective responsibility** for this, and where Constant is or should be (end)responsible.
- Are these some kind of house-rules? But where does the 'house' begin and end? **How to not make this territorial**? (maybe it is more a 'field' - ref. Kate Rich)
- List of hateful -isms: **add ageism**?
- Questions about tone: **imperative or not**? In general, multiple but different allergies surface to wordings/tone. Might need multiple versions for catholics, calvinists, anarchists, puritans, anti-imperialists, \...
- How to not forget that the **auto-restrictions** that this document might produce, also **generate possibilities for others** (ref. Jara Rocha)?
`Add in the intentions that the guidelines, with their ‘restrictions’, create possibilities for others (After "we have written these guidelines to think of ways ..."). hmmm. “the restrictions that these guidelines will produce for some of us, might create possibilities for others of us.”`
- "Do not share photographs or recordings on proprietary social networks unless explicit consent." (and following text in long version) is causing confusion. Why not consent forms? It is an attempt to make space for media/visual production on other terms (agendas not set by GAFAM[^12]), instead of minimizing situations where images can be made and shared. But: might be in conflict with GDPR[^13]? And might even be in conflict with FAL[^14]\... Does this mean Constant gallery needs a \'non-commercial\' license? Need to think this through more.
`Maybe one of us can find a bit of time to attack the regulations and try to understand what they would uimply for us .. and from there we could think of ways to collectively re- imagine 'data', 'protection', 'regulation' for example in a GDPR sprint?`
`Why not write a specific license for the Gallery? Is this possible? Fun? Is it compatible with the GDPR ? "You are free to use these images but we don't want you to share them on FB" :-) so more as a technopolitical statement then an enforceable regulation?`
- Proposal to add to short guidelines: "**accept the limits of your understanding"** \-- not everything can always be processed at the same time/level/speed by everyone (ref. Algolit)
`Why not write a specific license for the Gallery? Is this possible? Fun? Is it compatible with the GDPR ? "You are free to use these images but we don't want you to share them on FB" :-) so more as a technopolitical statement then an enforceable regulation?`
- One should be able to print this document from the website (print CSS needs attention)!
`There is no possibility to print the document at the moment, we're reporting it on the Gitlab issue tracker (DONE, look for 'print css'– ticket opened it already 11 months ago :-/)`
[^1]: Constant worksessions
[[http://media.constantvzw.org/wefts/103/]{.underline}](http://media.constantvzw.org/wefts/103/)
[^2]: Bibliotecha documentation and installation manual:
[[https://networksofonesown.varia.zone/Bibliotecha/index.html]{.underline}](https://networksofonesown.varia.zone/Bibliotecha/index.html)
etherbox documentation and installation manual:
[[https://networksofonesown.constantvzw.org/etherbox/manual.html]{.underline}](https://networksofonesown.constantvzw.org/etherbox/manual.html)
[^3]: Constant Collaboration Guidelines
[[http://media.constantvzw.org/wefts/123/]{.underline}](http://constantvzw.org/w/?u=http://media.constantvzw.org/wefts/123/)
[^4]: About deep listening: Earl E. Bakken, Center for Spirituality &
Healing. *Deep Listening*
[[https://www.csh.umn.edu/education/focus-areas/whole-systems-healing/leadership/deep-listening]{.underline}](https://www.csh.umn.edu/education/focus-areas/whole-systems-healing/leadership/deep-listening)
[^5]: Images from the comments chalked on the Constant window
[[https://gallery.constantvzw.org/index.php/Collaboration\_Guidelines]{.underline}](https://gallery.constantvzw.org/index.php/Collaboration_Guidelines)
[^6]: [[http://constantvzw.org/collectiveconditions/no\_u\_in\_dump/etherdump/thepadis.diff.html]{.underline}](http://constantvzw.org/collectiveconditions/no_u_in_dump/etherdump/thepadis.diff.html)
[^7]: Queering Damage
[[https://queeringdamage.hangar.org]{.underline}](https://queeringdamage.hangar.org/)
[^8]: Transformative Justice is a set of D.I.Y and anti-authoritave
methods and tools for engaging someone who has perpetuated harm in
an accountability process.
[[https://supportny.org/transformative-justice/]{.underline}](https://supportny.org/transformative-justice/)
[^9]: Notes from *Collective Conditions*, on Calling Out and Calling In:
[[http://constantvzw.org/collectiveconditions/no\_u\_in\_dump/etherdump/keepinout.diff.htm]{.underline}](http://constantvzw.org/collectiveconditions/no_u_in_dump/etherdump/keepinout.diff.html)
[^10]: Notes from *Collective Conditions*, on implementation:
[[http://constantvzw.org/collectiveconditions/no\_u\_in\_dump/etherdump/consequences.diff.html]{.underline}](http://constantvzw.org/collectiveconditions/no_u_in_dump/etherdump/consequences.diff.html)
[^11]: Notes from *Collective Conditions*, on Audiovisual Gymnastics:
[[http://constantvzw.org/collectiveconditions/no\_u\_in\_dump/etherdump/pictures\_recordings.diff.html]{.underline}](http://constantvzw.org/collectiveconditions/no_u_in_dump/etherdump/pictures_recordings.diff.html)
and
[[http://constantvzw.org/collectiveconditions/no\_u\_in\_dump/etherdump/audio-visual-gymnastics.diff.html]{.underline}](http://constantvzw.org/collectiveconditions/no_u_in_dump/etherdump/audio-visual-gymnastics.diff.html)
[^12]: GAFAM: Google Amazon Facebook Microsoft
[^13]: GDPR: General Data ProtectionRegulation
[^14]: FAL: Free Art License
[[http://artlibre.org/licence/lal/en/]{.underline}](http://artlibre.org/licence/lal/en/)
All documentation, including the documents in this publication, can
be found here: \[include link\]

90
static/contributions/collective-intro-for-publication.md

@ -0,0 +1,90 @@
# Iterations ... {.inline}
Iterations investigates the future of artistic collaboration in a digital context. That tag line was used throughout the four years that the project ran. The basic concept was to 'iterate' an action in several similar, but fundamentally different circumstances.
Inspired by recursive forms of collaboration as they exists in open source software development, the project Iterations applies repetition and circularity to artistic methodologies, in which the output from one activity is used as the input to the next.
The project Iterations consisted of a series of residencies, exhibitions, meetings, exchanges and collective worksessions that were organised between 2017 and 2020. Each activity produced material given to the next iteration. As such the series experimented with collective creation, authorship and conditions, and each presentation was an open invitation for re-appr0priation. Materials, code, images, instructions and documentation are shared under open content licenses on the website of the project. Iterations invests in producing cultural commons.
Although Hangar, esc, Constant and Dyne all operate in the same field, they have different points of departure, missions and modes of operation. For this project we established common methodologies, which were implemented differently by each organisation.
In december 2017, a first colloquium took place in Hangar were some common outsets were discussed. In spring 2018 a group of 30+ artists, hackers, theater makers, musicians embarked for a shared residency to Giampilieri, a small village hidden between the sea and the mountain in the metropolitan area of Messina, Sicily. This Trasformatorio residency was organised by Dyne.org. The works produced turing the residency were festivily presented in the village. As material to be remixed by the artists who joined for the next residency, a song was selected that was sung in a hardly understandable, Sicilian dialect. With this handover, the challenge of multilinguism was introduced. In the residency that followed in Barcelona in November 2018, the work concentrated on forming a collective persona. A collective artistic identity emerged with the name Rica Rickson. The outcome, one rule: any of the residency artsts can inhabit her. In May 2019, several presentations and meetings later, a group of artists worked together under the name 'Common ground' on creating a site-specific installation for the exhibition Collaboration Contamination in the spaces of esc. When you work together, you will be influenced by the people you work with. Over the summer of 2019 two residencies were organised in Brussels resulting in the exhibition Operating / Exploitation in Bozar-Lab in October immediately followed by the worksession Collective Conditions.
# ... Investigates ... {.inline}
Let's look closer at the words in that sentence 'investigate the future of artistic collaboration in digital contexts'. To start with investigation. The means of inquiry that were developed were led by the practice of doing art together. Allthough Iterations followed a described methodology, the way we investigate is multiple: led by 'intuition', affinities, impulses and less 'Re-search' (searching again for what we know is already there, to prove a thesis). Is this then what we could call 'experiment' ? That hase connotations of: fooling around without a goal, but also: suspicious lab work that exploits living bodies for the sake of science. The artists as lab rats in a white cube test tube? That is far from what we were after. But is any collaboration free of friction? Are the conflicts product of the artificial setting of putting different artists together to coexist or on the nature itself of the collaboration? does collective productions exist without these difficulties?
Inquiring led by the practice of doing art together. Experimenting as fooling around with no goal. Doing art together with no goal. Doing art together. Inquiring, experimenting, fooling around, with no goal. No goal, or the goal was no other than the set of conditions: being together, doing together, doing art, doing art in a way that iterates a previous attempt, and that can be iterated in a future attempt. No goal, or the goal was the reconsider again and again the set of conditions. How often we took a step back, or we set the conditions back to the starting point, to try again! We did that and are doing that in every iteration, but also inside every iteration, but also in the relation of this project and the previous and future one. The iterating method is working in different scales. To step back, reconsider conditions, reset them, and try again.
One thing we are certain about is that the goal of the investigation is not the art piece. It might be the art labor, or the art process, or the experience of togetherness,not certain about this though.
# ... the Future of ... {.inline}
Starting a rule based Iterations creates new futures. They are plural. And they have repurcusions on the here and now, quantum presences and diffractions, ... \
Well maybe that is close to what we hope artistic can do: change perspectives and bring new orientations to rusted concepts? \
A small deterioration has immediate effects: the roadmap can go in the bin. it is agile roadbuilding into the vast unmapped territory of having no markers.It can also construct empty spaces to be reconstructed over and over. \
Our collective national politics are fucked up by neo-liberal turbocapitalists, and maybe it is good to be in a (carbon neutral) car where every passenger has their own steering wheel. We do not know how to go straight, but we learn to negotiate with materials that matter. Future is made collectively or it will not be. An this collectivity needs communication with other species, the activation of a post-human landscape.
Future not as a promising and well balanced picture that we can offer as the output of this project. Sorry about that. Instead a plural and diffractive future that has consequences in the here and now. A diffractive future in the here and now. Everyone in this car has a steering wheel, but this is only one car, and we are together in it. The future here and now, so not a future separated from us. There is not a waiting period before this future, no open gap available for hope or dispair. Instead a material future, so one future we can touch, made of attempts that diffract and overlay in front of our eyes, departing from our hands.
# ... Artistic Collaboration ... {.inline}
Is the collaboration artistic, or is the artistic collaborative ? Both (and more) hopefully. A more internal motivation that is proper to our type of organisations is that we have witnessed a rigid underexposure of collective practice in presentation of contemorary art. We want to change this around and highlight the fact that much artistic development of concepts, tools, methods and approaches is done collectively. Esc, Constant, Dyne and Hangar are close to hacksessions and we saw that individual practices spring from these shared situations are not always credited properly when presented.
So one starting point was to change the paradigm and put collective work as a precondition for participation. And as a header on the output. An attempt to dilute individuality, to create bodies in alliance.
... Commoning and its forms as premise.
... Nevertheless, seems that it is hard to go out of the normal group dynamics in such a short experimental time, so those with strong voices keep the drive, that was always a big question, how to keep the silent voices there, meaning, producing, weaving?
... The collaborative aspect of art labor, but not hidden under the name of the artist, but clearly stated and put on the surface, so it is visible and audible. The name of the artist and the title of the piece as the framing of the art work (work meaning labor). What happens when we take that out? Not clearly framed, the art piece is not well prepared to be seen and listened to, but on the other hand, it is very ready to be repurposed or continued, as a whole or in some of its parts. The communality offers another reading, an integral experience.
The handover meetings were the sharing of an ongoing art piece? This is not very clear at this point. They were the sharing of an ongoing process, but this process was not easily framed as an art piece, and when it was, that framing (the name of the artist and the title of the piece back into play) were a limit and a strong conditioning of the collaborative process that the handover meeting was suppose to trigger. The handover was a situation, a gesture to bridge, to give away some element of the lived experience of the iteration. The Handover was in everycase a metamorphosis, and a becoming action.Contrary to the expectation the exchange among artists was not of materials, but of ideas and experiences, sometimes of data.
We witnessed an increasing abundance of 'co' and 'col' prefixes. (With, together, joint)
And why did we bother to join 'labor' to the collective ? Is 'collective' not good enough? Is just being together not more desirable then thinking about work ? To collaborate you do not have to be friends, a common goal is enough. Sometimes just a common inspiration, but it is hard to arrive to the common ground. So why did we choose for collaboration? We were inspired by F/LOSS practices that include strategies to make it possible for people to contribute to projects they find important, even when they are not best friends. The Linux kernel, open licenses. A sort of hope for a possible jamming in the arts, a dissolution of ego within technology. But also thinking of the rights of the work, next to rights of the author. Shared work has the right to be disseminated, to have a life of its own, to be exposed to brilliant influences, benefit from the additions of others. One tool we tried from the outset is to apply Open Content licenses to all the work that was produced. That applies to this book as well. It is published under a Free Arts License (?) which means that you are free to copy, change and share it, as long as you make mention to the original authors.
In some key moments, the group involved in Iterations decided to challenge the collaboration and to try to go deeper into the collective. There were discussions about this in the Hangar iteration, for example. So the collaborative, where different people can work, labor, together without being friends, was surpassed toward the collective, where the separation between the different contributions is not clear anymore. This means that, in some moments, the development of the project challenged the decision made in the beginnig of using the word collaboration instead of collective action. The necessity of creating unified criteria is a hard demand, certainly generating common strings or media would have been more effective and less painful.
The prefix 'con-' was also important: in the course of the project, conflict became to be considered as a constructive element. Staying 'with' the trouble as a way to not let go, to make space for difficult relations, that are necessary for practices that want to embrace differences. Yes, belonging to, it is a difficult precondition to fulfill a goal, but it gives space for alliances. At the end to stand with, to be part, can also be a collective statement, an action of bodies in alliance. Corporalities that keep their individuality.
Iterations has created situations in which differences were encountered, discussed and transfered into work. In many cases the artists, organisers, and other parties that worked together met for the first time and had no previous experience in collaborating. Although not always easy, it is important that tensions and frictions can arise from these situations and that they are dealt with, creating productive differences that earn a central place in the common practice.
# ... in Digital ... {.inline}
When we mention 'digital' as a terrain for our operation, this includes all (im)possibilities of its use in all contexts. Iterations made a continous attempt to inquire in what 'digital' could be, and where it ends to be relevant. But the use of the media delivered a particular aesthetic, some fragmentary landscape that travelled across the Iterations, that is now present in this new attempt to consolidate collaborative approaches: the new experiment, the textual. Intertwining, creating membranes from disgregated elements.
Over the last decades, the 'digital' has shifted meaning. The early net induced the emergence of net art, critical cyborg identity and gender tech experiments, space for independent infrastructures. In our present, this hopeful potentiallity is overshadowed by techno colonial, big data correlating, energy slurping, all invasive commerce. 'Digital' therefore, can not remain un-annotated, but asks for critical positioning of artists. Very present in all Iterations was that artists prefered to close their laptops, and insisted to work on what is between them as humans instead of endorsing machines as a central tool.
The mediation of the digital in this project. The digital media(tion). The writing of this text is being done in a shared pad, to which we connect from three different (european!) cities. Our words come together through our computers, to a common digital ground, in a file that is stored, most probably, in a drive located somewhere in Brussels (perhaps in the very Constant office?). But we know each other, we like and need to come together, and we talk about location, coffee, about details that make our bodies more present in the writing. It seems fair to say that that was a key element of this project: we enjoy being together, phisically in the same place. This is perhaps a previous condition, previous to all the other ones.
Other participants in Iterations were more radical in this, as said before: they put the computers aside, thay wanted to be together, with the bodies together, and to share the time, the food, the temperatures, the sounds and silences, the energy and the fatigue. When this happened, the digital was perhaps no more than a metaphore. But this is a powerful metaphore: to collaborate as we do in the F/LOSS, to let things being taken appart, repurposed and continued as we do in the F/LOSS.
# ... Contexts ... {.inline}
Ecology, post truth times, musea and exhibitions vs. performatic actions, how are the different contexts influencing the iterations, their dynamics and their products. Ecology as big topic, the post-human as question, as method. A necessity of making kin, of understanding and completing, but why not, of contaminating. Collectives are also formed from intrusions, penetrations and alterations, transpositions or accidents.
As a relativistic concept, what focal event is 'context' depending on? And what (a)symmetries are involved in the position of the subject. Our work in culture is associated with many sectors of society, with tech innovation schemes, with eco(-logical -nomical)politics, it relates to futures we might not be able to imagine.
We think of the context in a way similar as how we think of the future. It is right here, very close, but it is also the diffractive and plural openness of the immediate things.
On another tone: our direct working surroundings, the physcial spaces in which the art workers involved in the project worked in are art centers, a rural area, private houses, and institutes. The context of an institutional exhibition requires very different specific dealings with digital means than a small village in Sicily.
.... A resistance toward how the digital media makes things unlocated and abstract. There is no cloud, but a bunch of drives and cables, as we learned through Joana Moll and other colleagues. But also the digital context, seen as metaphore but also as tool, offers the opportunity to get rid of some oppresive framings of the art process. \
... A work, a labor, that takes place here but will continue elsewhere, with this materiality but that can be transfered to another materiality, done by these people, but that can be continued by other people. The context was like this, concrete and dislocated at the same time. \
.... Who is here? Who is near us? Who is doing this with us? When is this happening? And where?
The dislocation, the intuitive bringing together of separated places, things and people, as a method of establishing a context.
Iterations **foregrounds collective work because we find that this is urgent** to do.
The internet has become an amazingly complex place and can be interpreted as a metaphor for the whole world - no individual is capable of grasping its totality. \
For art practitioners interested in technology, this gives reason to get together, to collectively understand, use, develop, or dismantle, the main infrastructure of our times. To take apart and hack together networks of our own, with an attitude, invent and run multiple internets and built futures that we would like to inhabit. \
We, early twentyfirst-centurists are living in the post-capitalist ruins that have become our collective home. We are happy to volunteer for doing some housekeeping, maintenance, restoration. This requires that we learn to work together, to negotiate and speak, to struggle and deal with those agencies who are undermining its fundaments.
As a collaboration between several European organisations **Iterations is co-funded by the Creative Europe framework of the European Commission**. Although the project springs from our shared conviction that collectivity in the arts needs investigation and attention, the project is also formatted around the criteria of the Creative Europe framework. There is a duality of potentially conflicting interests.
An example of this is the fact that we enter into a programme that supports "transnational cooperation projects from different countries", meaning that we endorse a paradigm of increased mobility. Whether we choose to meet and work remotely through online tools, or we travel by train our airplane, our actions are increasing carbon footprints. \
Another example of how our work is influenced through such a framework is that the geographical delimitation to European borders is not self evident for our organisations, in other situations we work with partners from outside the EU. \
Staying inside the scope of a funding programme without underexposing sides of the work that are less compatible with that programme is interesting cultural-political gymnastic exercise. If a shared long term vector between us is to speed up the end of capitalism, then how are projects such as Iterations benificial to that? Rather then stating this as an explicit part of its mission, it is making an effort by creating space and opportunity for critical art practices to develop. Stimulating awareness, sensitivity, intuition for the politics of tools and technologies, through bringing about togetherness and exchange.
**This publication is not**: a catalogue, complete, a documentation. Rather it aims to create new openings by separating lines of work through a prism that consist of several editorial handles. Approaches and attitudes that give inspiration for new concepts and works yet to be developed. This publication is an Iteration in itself. Inside this book, contributions of some protagonists from the past and Iterators from the future coincide in a joint effort to transport ideas to yet unkown persons and contexts. You, reader might be part of this. The book is a hybrid vessel of ideas, paper, texts and concepts that can be unpacked and remixed.
Thank you to: everyone who participated in the Iterations activities.
(Full list of names! needs to be reviewed)
https://pad.constantvzw.org/p/iterations-participants

132
static/contributions/holding-spell.md

@ -0,0 +1,132 @@
# holding spell {.hidden}
we share a rich vocabulary; us. \
we can speak of \
broad skies and magnitudes, \
the sound of a blown voice, \
through a horn softly \
on a hill far away, \
of magnetite swathes \
and sparks so sharp, \
they can be felt by grazing a little finger. \
we can speak of things that \
when we don’t feel strong, we think about \
and of things that \
when we want to feel strong, we think about. \
little animals, small and quiet and shivering \
caressed but \
suspended \
by the tips of whiskers only air. \
when we speak, noses twitching \
it can be of \
separateness, discreteness \
the spaces between circles \
overlapped, but still holding \
we can multiply interpretations, \
and brace against their weight \
or \
we can scoop up abundance \
and bathe lightly. \
throats, quivering, sound like cold or terror \
but they too ask, whether the circles \
are organisms micro-vibrating \
with the energy it takes \
to realise squelchy new forms \
to absorb: experience grows the self grows autonomy \
by care and love into the blood. \
our expanding throats \
stickily coated with the outside, \
wonder \
whether us-as-earth, will shift magnetic poles again \
as we did recently \
780,000 years ago \
well, not within my living memory but \
maybe within ours. \
it’s comforting that a polar shift might mean no more \
to glottises \
than huffing in the wet brown mulch from the forest floor; \
yelling in bluebells until we laugh \
is anyway more accessible and soothing. \
to you, the piece of my bruised heart: \
that bright lodged glint \
is your intelligence \
equal to all winds it bends against \
think of it like a tinder mushroom - \
cupped, it can keep you warm \
and light many fires along the way \
when we loudly open ourselves without \
insides \
the world rushes in \
unbidden even \
but making calm salve \
by making ourselves space \
avoids projection. \
when we speak, let’s not speak \
about identity \
no -isms or -nesses, \
just about sheltering \
the trembling creatures, we are \
just about protecting \
the vulnerability in our voices. \
that belief, in self-knowledge \
might even allow \
ample vibrant exchange, iron fizzing, \
something like a conversation between \
million year old magnetic traces found inside a tenth of a millimetre of rock \
and a magnetometer \
in the key of hysteresis \
this vocabulary we share, \
made of hard and soft sounds \
of silences \
and recurrences \
(i won’t lie) \
has lashed at times, \
and whipped us round \
but it has also gifted us \
cycles of growth \
and non-linear narratives \
comfortably spiky creations \
and exuberant wide worlds; \
so now \
when we speak, intertwined, \
i’m grateful for all those words \
that bind and support us \
leaning back away from another, \
onto each other, \
overlapped but holding, still.
![](images/spell-screenshots/1.png)
![](images/spell-screenshots/2.png)
![](images/spell-screenshots/3.png)
![](images/spell-screenshots/4.png)
![](images/spell-screenshots/5.png)
![](images/spell-screenshots/6.png)
![](images/spell-screenshots/7.png)
![](images/spell-screenshots/8.png)
![](images/spell-screenshots/9.png)
![](images/spell-screenshots/10.png)
![](images/spell-screenshots/11.png)
![](images/spell-screenshots/12.png)
![](images/spell-screenshots/13.png)
![](images/spell-screenshots/14.png)

80
static/contributions/iteration-5.md

@ -0,0 +1,80 @@
# /opt {.hidden}
![](images/iteration-5-1-1.jpg)
![](images/iteration-5-1-2.jpg)
![](images/iteration-5-1-3.png)
![](images/iteration-5-1-4.jpg)
![](images/iteration-5-1-5.jpg)
![](images/iteration-5-2-1.jpg)
![](images/iteration-5-2-2.jpg)
![](images/iteration-5-2-3.jpg)
![](images/iteration-5-2-4.jpg)
![](images/iteration-5-2-5.jpg)
![](images/iteration-5-2-6.jpg)
![](images/iteration-5-2-7.jpg)
![](images/iteration-5-2-8.jpg)
# /var {.hidden}
![](images/iteration-5-3-1.jpg)
![](images/iteration-5-3-1.jpg)
# /boot /home /lost & found {.hidden}
Another story starts with some fruits made of almond-paste. Almondpaste
from Sicily was rolled between hands in Barcelona and Connie
chose to make a Nispero. \
Nispero is a spring fruit, and this was autumn, so when Iris and Rosa
searched for it in the shops, they ended up bringing plums home. Home
was the place were Iris was living together with Azahara and Giulia,
so plums were eaten in the morning for breakfast accompanied with
Antonia’s recently made coffee. Coffee was served while awaiting Julia,
coming with her son Victor, and later on Mafe proposed an exercise
that consisted of writing another story.
The story goes, Connie bought 4 nispero fruits in Austria where it is
called Wollmispelfrucht because it reminded her of one of the places
she used to live in the past. Past midnight Mia tried some for the first
time in her life, Ulla had some too and the baby in her belly, later known
as Mika, tried some for the first time pre-life as well. Well-formed, round
and smooth; Ulla found the seeds of the fruits surprisingly beautiful
and kept them in the communal kitchen, a small paper cup with them
all inside. Inside esc one evening, before going to drink schnapps with
Norbert, Connie had to video-call another collective she is in. In an
effort to distract her attention back to us, Antonia took two of the
seeds and spat them across the room, where Mia was standing, who
spat them back. Back at work the next day Connie gave these two seeds
to Mia who took them to Brussels where nispero is mostly called néfle
or loquat by the inhabitants. Inhabiting Brussels, Azahara, who knew
Connie and Antonia from a different collective, got one seed, the other
one took many weeks to germinate and springup. Up until now it has
grown, but it has not yet born any fruit other than this story.
Memory is a fragile thing, and it's hard to tell what a good reminder
is. Sometimes a simple smell is enough to bring you back to childhood,
sometimes a modest receipt evokes an entier day full of emotions.
What should be kept, as evidence or reminder of someone or some collective
working process ? How to catch the moments that shape a person or a group ? \
Usually, we take pictures of what we think on the spot is a good (future)
memory, or we write postcards to share it with friends. Doing so, we
usually avoid sad memory or moment of conflict, even though they are
important too (maybe even more). \
For example : how to remember when such a conflict starts ? Or the
breakpoint between a conflictual sitation that eventually lead to something
deeper and more destructive like a break of trust ? \
It's usually something you'd try to escape as a group, if not individually.
As it might lead to the end of the group itself, who cares to take a
picture then ?

394
static/contributions/rica-rickson.md

@ -0,0 +1,394 @@
# Rica Rickson {.hidden}
[I am here.]{.green}[\ I am also here.]{.red}[\ or there, or somewhere
else. it depends where I find myself at the moment.]{.green}[\ I am a
little bit nervous]{.red}[(tense?)]{.green
.char-style-override-1}[.Shy?]{.red}[\ Melancholic?]{.green}[\ In a
continuo]{.yellow}[u]{.green}[s hurry?]{.yellow}[\ asking myself, when do
we really have time for collective digestion of the events happening?
How can we really understand processes if we cannot stop?]{.pink}
[]{.yellow}
[I was cleaning my cradle this morning and I found this box full of
memories. On the top of the box there was a bingo board full of english
words:]{.green}
[questioning -- magic -- empowerment -- (un)selfishness --
transformation -- authorship -- staying with the problem --
misunderstanding -- improvisation -- singularities -- tension -- taking
care -- agency -- trust -- porosity -- open mindset -- collective --
collaborative -- effort -- joy -- dis/agreement -- respons-ability --
needs -- power-relations -- desires]{.green
.char-style-override-2}[]{.green .char-style-override-1}
[]{.green}
[In the box with the words I also found part of a message\ ]{.yellow}[I
had sent to Brussels:]{.red}[\ ]{.yellow}
[As I faced a lot of conflicts, uncertainties and questions after the
residency at Hangar, I would like to further continue our vivid
discussion about\ ]{.yellow
.char-style-override-3}[collectiveness]{.yellow .char-style-override-2}[
and to further reflect upon and share our experiences. I therefore
regard the worklab a constructive and interesting environment to
integrate with and a constructive opportunity to investigate more deeply
the potentials and\ ]{.yellow .char-style-override-3}[needs]{.yellow
.char-style-override-2}[\ of my continuously developing
organism.]{.yellow .char-style-override-3}
[]{.yellow .char-style-override-3}
[I was freshly born, when I felt like being thrown back in a mechanism,
where I emerged from.]{.violet}[]{.red}
[The individual parts grew together within an intensive reflection about
]{.red}[authorship]{.red .char-style-override-1}[,
]{.red}[questioning]{.red .char-style-override-1}[\ the difference
between\ ]{.red}[collaboration]{.red .char-style-override-1}[\ and
]{.red}[collectiveness]{.red .char-style-override-1}[,
]{.red}[questioning]{.red .char-style-override-1}[\ the market logics of
art production,\ ]{.red}[celebrating female\ ]{.yellow}[magic]{.yellow
.char-style-override-1}[.]{.yellow}[\ In an organic process of
mutual]{.red}[\ act of\ ]{.green}[taking care]{.red
.char-style-override-1}[, listening,]{.red}[\ ]{.blue}[trust]{.blue
.char-style-override-1}[,\ ]{.blue}[(]{.pink}[un]{.blue
.char-style-override-1}[)]{.pink}[selfishness]{.blue
.char-style-override-1}[\ etc. I wanted to create a body that protects
myself from the artworld outside.]{.red}[\ I thought, then it is my
decision, whether I care about\ ]{.violet}[authorship]{.violet
.char-style-override-1}[\ or not,]{.violet}[\ I thought, I am a we, a she,
a him]{.red}[\ (maybe less)]{.orange}[\ and finally an I, but not a
label.]{.red}[\ I thought, I can emancipate myself from
]{.blue}[power-relations]{.blue .char-style-override-1}[\ given by
institutions,]{.blue}[\ I am the body I live in.\ ]{.red}[(whereas an
institution is an uncorporeal body.)]{.orange}[\ I thought I could escape
from market forces, productivity strategies, neoliberalism.]{.red}[\ I
]{.yellow}[trusted]{.yellow .char-style-override-1}[, that I am my own
matrix]{.yellow}[\ but I am just a parasite, some uninvited organism that
]{.green}[needs]{.green .char-style-override-1}[\ its host to exist and
grow although my presence necessarily disturb its normal functioning. I
am grateful to this host which is necessary for my own survival and at
the same time I feel stuck in an environment whose forces go against my
]{.green}[desires]{.green .char-style-override-1}[\ and structural
dreams.]{.green}[\ I was freshly born, when I realized that I cannot
escape and that it does\ ]{.red}[and will]{.green}[\ affect me. That was a
slightly painful, disappointing conclusion, it destabilized my organism.
]{.red}[Who am I? May I present your work and what is its meaning? What
is your name?]{.orange}[\ How much is it? I would like to invite you, who
will come?]{.yellow}
[]{.yellow}
[I can resist,]{.red}[\ and I have to,]{.blue}[\ if I want to survive the
surrounding fragmenting forces.]{.red}[\ If I don't, I might be disolved
in the host, phagocited by the bigger organisms, end up being part of
the different cells forming this host.]{.green}
[]{.yellow}
[I have to stay with my often controversial\ ]{.red}[needs]{.red
.char-style-override-1}[, different temporalities,
]{.red}[misunderstanding]{.red .char-style-override-1}[s.
]{.red}[]{.yellow}
[Being myself in is a constant state of\ ]{.yellow}[tension]{.yellow
.char-style-override-1}[, that costs energy,\ ]{.yellow}[effort]{.yellow
.char-style-override-1}[,\ ]{.yellow}[agency]{.yellow
.char-style-override-1}[\ and commitment.]{.yellow}[\ I am my own
]{.green}[desire]{.green .char-style-override-1}[\ and my own
parasite]{.green}[. I am Frankenstein and Frankensteins Monster]{.blue}[
Mother.]{.green}
[]{.green}
[In the box with the words and the message I had sent to Brussels, I
found part of an email I had sent to Sicily:\ ]{.blue}
[Within the last days I have been reflecting upon different ways of
collaboration, starting from different concepts I was introduced to, for
example misunderstanding (between languages, different cultures) or the
"parasite-concept" asking who hosts whom etc.]{.blue
.char-style-override-3}
[\...]{.blue .char-style-override-3}
[I would be happy, if you would like to participate, which implicates,
that your name will not appear, as an individual author. Of course,
everything I am working on now, will also be open and collectively
shared.]{.blue .char-style-override-3}[]{.pink .char-style-override-3}
[]{.green}
[]{.green}
[In the morning, I was reading some articles about "identity in art
production"- very often they\ ]{.red}[- but who are they? -]{.green}[
authors :D - referred to "self-care", which means transforming personal
issues into artworks.]{.red}[\ And I thought about "the death of the
author" by Roland Barthes that I had just read again yesterday:
]{.green}
[Here is a quote about it that I found in a very well-know fabric-like
content website:\ ]{.orange}[In a well-known quotation, Barthes draws an
analogy between text and textiles, declaring that a "text is a tissue
\[or fabric\] of quotations," drawn from "innumerable centers of
culture," rather than from one, individual experience. The essential
meaning of a work depends on the impressions of the reader, rather than
the "passions" or "tastes" of the writer; "a text's unity lies not in
its origins," or its creator, "but in its destination," or its
audience.]{.orange .char-style-override-3}[]{.green}
[]{.green}
[How to\ ]{.pink}[connect,]{.green}[\ reconnect with myself?]{.pink}[
Myself are you hearing me?]{.yellow}
[Every collaborative / collective project is a switch that turns
on.]{.pink}[\ My daily life is made of many switches, who am I when they
are all off, but above all who am I when, for some reason, they are all
on?]{.yellow}
[Economic survival and also a certain character of mine, lead me to work
on different fronts, to create, a verb that problematically collides
with "produce" many things / projects / situations / groups.]{.pink}[
What was the question again? cultural creation or cultural
production?]{.green}
[But where am I when I'm here and I have to be there tomorrow? Who am I
with you today if I have to be with them tomorrow?]{.violet}[]{.pink}
[Okay.]{.yellow}
[Now the switches are all on.]{.blue}[]{.pink}
[I'm getting anxiety. Maybe I should just do one project? Make my
curriculum vitae coherent, functional, be one thing, a monad.]{.pink}[\ A
nomad?]{.green}[\ Is this an aspiration of my deep self or the market
demand? How and when did I embody it? Who am I when I reconnect with
myself and all the different parts I'm connected to?\ ]{.pink}[How would
my body react if one of these parts remained passive or atrophied, or
simply got carry by others? Or on the contrary, if it became more
active, or even the most active\... like the motor of the whole
body?]{.blue}[\ Am I still connected to myself if I am not connected with
anyone else?]{.green}
[]{.green}
[Do I need a body? But what if I don't need the calm of the
Unity.]{.blue}[\ I want to live WITH and to work IN the contradiction.
How to develop contradictions as a new form of knowledge? How to embrace
them as a way to learn that the contrary is in me?]{.yellow}[\ How to
inhabit the contradictions in myself? The space in between is always an
interesting textile made of different colors and knots to create a whole
full of memories.]{.pink}[\ The space in between has an enormous creative
power.]{.orange}[\ I have the intuition that the idea of Unity it's what
made Western culture so wrong. The subjective intensity in this "in
between" erase the ego, the authority, and I recognize my own ignorance.
]{.violet}[There is a creative explosion IN my contradictions.
]{.green}[How to take
de]{.yellow}[c]{.green}[i]{.yellow}[s]{.green}[ions in the
contradiction? Can they adapt on the in-between]{.yellow}[? Are
decisions univocal?]{.blue}[\ Maybe the question is not how to overcome
contradictions but how to live with them, how to inhabit them to avoid
schizofrenia. To transform schizofrenia into a process of
knowledge.]{.red}[\ Can I transform my stigma into my emblem?
]{.blue}[Can I transform my monstrosity into my beauty?]{.violet}[\ How
to be an alternate body?]{.orange}
[]{.orange}
[Author/authority\... Power Relations]{.green}
[\ Where is the reader, who is\ ]{.orange}[taking care]{.orange
.char-style-override-1}[\ of what and whom?]{.orange}
[]{.orange}
[Why am I an artist?]{.red}[\ Am I an artist?]{.green}[\ How do I solve my
problems? Are they problems, traumas or obsessions? Are my obsessions
contradictory within myself. Are my obsessions individual or
collective?]{.pink}[\ I am a she\...]{.red}[\ Am]{.yellow}
[I? Is my identity my choice?]{.yellow}[\ Is it what emerges through the
interconnection of my different parts?]{.violet}[\ Is it the choice of
the reader? I chose it some time ago but I have the freedom to go
against my choice.]{.green}[\ Will I forever adolescence?]{.red}[\ Freedom
can be troublematic\...]{.green}[\ But making choices means
]{.red}[questioning]{.red .char-style-override-1}[, and a question is
always a good start for creation.]{.red}[\ What was the question
again?]{.blue}[\ Is the question still important when having a question
mark going on?]{.yellow}
[]{.yellow}
[There is something about starting that is always making me nervous,
]{.green}[tense]{.green .char-style-override-1}[, shy.]{.green}[
(Respons-ability)]{.green .char-style-override-1}[]{.green}
[There is no need to start from the beginning.]{.blue}[\ I understand the
world around me, using the dimensions of my body are my reference. The
world around me, starts from my body.]{.red}[\ My body is troubled, it is
human, but multiple.]{.green}[\ And I love,\ ]{.pink}[fight for]{.green}[
and will always defend my\ ]{.pink}[porosity]{.pink
.char-style-override-1}[. My body is in the world, connected to the
world but not necessarily placed in one specific space at once\... I am
now in my bed]{.green}[, I am on my sofa]{.violet}[, I am
outside.]{.blue}[\ ]{.yellow}[There is a small breakfast table holding
the computer on which I am writing, so it let my belly breath.]{.green}[
There is a big window in front of me, showing the grey in grey of the
sky.]{.red}[\ Where else am I present?]{.green}[\ The sky is grey here, it
is winter and cold and I wished to be back on a sunny November day in
Barcelona, having beer - no, what was is? on the streets- on the balcony
of the casita, having coffee, walking around, jumping in the
sea.]{.orange}[\ Having fun,]{.yellow}[\ with\ ]{.pink}[joy]{.pink
.char-style-override-1}[\ ]{.pink}[and\ ]{.blue}[dis/agreement]{.blue
.char-style-override-1}[]{.blue}
[]{.blue}
[Do I need a body?]{.pink}[\ One body that fills a house, and fills all
its entities with passion.]{.blue}[\ I walk by the house almost every
week. I see the balcony and remember the view from there, but I am not
allowed to enter the]{.green}
[place anymore. The house is now just a shell for other bodies to enter,
inhabit, cohabit, make the place alive.]{.green}[\ Do I need a house?
]{.orange}[I enter another body of this kind some month ago in Brussels,
there was an elephant bone in the middle of the room There was a garden
too.]{.green}[\ What does it mean, What does it mean, if I can not go
back to where I started?\ ]{.blue}[Start is not necessarily the
beginning.]{.red}[\ Does this make me fragmented?]{.pink}[\ Is this
fragmentation subject of my art?]{.blue}[\ Can I cure this fragmentation
through my art?]{.yellow}[\ Can I recreate]{.violet}[/reeneact]{.pink}[
the start? Do I need hypnosis to do a regression?]{.violet}[\ Do I need
hypnosis to do a regression?]{.yellow}
[]{.yellow}
[I am a constellation of affinities. A community of sense. A body
politics.]{.yellow}[\ I think this is a privilege.]{.blue}[\ I feel now
more solid!]{.red}
[]{.red}
[Can I add visual memories here?
https://cloud.hangar.org/apps/files/?dir=/Documentation/Photos/05.11.18-Action1&fileid=12866]{.red}[
I just erased something that I found irrelevant for the reader, is it
self censorship?]{.violet}[\ Censorship? It destabilizes my memory, I
cannot remember what was there.]{.red}[\ What had opened before the
sky?]{.yellow}[\ The recording platform.]{.violet}[\ I don't remember
talking about a recording platform today.]{.red}[\ Anyways, The sky also
just opened. I'll go out quickly to look at the sky without glass
inbetween There is a discoball in front of the window that fills my room
with light dots.]{.pink}[\ Once I was at an exhibition, where the artist
put round colored stickers all over the room (on everythig), so it
lookied like there was a discolight.The title was "I am here, but
nothing".]{.orange}[\ Yesterday I was watching the instagram stories two
friends. They both had a picture of light reflections of there windows
on their wall. There was a light comunication between their places, it
often happens when there is a rainbow that is observed by several people
at the same time but from different places.\ ]{.green}[My body has the
ability to see the world from different places, angles and timezones. I
can do that with all my senses, actually. and I can even appear and act
in different places, without being virtual or a spirit.]{.yellow}[\ My
flatmate just brought me a crosswordpuzzle.]{.blue}[\ The nursery sent me
a message, they need diapers.]{.green}[\ I remember the first time I
misunderstood myself. In front of a bar. Sometimes I understand a
reference better, than a word.]{.blue}[\ My language is
allegorical.]{.orange}[\ My language is circunstancial.]{.green}[
Allegory is self-conscious poiesis that respect the conditions of
particularities without reduce the power of the common. Allegorical
language is aesthetically open.\ ]{.pink}[Even my silence is allegorical,
]{.blue}[circunstancial.\ ]{.green}[My children also learned to speak it.
We learned it together. They taught me. Once, they create a very
beautiful one at Hangar. It was an allegory of gestures and objects, a
common ground, a garden. We create this center that shaped
everything.]{.orange}
[]{.orange}
[///\ ]{.yellow}[improvisation]{.yellow .char-style-override-1}[
///]{.yellow}[\ What was the question again?]{.red}[\ how to make of
trouble a generative force?]{.violet}[\ Wait I'll be back.\ ]{.green}
[// this option would imply to both provisionally define/understand what
trouble is (or not) in cultural creations, and to list and unfold its
generative forces/potential.]{.yellow}[]{.violet}
[Troubles in cultural creations:]{.violet}[\ disturbing the
comfort]{.red}[,]{.green}[authorship]{.green
.char-style-override-1}[/authority]{.green}[, censorship,
]{.blue}[money,\ ]{.red}[set of values,]{.pink}[
]{.red}[misunderstanding]{.red
.char-style-override-1}[,freedom?]{.orange}[]{.red}
[What is a cultural creation?]{.orange}[\ I tried to avoid this
question\... Maybe I/you can find references.\ ]{.violet}[I feel lonely,
uncomplete today.]{.yellow}[\ Melancholy is a well known motif in
cultural creation.]{.blue}[]{.yellow}
[]{.yellow}
[Maybe after that it was a long time I wasn't seeing myself. Sometimes,
you feel your entire body as being so many different parts, not even
knowing which kind of fluctuation causes them to stay together and
merge.]{.yellow}
[Sometimes I have no sleep, but my eyes are closing.]{.blue}[\ Sometimes
my legs want to run, but I am still siting here.\ ]{.orange}[In the end,
the symbiosis that are established between all the parts shows that a
type of connection]{.pink}
[makes any of of them be here, touching us in some
way.]{.pink}[]{.yellow}
[]{.yellow}
[I need to change spot, my butt is hurting!]{.green}
[]{.green}
[Hello there reader, back again in a new chair, a new location, a bit
cold due to the weather conditions of this place.]{.violet}[\ Rica means
rich, a rich woman in Spanish.\ ]{.blue}[Rica also means sexy, hot,
tasty, ggood for latinos. My language is circunstacial.]{.green}[\ When I
was born some of the members of this collective organism were
]{.yellow}[questioning]{.yellow .char-style-override-1}[\ its own
precarious situations and while half joking, we thought that being
called Rica was a]{.yellow}
[good option to get out this precarity, a total contradiction as being
all artists and working in art institutions]{.yellow}[, we know how
individual names are the prefered option for most of the places we work
and try to collaborate with.]{.red}
[]{.red}
[I like]{.blue}[\ to cook with others, do some gardening, write texts
together]{.violet}[\ and get a bit confused with the limits of
]{.orange}[my]{.blue}[\ own self]{.orange}[, dance, make sounds
]{.red}[and try to travel with all its members to the different cities
they get invited to contribute with their works.]{.blue}[\ I
]{.green}[need]{.green .char-style-override-1}[\ money and time to get
mysef together.\ ]{.green}[Am I really\ ]{.orange}[always]{.pink}[\ ok to
]{.orange}[stay with the trouble?]{.orange
.char-style-override-1}[]{.green}
[]{.green}
[//Endnote: This text was created by Rica Rickson in a process of
creative writing, It is a reflection that shall reveal the generative
forces of troubles. //]{.blue}[]{.green}
[]{.green}
[Ps. I am also fine with the sharing of the Cocktail]{.orange}
[]{.orange}
[#color]: 12
[#text]: 2
[#trace]: 1
[#curve]: 1

27
static/contributions/score-esc.md

@ -0,0 +1,27 @@
# score esc {.hidden}
Ear Worm sound rotating round the space. Dynamics increasing and decreasing over time. Vibrations of the floor are picked up by an earthqake sensor adding further sound to the soundscape. Soil is put on the floor. The diagram on upper left corner shows growing of plants during the exhibition's time span. Slogans and ideas are written on the windows.
Within this composition we intend to project different musical reflections upon our collective practice and the exhibition within the Iterations in Graz into the concrete physical space. Intentionally, we started from different compositional approaches, to destabilize the dominant Image of the **authentic** Composer, whose **individual** work is stylistically **coherent**. We hereby &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; oppose (propose) a spatial conception, that might be **inconsequent**, but therefore **open** for Interpretation and **Improvisation**.
![](images/scoreESC.jpg)
![](images/scoreESC-1.jpg)
![](images/scoreESC-2.jpg)
![](images/scoreESC-3.jpg)
![](images/scoreESC-4.jpg)
![](images/scoreESC-11.jpg)
![](images/scoreESC-7.jpg)
![](images/scoreESC-8.jpg)
![](images/scoreESC-9.jpg)
![](images/scoreESC-10.jpg)

320
static/contributions/spideralex.md

@ -0,0 +1,320 @@
# Por debajo y por los lados: Sostener infraestructuras feministas con ficción especulativa
Los talleres de "***Futurotopías feministas***[^1]" han mostrado su
capacidad para aportar lo que un grupo necesita en ese momento para
poder sostenerse. En ellos compartimos acerca de cómo la ficción
especulativa puede sostener nuestros esfuerzos para la creación de
infraestructuras, comunitarias y feministas. A veces han permitido que
una comunidad identifique sus malestares y ponga el foco en lo que
necesita sanar, otras veces han servido para contrabalanzar violencias
estructurales causadas por el ***patriarcalismo*** (esa alianza criminal
del patriarcado y el capitalismo), en otras nos ha permitido un respiro
juntas para seguir cuidando de los posibles; y en otras ha activado
ecosistemas habitados por narrativas, personajes de ficción y llamadas a
la acción colectiva transformadora.
En los párrafos que siguen partimos de algunas fricciones que se dan
entre la infraestructura comunitaria/libertaria y la feminista. Pensamos
en cómo las ficciones especulativas ayudan a sostener la infraestructura
feminista, e ilustramos con algunas técnicas para hacer talleres de
ficción especulativa juntas.
Infraestructuras rugosas
-----------------------------
**Infraestructura**: Construir una casa o algo que te sostiene desde
algún lugar (diferente al mercado). Hacer comunidades situadas en una
multitud de lugares.
Soy parte de una comunidad (Calafou) que "construyo especulativamente"
con muchas otras personas que no siempre he elegido, acostumbrándome a
sentir con ellas y quererlas. Un ser polimorfo monstruoso llamado
comunidad que siempre enraíza en un territorio y un paisaje. Para
construir comunidad hay que volver a un territorio donde se hace y
deshace la comunidad, compuesta por seres sentipensantes, pandillas,
voluntades, experiencias, trayectorias, subjetividades y disonancias
varias.
Calafou consiste en recorrer uno de los posibles caminos que separa lo
distópico cyberpunk de una futurotopía. ¿Cómo se hace, cuando siempre
nos dijeron que la infraestructura o nos matará (*kill you*) o nos
poseerá (*enslave*)?. En Calafou, a veces, nos pasan ambas cosas.
Multi-gestionamos juntxs una ***infraestructura viva***. Eso genera
a veces estrés, frustraciones, miedos y sentimientos de culpa. Uno de
los valores de la comunidad reside en cuántas equivocaciones se permite
por el camino. También genera técnicas, herramientas, conocimientos e
infraestructura. Otro de sus valores radica en cuánto se permite soñar y
especular juntas.
Hacemos especulativamente infraestructura con otrxs, sin saber siempre
cómo hacerlo. ¿Qué hay que fijar, sistematizar, documentar? ¿Qué hay que
dejar escapar y no tocar? ¿Qué hay que regular y gobernar? ¿Qué es lo
que no tiene nada que ver con todo esto? ¿Cómo sobrepasar las tensiones
causadas por los procesos de fijación y seguir permitiendo que la
infraestructura también sea fluida, ligera, adaptable?
A veces me gustaría ser *Binti[^2] la harmonizadora,* colaborar en que
las energías de las entidades que conforman la comunidad puedan
confluir, resolucionar, transformar juntas. Saber hacerlo con todas las
especies presentes en el territorio, activar esa interfaz. Las siento
cerca y lejos, la puesta a distancia de lo sensible sigue allí e
interfiere en nuestras proyecciones de las infraestructuras comunitarias
posibles.
Infraestructuras que tienen tanto de técnicas como de tecnologías. Si
algo las delimita entre ellas, ese algo es relativo, borroso, cambiante.
Redes comunitarias de comunicación, comunidades que mantienen y
fortalecen saberes, infraestructuras que sostienen comunidades diversas.
Hablamos de infraestructuras que se enfocan en lo que consideramos como
básicamente necesario, como: luz, agua, caca, pis, conectividad,
compost, aguas residuales, residuos orgánicos/electrónicos/industriales,
alojamiento, producción de cosas materiales e inmateriales; y a veces se
procesa y hacen comidas o bebidas.
Se trata de la infraestructura básica que te sostiene y te consume,
porque ese tipo de infraestructura no siempre te libera, ni a tu tiempo,
ni a tu energía. No estoy segura de cómo se construyen infraestructuras
en comunidad que nos sostienen y son a la vez sostenibles
enérgeticamente y emocionalmente. Y puede que la infraestructura
feminista nos ofrezca pistas adicionales por explorar.
Infraestructuras feministas
---------------------------
Entendemos por ***infraestructuras feministas*** lo que
sostiene y apuntala con recursos, más o menos estables, las luchas
feministas: para su desarrollo y avance. Por ***recursos*** nos
referimos a técnicas, tecnologías y procesos (analógicos, digitales,
sociales) incluyendo espacios seguros, refugios, bibliotecas, redes de
sororidad y de confianza, servidores, páginas amarillas, repositorios,
bots, herramientas de documentación y memoria, enciclopedias,
HerStories, técnicas para la vida, conjuros, rituales y exorcismos. Se
incluye aquí también lo móvil, efímero, transicional que pueda residir
en la infraestructura temporal de encuentros, talleres y fiestas que
nutren la confianza, el cariño y el bienestar de las compañeras
feministas.
Creemos que la infraestructura feminista se encuentra ***por debajo y
por los lados***, es a menudo precaria y a veces difícil de ver. Pero es
extensa, distribuida y pone en su centro el valor y afecto que se
ofrecen entre sí las personas, máquinas y ecosistemas que la componen.
Nuestras perspectivas y condiciones de acceso, uso y desarrollo de las
tecnologías están profundamente influenciadas por cómo el patriarcado,
el capitalismo y el colonialismo están arraigados en nuestro cotidiano y
las sociedades en las que vivimos. Por ello necesitamos desarrollar
metodologías nuevas para identificar los procesos que están creando
infraestructuras feministas y cómo éstas nos señalan técnicas y
tecnologías liberadoras pensadas desde y para la vida.
La creación de infraestructura feminista trata de darnos respuestas y
valor. Pensar en la diversidad de nuestras contribuciones y acciones nos
permite abrir nuevos horizontes de acción política, así como procesos
restaurativos, y modelar otras posibilidades para todas nosotras.
Puede que la infraestructura feminista sea la que ofrece también
capacidad de sostenerse mediante la acción de trabajar juntxs los miedos
y vergüenzas impuestos por el sistema ***patriarcalista***. La
infraestructura feminista se parece a la infraestructuras comunitarias
anteriormente detalladas pero se hace desde otros lugares y
motivaciones.
Las mujeres y las compañeras feministas siempre han estado allí, por
debajo y por lados, compartiendo ***técnicas para la vida*** y haciendo
***tecnologías apropiadas*** (desde una idiosincrasia que no
contamina ni resta), tecnologías "lentas", tecnologías ancestrales,
"tecnologías menores", y tecnologías libres en pos de la soberanía y
autonomía de las comunidades que las desarrollan. Hemos sido portadoras
y garantes de que el conocimiento acerca de esta diversidad de técnicas
se compartiera y evolucionara dentro de comunidades ya que como nos
recuerda Margarita Padilla: "*todas las tecnologías se desarrollan en
comunidades, que pueden ser, más o menos, autónomas o pueden estar, más
o menos, controladas por las corporaciones. En la lucha por la
soberanía, la cosa va de comunidades. Nadie inventa, construye o
programa en solitario, sencillamente porque la complejidad de la tarea
es tal que eso resultaría imposible*[^3]".
Técnicas para la vida \<\> Tecnologías apropiadas
-------------------------------------------------
Pero ¿qué es lo que delimita la línea sensible entre técnicas y
tecnologías y por qué eso importa para la infraestructura feminista?
Según Biacini y Carnino, la técnica es "*a la vez un saber hacer y unas
herramientas, es decir, un conjunto de procesos informales y su
sedimentación instrumental en los objetos producidos por lx artesanx[^4]
\[\...\] La tecnología es un conjunto de procesos macrotécnicos (es
decir, procesos que son más grandes que el ser humano y la comunidad de
una aldea) que son posibles gracias a la alianza de la ciencia y la
tecnología[^5]*".
La técnica puede ser no tecnológica, pero la tecnología se hace
absorbiendo técnicas. En la sistematización inducida por la producción
de tecnologías, se pueden obviar y destruir por completo técnicas para
la vida. Las que nos ofrecían otras maneras de pensar nuestra relación
con el entorno y los valores que proyectamos en esa relación.
Biacini y Carnino nos aportan otro elemento central de reflexión al
desatacar que "*Según Wigney, el mundo antiguo seguía produciendo hechos
relativos en lugar de hechos absolutos porque se basaba en conocimientos
esencialmente situados y difíciles de transferir a otros lugares. La
industrialización y su corolario, la proletarización definida como
despojo artesanal, sólo fue posible a escala masiva con la ayuda de la
ciencia en desarrollo. Esta ciencia, lejos de ser especulativa, está
profundamente arraigada en la realidad: es un hecho*[^6]\".
En ese deslizamiento sentimos de nuevo el potencial de la práctica de
ficciones especulativas para recuperar esas técnicas para la vida y ver
cómo nos llevan hacia tecnologías apropiadas para nuestras comunidades.
En lo situado y lo especulativo encontramos manera de deshacernos del
mito de la ciencia y el progreso tecnológico. Esa tecnología se define
ante todo como una política de hechos consumados (*move fast and break
things*). Muy pocos participan en soñarla, diseñarla, decidirla; pero
todas nosotras estamos expuestas a los efectos de su implementación. No
deja lugar a un diseño especulativo colectivo de las técnicas y
tecnologías que necesitamos y nos merecemos. Las ciencias modernas y las
"nuevas" tecnologías se basan en poner a distancia, anular o absorber
las técnicas necesarias para la vida y nos impiden encontrar los pasos,
atajos y caminos hacia nuestras tecnologías apropiadas.
Según Elleflane, una "*tecnología adecuada y también apropiada, copiada,
obtenida. \[...\] describe aquella tecnología que mejor se adecua a
situaciones medioambientales, culturales y económicas, requiere pocos
recursos, implica menos costos, tiene un bajo impacto ambiental, no
requiere altos niveles de mantenimiento, se genera con destrezas,
herramientas y materiales de la zona y puede ser localmente reparada,
modificada y transformada. Al fin y al cabo, ¿qué comunidad no necesita
que una tecnología sea eficiente, se comprenda y se adapte a su contexto
medioambiental, cultural y económico propio*?[^7]\". En esta comprensión
y consentimiento mutuo entre comunidades y sus tecnologías apropiadas se
encuentran las claves para una infraestructura feminista que sostiene
ecosistemas regeneradores. Como si estuvieran basados en procesos de
autopoiesis, se alimentan de nuestras ideas, memorias, narrativas,
historias, fabulaciones y deseos.
Vocabularios nuevos para crear mundos
--------------------------------------
Lo que no se nombra no puede existir, por ello invitamos a crear nuevos
vocabularios y técnicas para explorar estas futurotopías e
infraestructuras feministas. Por ejemplo, podríamos hablarnos desde
estas nociones para darles forma:
Eco-tecnología (*Toward an ecological society*, Murray Bookchin): "*Una
eco-comunidad podría ser sostenida por una nueva clase de tecnología
compuesta de maquinaria flexible y versátil cuyas aplicaciones
productivas garanticen la durabilidad y la calidad, no siendo
programadas para la obsolescencia ni para producir una cantidad absurda
de baratijas y entrar en la rápida circulación de mercancías básicas*".
Eco-economia (*Trilogía de Marte*, Stanley Robinson): ¿Cómo creamos en
este y los otros planetas una economía que pone en su centro una
ecología basada en la solidaridad geológica e inter-especies?
Cacatedra (*Ecosec*): Volverse experta en gestionar nuestra mierda, en
el sentido literal y figurado, toda la cadena que implica expulsarla,
recogerla, sacarla a paseo y compostarla. Devolverle su dignidad como
indicio de buena suerte y abono para la tierra.
Para-paraíso (*Tatiana, THF Editorial*): Se trata de un paraíso cuidado
por antiguos paramilitares reconvertidos en guíás turísticos y
guardianes del bosque y los ríos, o de un paraíso situado en una
dimensión paralela/alternativa que está muy cerca pero a la cual no
sabes aun como llegar.
Narcofeminismo (*Metzineres*): Teoría práctica basada en lo desarrollado
y hablado en los espacios autogestionados por mujeres que consumen
drogas, construyendo una relación diferente con un hábito de consumo a
través del feminismo y la solidaridad, empoderándose juntas para
sobrepasar situaciones de violencia de genero sistémicas. Apropiarse de
tecnologías blandas, químicas y relacionales.
Futurotopía (*B01*): Contraer los futuros y nuestras utopías, resaltar
la posibilidad de pensar juntas en los futuros hacia los cuales queremos
caminar juntas, volviéndolos posibles al permitir pensarlos y
contárselos a otras personas. Para trabajar juntas en el eje del tiempo,
también proponemos algunas técnicas de Black Quantum Futurism[^8]:
Visión de futuro : a través de este modo de práctica se aumenta la
capacidad de conocer el futuro al ser capaz de verlo con más claridad
visual de lo normal. Este modo implica poca o ninguna desviación del
futuro, sólo una mayor precisión sin visualizarlo.
Alteración futura: este modo de práctica implica una estrecha desviación
de la realidad presente, utilizando lo que ya está disponible y lo que
es estadísticamente probable para elegir el futuro de un pequeño
subconjunto de futuros probables
Manifestación futura: este modo de práctica implica el mayor grado de
creatividad, permitiendo al practicante construir el futuro paso a paso,
pieza a pieza.
Y añadimos generar nuestras propias profecías autorrealizadas "*una
predicción que, una vez hecha, es en sí misma es la causa de que se haga
realidad*".
Finalmente, listamos otras técnicas posibles[^9] que hemos ido
detectando en nuestras ficciones especulativas: visionar, relatar
nuestros suenos y pesadillas, contar nuestras memorias, meditar, dormir
despiertas, ramificar, crear recuerdos del futuro, rescatar y exorcizar
pasados no escritos, apuntar a los vacíos, suturar las grietas, viajar
en nuestras cuerpas, crear procesos restaurativos, sanar los traumas,
limitar las narrativas impuestas, escritura automática.
Pensar nuestras infraestructuras desde los puntos de tensión
-------------------------------------------------------------
La infraestructura comunitaria y la feminista comparten puntos en
común y algunas tensiones que las atraviesan.
Creemos que ambas se basan en procesos de hacer juntas de manera
especulativa. Ambas pueden explotar, reventar, desaparecer rápidamente
también.
Implican técnicas para la vida, tecnologías que permiten
sistematizar/fijar ciertos procesos y que existen en ecosistemas que
necesitan ser documentados, comunicados, circulados para así existir.
Tienden a sistematizar/sedimentar ciertos procesos, absorbiendo técnicas
para la vida y haciendo a veces con ellas tecnologías apropriadas.
Orientan ciertas necesidades/acciones hacia algunos recursos pensados
para cubrirlos/darles una respuesta, generando efectos varios que no
sabemos aún cómo rastrear/leer.
La infraestructura tiende a (re)generar y (a)cumular, y hay que revisar
con periodicidad la alquimia que se deriva de esa tensión, para drenar o
regar/alimentar a tiempo.
La infraestructura estable, o la que está en beta, nos adentra en una
tensión paradójica entre "ganar" en autonomía y no "perder" en
independencia .
Así que en sí misma es una pregunta abierta acerca de cómo podemos o
seguir con ella, o vivir sin ella, y en qué grado o de qué modos lo
hacemos posible juntas.
[^1]: <https://zoiahorn.anarchaserver.org/specfic/>
[^2]: https://en.wikipedia.org/wiki/Nnedi\_Okorafor
[^3]: Margarita Padilla, "Soberanía Tecnológica, Volumen 2", 2018,
Disponible:
https://www.ritimo.org/IMG/pdf/sobtech2-es-with-covers-web-150dpi-2018-01-13-v2.pdf
[^4]: El lenguaje es también una tecnología, a menudo binaria, y en esta
ocasión he decidido des-binarizar alx artesanx.
[^5]: "On arrête parfois le progrès", Cedric Biacini y Guillaume
Carnino, \"Les luddites en France: Résistances a
l\'industrialisation et a l\'informatisation\", Ed. L\'échapée, 2010
[^6]: ibid
[^7]: De las tecnologías apropiadas a las Tecnologías Re-Apropiadas, por
Elleflâne, Soberanía Tecnológica, Volumen 2", 2018, Disponible:
https://www.ritimo.org/IMG/pdf/sobtech2-es-with-covers-web-150dpi-2018-01-13-v2.pdf
[^8]: https://www.blackquantumfuturism.com/
[^9]: https://zoiahorn.anarchaserver.org/specfic/2020/02/11/iterations/

157
static/css/curves.css

@ -0,0 +1,157 @@
svg {
touch-action: none;
display: block;
clear: both;
/*border:1px dotted magenta;*/
/*margin-top:-1px;*/
}
.handle {
fill: none;
pointer-events: all;
}
/* including #handles is a hack to increase specificity */
#handles .handle:hover {
/*fill: #f00;*/
fill: black;
}
svg:hover .handle {
/*fill: #800;*/
fill: black;
}
svg:hover .selected .handle {
/*fill: #fcb;*/
fill: black;
stroke: black;
}
#handles line.target {
stroke: black;
}
#handles circle.target {
fill: none;
stroke: black;
stroke-opacity: 0.15;
stroke-width: 10;
}
svg:hover #handles line.tan {
/*stroke: #00f;*/
fill: black;
stroke-width: 1;
}
svg:hover #handles .tanhandle {
/*fill: white;*/
fill: black;
/*stroke: #00f;*/
}
svg:hover #handles .tanhandle.computed {
/*fill: rgba(255, 255, 255, 0.3);*/
fill: black;
/*stroke: rgba(0, 0, 255, 0.3);*/
}
svg:hover #handles .tanhandle:hover {
/*fill: #fcb;*/
fill: black;
}
#grid line {
stroke: #ddf;
}
#nav {
position: relative;
font-size: 12px;
/* float: left; */
height: 25px;
z-index: 10;
top: 0px;
left: 0;
padding: 0;
margin: 0;
width: 100%;
user-select: none;
}
#nav li {
float: left;
position: relative;
list-style: none;
}
#nav li a {
display: block;
padding: 5px 10px;
color: #000;
text-decoration: none;
cursor: default;
}
#nav a:hover:not(.inactive) {
background: #cdf;
}
#nav li ul {
position: absolute;
display: none;
background: #fff;
box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.3);
/*float: none;*/
}
#nav li ul a {
width: 10em;
width: -moz-max-content;
width: max-content;
padding: 4px 10px;
cursor: default;
}
#nav li ul a.inactive {
color: #888;
}
#nav li:hover ul {
display: block;
left: 0;
margin: 0;
padding: 0;
border-width: 1px;
border-style: solid;
border-color: #000;
}
#nav li:hover ul.off {
display: none;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(255, 255, 255, 0.3);
}
.modal-content {
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 60%;
background-color: rgba(255, 255, 255, 0.8);
}
.modal-content pre {
white-space: pre-wrap;
}
.modal-content textarea {
width: 100%;
}
.modal-content button {
font-size: 14px;
}
.close {
float: right;
font-size: 28px;
user-select: none;
}
.close:hover, .close.focus {
cursor: pointer;
}
.invisible {
opacity: 0;
}

162
static/css/stylesheet.css

@ -0,0 +1,162 @@
@font-face{
font-family: 'main';
src:url(../fonts/go-mono/Go-Mono.ttf);
font-weight: normal;
font-style: normal;
}
@font-face{
font-family: 'main';
src:url(../fonts/go-mono/Go-Mono-Bold.ttf);
font-weight: bold;
font-style: normal;
}
@font-face{
font-family: 'brazil';
src:url(../fonts/Brazil/Brazil-Regular.otf);
}
body{
color: black;
/*background-color: rgba(255, 0, 255, 0.35);*/
/*background-color: rgba(194, 216, 28, 0.35);*/
background-color: rgb(247, 245, 243);
font-family: 'main';
font-size: 20px;
line-height: 1.7;
text-align: center;
}
/* general */
/* ------- */
div#main{
margin-top:5.5em;
}
hr{
border:0;
border-bottom:1px dotted magenta;
/*border-bottom:50px dotted #8fbeff;*/
/*border-bottom:50px solid #8fbeff;*/
margin:5em 0;
clear: both;
}
small{
display: block;
line-height: 1.6;
margin:2em;
}
.block{
color:magenta;
width: calc(50% - 8em);
float: left;
padding:0em 4em 3em;
}
.block p{
margin-top:2em;
}
.info{
color:magenta;
padding:1em 0 0;
font-size: 32px;
max-width: 1000px;
margin:0 auto;
position: relative;
}
/* nav */
div#tools-nav,
div#tools-extra{
position: fixed;
top:5px;
z-index: 10;
}
div#tools-nav button,
div#tools-extra button{
line-height: 1.5;
}
div#tools-extra{
right: 5px;
}
/* special font for handles and annotation tools*/
#title,
strong.handle,
strong.annotation,
button{
font-family: 'brazil';
font-size: 190%;
font-weight: normal;
line-height: 0;
}
#title{
font-size: 110%;
}
svg#index{
width: 400px;
}
table{
width: 100%;
margin:1em 0 5em;
}
table th,
table td{
padding:1em;
border:1px solid;
vertical-align: top;
}
table td textarea{
width: calc(100% - 2em);
height: 100%;
margin: 0.25em 1em;
}
table td select{
width: 100%;
}
/* forms */
form{
line-height: 3;
}
input{
vertical-align: middle;
margin-left: 1em;
}
input.submit,
input.reset,
a#reset,
button a{
margin:0;
text-decoration: none;
color:black;
}
/* annotation tools */
/* ---------------- */
/* curves */
svg{
margin:0 auto;
}
svg.canvas{
background-color: rgba(220,220,220,0.8);
}
div#curves svg.canvas{
background-color: transparent;
}
/* color */
#color-preview{
width: 100px;
height: 100px;
float: left;
border-radius: 100%;
margin:1em;
}
/* text */
/* visual-traces */
img.traces{
max-width: 500px;
vertical-align: middle;
margin:0 0 1em 1em;
}

44
static/css/text-scan.css

@ -0,0 +1,44 @@
/* --------- */
/* text-scan */
#wrapper{
width: calc(100% - 0em);
}
div#crossings, div#sentences, div#ADJ, div#PRE{
position: relative;
display: inline-block;
width: calc(50% - 1.5em);
top:0;
left: 0;
margin:0 1em 2em 0;
padding:0;
vertical-align: top;
font-size: 90%;
}
div#crossings{
float: right;
}
div#txtfiles{
position: absolute;
width:200px;
top:0;
right: 40px;
}
div.sentence{
margin:-0.5em 0 0;
}
strong.query{
display: inline;
width: 100%;
height: 12em;
margin:10em -6em -10.5em -6em;
padding: 10em 6em;
text-align: center;
}
small, sup{
font-family: monospace;
font-size: 10pt;
}
div.sentence small{
display: block;
margin-bottom: 5em;
}

BIN
static/curves/014-Loren-Britton-Disclaimer_5000.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

7
static/curves/1.svg

@ -0,0 +1,7 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M119 310.6666717529297C106.66666666666667 248.6666717529297 94.33333333333333 186.6666717529297 82 124.66667175292969C156.66666666666666 116.33333841959636 231.33333333333334 108.00000508626302 306 99.66667175292969C352.6666666666667 94.66667175292969 399.33333333333337 89.66667175292969 446 84.66667175292969C451.3333333333333 137.33333841959634 456.6666666666667 190.00000508626303 462 242.6666717529297C397.6666666666667 249.00000508626303 333.3333333333333 255.33333841959634 269 261.6666717529297C269.54639784064454 236.17080432299434 261.4502396564792 200.60739308901887 235 194.6666717529297C209.50099641504292 188.93959794892874 187.1756949390231 215.28680923887313 177 237.6666717529297C164.1156183501165 266.00387050259155 164.63694384125805 301.33739707446097 182 327.6666717529297C213.20135307937326 374.9802891285833 286.2872631346337 374.261980380885 336 353.6666717529297C364.1445260671584 342.0067785929438 390.40980415727887 321.6505659363975 402 292.6666717529297C414.8540803654511 260.52214976264753 407.756787255209 222.5211973337685 388 194.6666717529297C349.68905890028975 140.6531788148748 268.93768467361656 132.0717430349891 209 153.6666717529297C181.98791525666218 163.39884689002034 157.66576415019495 179.81380238231938 138 200.6666717529297" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(119 310.6666717529297)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(82 124.66667175292969)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(306 99.66667175292969)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(446 84.66667175292969)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(462 242.6666717529297)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(269 261.6666717529297)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(235 194.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(177 237.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(182 327.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(336 353.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(402 292.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(388 194.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(209 153.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(138 200.6666717529297)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="8.23314889296513" y2="-8.730135125315476"></line><circle r="3" class="tanhandle computed" cx="10.291436116206413" cy="-10.912668906644345"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

8
static/curves/10.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M156 248.13333129882812C201 229.46666463216147 246 210.79999796549478 291 192.13333129882812C348.6666666666667 232.46666463216144 406.33333333333337 272.7999979654948 464 313.1333312988281C377.6666666666667 339.1333312988281 291.3333333333333 365.1333312988281 205 391.1333312988281C193.57582490707534 352.3761655110559 193.07989408253036 297.2742561126642 230 275.1333312988281C262.9471451455424 255.37498431226584 313.36887697823494 269.25481657028485 333 302.1333312988281C352.5666589847736 334.90388064060596 337.08979980296766 379.0903239842566 367 404.1333312988281C400.2115402259327 431.94046195416695 453.4582834738683 406.06430952410574 485 380.1333312988281C471.67230755429813 317.9498203848353 437.49678762348753 259.1711400203745 385 222.13333129882812C343.71619710998425 193.0065663297978 268.12988933817934 161.11781618428074 280 114.13333129882812C293.9371519015892 58.96704671037142 410.6441651671511 53.43583800704196 455 99.13333129882812C491.69454019631996 136.9377940213891 471.81086739912746 202.52681534154746 445 244.13333129882812C378.9614089910384 346.61549458385906 214.52058989847953 391.081841739659 114 320.1333312988281C42.79870461701117 269.87869337855176 -8.258299995398652 153.30028679517488 46 82.13333129882812C108.95806008438797 -0.44451320684079576 289.9021783569017 9.343168261720095 333 102.13333129882812C353.7837232240067 146.88095410083912 337.151228890026 199.3899584688685 312 239.13333129882812C296.033790254328 264.3627556493503 274.7259852314941 287.45980979101927 270 318.1333312988281C262.56661374873585 366.3789609158119 299.26866756531854 413.18984274719725 342 433.1333312988281C405.2969958723325 462.6751883064962 490.4261989561637 437.28243564614905 530 380.1333312988281C564.1056535862571 330.8808585966093 558.7852639492309 266.114194310134 543 211.13333129882812C525.1773706101097 149.05622266520407 490.2427668304449 86.31866719785053 430 57.133331298828125C394.1276185475109 39.754522899548896 354.0507078716826 36.58267578511893 315 34.133331298828125C240.16406166852357 29.43946010113042 157.71244073432396 26.68644111095019 94 73.13333129882812C18.541776667693505 128.1429930304004 -13.50755345706186 239.6812536953007 22 326.1333312988281C44.36526711378124 380.5872043453725 92.0752876181496 421.5387487966417 146 443.1333312988281" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(156 248.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(291 192.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(464 313.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(205 391.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(230 275.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(333 302.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(367 404.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(485 380.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(385 222.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(280 114.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(455 99.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(445 244.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(114 320.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(46 82.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(333 102.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(312 239.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(270 318.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(342 433.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(530 380.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(543 211.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(430 57.133331298828125)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(315 34.133331298828125)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(94 73.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(22 326.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(146 443.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-11.13996007482092" y2="-4.461086138082957"></line><circle r="3" class="tanhandle computed" cx="-13.924950093526148" cy="-5.576357672603697"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

8
static/curves/11.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M416 42.133331298828125C341 71.13333129882812 266 100.13333129882812 191 129.13333129882812C187.33333333333334 170.46666463216144 183.66666666666666 211.7999979654948 180 253.13333129882812C254.33333333333331 263.46666463216144 328.6666666666667 273.7999979654948 403 284.1333312988281C420.3333333333333 240.46666463216147 437.6666666666667 196.79999796549478 455 153.13333129882812C430.3333333333333 150.13333129882812 405.6666666666667 147.13333129882812 381 144.13333129882812C392.6666666666667 110.13333129882812 404.3333333333333 76.13333129882811 416 42.133331298828125Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(416 42.133331298828125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-11.192437020524073" y2="4.3277423146026415"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="-3.894736719783899" y2="11.350375583370225"></line><circle r="3" class="tanhandle computed" cx="-4.868420899729873" cy="14.18796947921278"></circle><circle r="3" class="tanhandle computed" cx="-13.99054627565509" cy="5.409677893253302"></circle></g><g class="handle" transform="translate(191 129.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(180 253.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(403 284.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(455 153.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(381 144.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

8
static/curves/12.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M124 132.13333129882812C182 135.13333129882812 240 138.13333129882812 298 141.13333129882812C282.3333333333333 194.79999796549478 266.6666666666667 248.46666463216147 251 302.1333312988281C211.2316972171952 266.6390064638967 132.89849158092497 204.05506326231236 108 231.13333129882812C86.79713631208007 254.19243205668513 135.40662405447694 308.49008714984126 164 336.1333312988281C155.08355541941168 293.3493382570666 158.96475058252116 233.17363824707795 201 213.13333129882812C268.18500100524307 181.10287921106942 393.36355812905833 270.4201133527033 416 238.13333129882812C427.5852821900247 221.60902579027822 395.0923267929571 197.7902215723125 396 173.13333129882812C397.5516265127292 130.9834866488617 495.78814030848423 111.36687346874123 535 144.13333129882812C579.1507572278266 181.02686137501058 540.1464834956487 277.37333072806354 487 308.1333312988281C451.4659664929063 328.69963821261024 405.9492376977495 322.06527425289795 371 303.1333312988281C307.6105964857171 291.1239072037453 234.2479505967231 303.31157950404975 189 352.1333312988281C177.81470312621235 364.20207210413037 168.7113894678934 378.1167075652287 162 393.1333312988281C152.66666666666666 381.46666463216144 143.33333333333334 369.7999979654948 134 358.1333312988281" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(124 132.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(298 141.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(251 302.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(108 231.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(164 336.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-2.448270172711733" y2="-11.74759435635271"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="-8.6274110733285" y2="-8.340730074268606"></line><circle r="3" class="tanhandle computed" cx="-10.784263841660625" cy="-10.425912592835758"></circle><circle r="3" class="tanhandle computed" cx="-3.0603377158896663" cy="-14.684492945440887"></circle></g><g class="handle" transform="translate(201 213.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(416 238.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(396 173.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(535 144.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(487 308.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(371 303.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(189 352.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(162 393.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(134 358.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

8
static/curves/13.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M182 269.1333312988281C137 263.1333312988281 91.99999999999999 257.1333312988281 47 251.13333129882812C96.12156562145479 300.686308571472 193.84783119109557 390.29152650765235 226 359.1333312988281C238.22975991653664 347.2816503406537 231.11838454446263 327.5068366254957 228 312.1333312988281C221.09295822940132 278.08190436998143 228.8720602400653 241.45133957575516 249 213.13333129882812C275.16168759469673 176.32644024683472 330.5385560311351 141.8114589169556 371 166.13333129882812C424.70170795525337 198.41408945914796 397.426821062545 301.45595837762585 356 351.1333312988281C292.939405717277 426.75305249553367 128.45897967946297 461.01536572078413 84 381.1333312988281C48.90092710412599 318.0687857455681 121.31816566630887 242.63628727978104 170 194.13333129882812C227.19562477510127 137.14786826647207 303.98004615011723 65.17506405215175 388 90.13333129882812C459.53498479782144 111.38291769185192 514.0774028970847 196.2911564089153 495 269.1333312988281C483.802736786667 311.8872118806879 444.6839875250464 360.25788106677305 399 353.1333312988281C357.4540677504834 346.6541231998182 332.34143717064813 297.68749049876277 334 257.1333312988281C336.15676703354063 204.39738901947777 383.8686674500593 158.91444665143158 435 150.13333129882812C468.30467865058563 144.4137028356634 504.87268314617165 153.95406276885421 529 178.13333129882812C558.1674445575009 207.36358008950683 576.3928680339095 265.242384102397 546 295.1333312988281C520.0874310358216 320.6179690029094 472.51846051360536 312.2995108688179 446 289.1333312988281C403.8295778362694 252.29380315630837 427.655218509394 189.05769149587655 399 142.13333129882812C368.2281774350923 91.74292560424136 294.69272548071154 89.23717558054281 241 103.13333129882812C195.3568589523287 114.94618284978831 155.24189337075416 140.89564857929835 119 170.13333129882812C104.83969144830029 181.55697863451988 91.16049638145144 193.57099011097262 78 206.13333129882812" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(182 269.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(47 251.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(228 312.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(249 213.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(371 166.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(356 351.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(84 381.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-5.835744932216319" y2="-10.48542231319805"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="5.835744932216319" y2="10.48542231319805"></line><circle r="3" class="tanhandle computed" cx="7.294681165270399" cy="13.106777891497561"></circle><circle r="3" class="tanhandle computed" cx="-7.294681165270399" cy="-13.106777891497561"></circle></g><g class="handle" transform="translate(170 194.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(388 90.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(495 269.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(399 353.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(334 257.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(435 150.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(529 178.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(546 295.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(446 289.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(399 142.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(241 103.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(119 170.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(226 359.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(78 206.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

8
static/curves/14.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M109 231.13333129882812C118 255.79999796549478 127 280.46666463216144 136 305.1333312988281C168.33333333333331 308.46666463216144 200.66666666666669 311.7999979654948 233 315.1333312988281C213.01209286388158 257.1521393784681 213.83212903229435 189.17836099187284 247 136.13333129882812C267.77639859507633 102.90585397707254 299.8616413030249 77.74910668886862 336 63.133331298828125C408 90.46666463216145 480 117.7999979654948 552 145.13333129882812C568.2424390998425 168.60399150890393 576.405350265502 199.23889692857384 568 227.13333129882812C559.8236437578109 254.2678141438379 537.4447736778911 273.9136865600715 514 288.1333312988281C469.3682948482126 315.20320196863275 380.31143605380015 347.2092693796733 361 309.1333312988281C337.6134665087924 263.0226123105526 432.5210803297419 145.69688493614703 422 136.13333129882812C412.907226583634 127.86809393213109 320.96787010747227 196.36241133914393 305 184.13333129882812C293.0033566316718 174.9456368236735 325.7020036724398 121.57374160886678 347 124.13333129882812C367.86521042937926 126.64090885084428 366.2533564011363 181.45269891810034 360 210.13333129882812C354.17633238556346 236.8432226344404 339.66862460545843 262.2594649943187 317 278.1333312988281C268.3801930995041 312.1796983776537 202.0453193038285 288.4336869568607 154 261.1333312988281C138.3096042405168 252.21772014552596 123.26740438192043 242.18351828845508 109 231.13333129882812Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(109 231.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="4.113145922470127" y2="11.273066602325535"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="9.487250473712038" y2="7.34793021530898"></line><circle r="3" class="tanhandle computed" cx="11.85906309214005" cy="9.184912769136226"></circle><circle r="3" class="tanhandle computed" cx="5.141432403087659" cy="14.091333252906919"></circle></g><g class="handle" transform="translate(136 305.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(233 315.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(247 136.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(336 63.133331298828125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(552 145.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(568 227.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(514 288.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(361 309.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(305 184.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(347 124.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(360 210.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(422 136.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(317 278.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(154 261.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

8
static/curves/15.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M184 119.25C143.57541999383673 143.4422822153239 102.1383854218216 189.605521449715 114 238.25C126.88941969723373 291.1095070978943 202.52605695359264 346.12693621876684 243 316.25C293.1748771211046 279.2120568024995 244.29727089828526 144.93541765859604 291 122.25C328.99728591915044 103.79317065515193 387.63288950656005 179.91085907796008 385 232.25C382.48106991760517 282.32374454064376 323.96714509478056 308.9500114508399 279 321.25C240.94862613969917 331.65829836483965 168.94872725694324 343.748043243326 172 371.25C175.36707608634217 401.598378765461 264.3590465710056 398.7629036610924 310 387.25C353.35211640827464 376.3144541313228 393.01473848527843 348.61400140461745 415 309.25C446.534588258376 252.78819372739616 438.4564994967828 178.1396679098558 401 126.25C364.82094130641275 76.13001104954647 292.03598092575004 34.50186578603892 234 63.25C210.91970502507945 74.68282851522257 193.5345975022246 95.58049666668222 184 119.25Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(184 119.25)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-10.29692812810228" y2="6.162245623528495"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="4.4837539755906155" y2="-11.130855775113401"></line><circle r="3" class="tanhandle computed" cx="5.60469246948827" cy="-13.91356971889175"></circle><circle r="3" class="tanhandle computed" cx="-12.87116016012785" cy="7.702807029410618"></circle></g><g class="handle" transform="translate(114 238.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(243 316.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(291 122.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(385 232.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(279 321.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(172 371.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(310 387.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(415 309.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(401 126.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(234 63.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

8
static/curves/16.svg

@ -0,0 +1,8 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M82 164.25C80.68009360686938 205.41465659331558 92.93209108645337 247.66354139982465 120 279.25C152.96852099844455 317.7220824557383 218.7929565110851 353.06828618412777 261 321.25C315.2920366534596 280.3212967087441 270.3141878664967 173.6345498340664 326 137.25C375.22148421599803 105.08917704082111 455.7375994871692 158.7979296732012 473 216.25C487.04199545010084 262.98403965619656 464.384614316276 329.3527522260402 416 341.25C385.15919190781125 348.8334175926972 355.4441864444358 331.43867840615843 331 314.25C279.7819622216067 278.23446796466516 217.55896960126032 219.11226359557674 236 154.25C253.49576748211445 92.71249056075622 330.9739593338736 70.40792427787373 391 70.25C457.3453336823679 70.07545010805632 529.2455049292329 96.88743579194893 565 155.25C586.0039710572339 189.5350768001506 593.0024441699134 232.20471074954722 583 271.25C558.4743594626503 366.98767296936035 430.47358553093227 445.8303379032404 342 401.25C300.6146023004446 380.3966006194457 279.4703775471695 338.3772561737345 251 304.25C230.8302371418174 280.0726366024041 197.320945075042 248.27491606744536 212 218.25C222.93536883782744 195.88251563531657 251.60250776756087 192.9323357004488 262 170.25C272.53372224301705 147.27047582724214 259.40651496075475 114.74716916823897 236 105.25C212.3973020974172 95.67322980659398 188.7867898669622 113.74752026786841 169 126.25C142.12161278439496 143.23337596219085 112.69651591913266 156.0035257807886 82 164.25Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(82 164.25)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-0.3845711901910259" y2="11.99383612526347"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="11.589090706339107" y2="-3.113354557425884"></line><circle r="3" class="tanhandle computed" cx="14.486363382923885" cy="-3.891693196782355"></circle><circle r="3" class="tanhandle computed" cx="-0.4807139877387824" cy="14.992295156579338"></circle></g><g class="handle" transform="translate(120 279.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(261 321.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(326 137.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(473 216.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(416 341.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(331 314.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(236 154.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(391 70.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(565 155.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(583 271.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(342 401.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(251 304.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(212 218.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(262 170.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(236 105.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(169 126.25)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

8
static/curves/17.svg

@ -0,0 +1,8 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M300 123.19999694824219C261.3333333333333 118.19999694824219 222.66666666666666 113.19999694824219 184 108.19999694824219C179.33333333333334 175.5333302815755 174.66666666666666 242.86666361490887 170 310.1999969482422C232.33333333333331 319.1999969482422 294.6666666666667 328.1999969482422 357 337.1999969482422C393 301.5333302815755 429 265.8666636149088 465 230.1999969482422C446.6666666666667 205.86666361490884 428.3333333333333 181.5333302815755 410 157.1999969482422C386.3333333333333 168.1999969482422 362.6666666666667 179.1999969482422 339 190.1999969482422C326 167.86666361490884 313 145.5333302815755 300 123.19999694824219Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(300 123.19999694824219)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-11.900913917212726" y2="-1.5389112823981979"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="6.036825426603061" y2="10.370956502112945"></line><circle r="3" class="tanhandle computed" cx="7.5460317832538255" cy="12.963695627641181"></circle><circle r="3" class="tanhandle computed" cx="-14.876142396515908" cy="-1.923639102997747"></circle></g><g class="handle" transform="translate(184 108.19999694824219)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(170 310.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(357 337.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(465 230.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(410 157.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(339 190.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

8
static/curves/18.svg

@ -0,0 +1,8 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M532 95.63333129882812C555.8082368199341 200.65071259555555 576.1127026933536 385.23897810409767 480 412.6333312988281C379.7287507817008 441.2129668661139 295.8366205890136 257.91816138399776 259 148.63333129882812C258.6666666666667 197.96666463216144 258.3333333333333 247.2999979654948 258 296.6333312988281C271.6666666666667 333.96666463216144 285.3333333333333 371.2999979654948 299 408.6333312988281C344 346.6333312988281 389 284.6333312988281 434 222.63333129882812C416.6666666666667 215.63333129882812 399.3333333333333 208.63333129882812 382 201.63333129882812C362 158.63333129882812 342 115.63333129882811 322 72.63333129882812C356.6666666666667 73.2999979654948 391.33333333333337 73.96666463216145 426 74.63333129882812" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(532 95.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="2.6531638248130696" y2="11.703021905418415"></line><circle r="3" class="tanhandle computed" cx="3.316454781016337" cy="14.62877738177302"></circle></g><g class="handle" transform="translate(259 148.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(258 296.6333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(299 408.6333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(434 222.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(382 201.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(480 412.6333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(322 72.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(426 74.63333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

7
static/curves/2.svg

@ -0,0 +1,7 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M174 103.66668701171875C149 147.66668701171875 124 191.66668701171875 99 235.66668701171875C199.33333333333331 268.66668701171875 299.6666666666667 301.66668701171875 400 334.66668701171875C413.6666666666667 284.33335367838544 427.3333333333333 234.00002034505206 441 183.66668701171875" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(174 103.66668701171875)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(99 235.66668701171875)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(400 334.66668701171875)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(441 183.66668701171875)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-3.1444280080411224" y2="11.580698273517298"></line><circle r="3" class="tanhandle computed" cx="-3.930535010051403" cy="14.475872841896622"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

7
static/curves/3.svg

@ -0,0 +1,7 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M191 281.1999969482422C221.66666666666666 242.1999969482422 252.33333333333334 203.1999969482422 283 164.1999969482422C320 221.5333302815755 357 278.8666636149089 394 336.1999969482422" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(191 281.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(283 164.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(394 336.1999969482422)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-6.5068575979518615" y2="-10.08269825988937"></line><circle r="3" class="tanhandle computed" cx="-8.133571997439827" cy="-12.603372824861712"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

7
static/curves/4.svg

@ -0,0 +1,7 @@
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M145 230.20001220703125C197.33333333333331 212.20001220703125 249.66666666666669 194.20001220703125 302 176.20001220703125C315 228.8666788736979 328 281.5333455403646 341 334.20001220703125C295.3333333333333 340.20001220703125 249.66666666666666 346.20001220703125 204 352.20001220703125" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(145 230.20001220703125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(302 176.20001220703125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(341 334.20001220703125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(204 352.20001220703125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="11.897746926207306" y2="-1.5632076253411085"></line><circle r="3" class="tanhandle computed" cx="14.872183657759134" cy="-1.9540095316763857"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

8
static/curves/5.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M292 238.13333129882812C262.90871041075627 211.119990965959 179.16870974684102 236.48890944225607 181 270.1333312988281C183.15648833099436 309.7522846633173 301.7921638780433 320.7673716934697 347 281.1333312988281C391.9565323293361 241.71961077619244 396.92244957257856 123.61727282342716 358 116.13333129882812C324.6281865035114 109.71665651293542 301.59853675243437 192.80988983652963 292 238.13333129882812Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle selected" transform="translate(292 238.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-8.79352189951559" y2="-8.165413192407335"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="2.48620185305188" y2="-11.739625221696024"></line><circle r="3" class="tanhandle computed" cx="3.10775231631485" cy="-14.67453152712003"></circle><circle r="3" class="tanhandle" cx="-10.991902374394487" cy="-10.206766490509168"></circle></g><g class="handle" transform="translate(181 270.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(347 281.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(358 116.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

8
static/curves/6.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

8
static/curves/7.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M184 196.13333129882812C169.66666666666666 221.46666463216147 155.33333333333331 246.7999979654948 141 272.1333312988281C190.66666666666666 293.1333312988281 240.33333333333334 314.1333312988281 290 335.1333312988281C306 293.1333312988281 322 251.13333129882812 338 209.13333129882812C327 185.79999796549478 316 162.46666463216144 305 139.13333129882812C268.3333333333333 133.79999796549478 231.66666666666666 128.46666463216147 195 123.13333129882812C174.66666666666666 138.13333129882812 154.33333333333331 153.13333129882812 134 168.13333129882812C136.33333333333334 183.46666463216147 138.66666666666666 198.7999979654948 141 214.13333129882812C185.33333333333331 214.13333129882812 229.66666666666669 214.13333129882812 274 214.13333129882812C270 173.13333129882812 266 132.13333129882812 262 91.13333129882812C303 112.13333129882812 344 133.13333129882812 385 154.13333129882812C388.3333333333333 186.79999796549478 391.6666666666667 219.46666463216147 395 252.13333129882812C376.3333333333333 256.46666463216144 357.6666666666667 260.7999979654948 339 265.1333312988281C306.6666666666667 263.7999979654948 274.3333333333333 262.46666463216144 242 261.1333312988281C236.33333333333334 236.79999796549478 230.66666666666666 212.46666463216144 225 188.13333129882812C175 164.13333129882812 124.99999999999999 140.13333129882812 75 116.13333129882812C70.33333333333333 156.13333129882812 65.66666666666667 196.13333129882812 61 236.13333129882812C56.43864782154819 267.0866488403002 62.10012773668091 300.0760213245702 80 326.1333312988281C139.1263829924508 412.2051460373149 291.26219267508054 363.65091227797564 381 316.1333312988281C376.4229847195143 326.0256805777002 373.8130246767921 341.0343049127129 383 348.1333312988281" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(184 196.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(141 272.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(290 335.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(338 209.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(305 139.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(195 123.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(134 168.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(141 214.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(274 214.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(262 91.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(385 154.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(395 252.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(339 265.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(242 261.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(225 188.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(75 116.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(61 236.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(80 326.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(381 316.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-5.038965885053305" y2="10.890767778686174"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="-10.604999020346929" y2="5.615513848121175"></line><circle r="3" class="tanhandle computed" cx="-13.25624877543366" cy="7.019392310151469"></circle><circle r="3" class="tanhandle computed" cx="-6.298707356316632" cy="13.613459723357716"></circle></g><g class="handle" transform="translate(383 348.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-9.495420016706005" y2="-7.337370012909185"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="9.495420016706005" y2="7.337370012909185"></line></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

8
static/curves/8.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M163 134.13333129882812C146.66666666666666 158.46666463216147 130.33333333333331 182.7999979654948 114 207.13333129882812C131.33333333333334 238.46666463216147 148.66666666666669 269.7999979654948 166 301.1333312988281C220.33333333333331 304.1333312988281 274.6666666666667 307.1333312988281 329 310.1333312988281C388.6666666666667 287.1333312988281 448.33333333333337 264.1333312988281 508 241.13333129882812C444.93263014072016 266.63074251821774 349.87857169919766 278.917528291535 309 219.13333129882812C293.3498312299803 196.24523286914808 288.66318908081786 167.21473921989502 293 140.13333129882812C282 168.79999796549478 271 197.46666463216147 260 226.13333129882812C256 187.13333129882812 252 148.13333129882812 248 109.13333129882812C227 148.13333129882812 206 187.13333129882812 185 226.13333129882812C185.66666666666666 200.13333129882812 186.33333333333334 174.13333129882812 187 148.13333129882812C182.953613992925 168.6761939619 173.75583175008686 208.18982066684114 163 206.13333129882812C151.45492372532073 203.92594026079095 158.59561313543708 157.7398071870216 163 134.13333129882812Z" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(163 134.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(114 207.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(166 301.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(329 310.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(508 241.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(293 140.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(260 226.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(248 109.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(185 226.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(187 148.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(163 206.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle selected" transform="translate(309 219.13333129882812)"><circle cx="0" cy="0" r="4" class="handle"></circle><line pointer-events="none" x1="0" y1="0" class="tan" x2="-6.773226651043796" y2="-9.905725654064423"></line><line pointer-events="none" x1="0" y1="0" class="tan" x2="6.773226651043796" y2="9.905725654064423"></line><circle r="3" class="tanhandle computed" cx="8.466533313804746" cy="12.382157067580529"></circle><circle r="3" class="tanhandle computed" cx="-8.466533313804746" cy="-12.382157067580529"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

8
static/curves/9.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M355 328.1333312988281C269.33333333333337 350.46666463216144 183.66666666666666 372.7999979654948 98 395.1333312988281C88.66666666666667 352.46666463216144 79.33333333333333 309.79999796549475 70 267.1333312988281C93.33333333333333 229.7999979654948 116.66666666666667 192.46666463216144 140 155.13333129882812C236 115.7999979654948 332 76.46666463216145 428 37.133331298828125C451.3333333333333 91.46666463216145 474.6666666666667 145.7999979654948 498 200.13333129882812C449.6666666666667 206.13333129882812 401.3333333333333 212.13333129882812 353 218.13333129882812C316.3333333333333 239.46666463216147 279.66666666666663 260.7999979654948 243 282.1333312988281C246.66666666666666 298.7999979654948 250.33333333333334 315.46666463216144 254 332.1333312988281C266.06641549125595 366.5562907758927 290.4492210276394 397.6126477743086 324 413.1333312988281C385.1108143781959 441.4033637374492 457.9466461616112 409.484848043744 505 366.1333312988281C520 337.1333312988281 535 308.1333312988281 550 279.1333312988281" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(355 328.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(98 395.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(70 267.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(140 155.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(428 37.133331298828125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(498 200.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(353 218.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(243 282.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(254 332.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(324 413.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(505 366.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(550 279.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-5.513075026485199" y2="10.658611717871386"></line><circle r="3" class="tanhandle computed" cx="-6.891343783106499" cy="13.323264647339233"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

8
static/curves/test.svg

@ -0,0 +1,8 @@
<svg id="s" width="640" height="480" pointer-events="all">
<!-- <g id="grid" /> -->
<path id="ctrlpoly" d="" stroke="none" fill="none"></path>
<path id="spline" d="M355 328.1333312988281C269.33333333333337 350.46666463216144 183.66666666666666 372.7999979654948 98 395.1333312988281C88.66666666666667 352.46666463216144 79.33333333333333 309.79999796549475 70 267.1333312988281C93.33333333333333 229.7999979654948 116.66666666666667 192.46666463216144 140 155.13333129882812C236 115.7999979654948 332 76.46666463216145 428 37.133331298828125C451.3333333333333 91.46666463216145 474.6666666666667 145.7999979654948 498 200.13333129882812C449.6666666666667 206.13333129882812 401.3333333333333 212.13333129882812 353 218.13333129882812C316.3333333333333 239.46666463216147 279.66666666666663 260.7999979654948 243 282.1333312988281C246.66666666666666 298.7999979654948 250.33333333333334 315.46666463216144 254 332.1333312988281C266.06641549125595 366.5562907758927 290.4492210276394 397.6126477743086 324 413.1333312988281C385.1108143781959 441.4033637374492 457.9466461616112 409.484848043744 505 366.1333312988281C520 337.1333312988281 535 308.1333312988281 550 279.1333312988281" stroke="black" fill="none" stroke-width="2"></path>
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2"></path>
<g id="handles"><g class="handle" transform="translate(355 328.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(98 395.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(70 267.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(140 155.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(428 37.133331298828125)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(498 200.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(353 218.13333129882812)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(243 282.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(254 332.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle" transform="translate(324 413.1333312988281)"><circle cx="0" cy="0" r="4" class="handle"></circle></g><g class="handle" transform="translate(505 366.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect></g><g class="handle selected" transform="translate(550 279.1333312988281)"><rect x="-4" y="-4" width="8" height="8" class="handle"></rect><line pointer-events="none" x1="0" y1="0" class="tan" x2="-5.513075026485199" y2="10.658611717871386"></line><circle r="3" class="tanhandle computed" cx="-6.891343783106499" cy="13.323264647339233"></circle></g></g>
<g id="plots"></g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

1
static/fonts/Brazil/.uuid

@ -0,0 +1 @@
2f3c42b9-3fc0-4acd-a79a-bb5cd3ef114f

BIN
static/fonts/Brazil/BRAZIL BERN 18.11.2010 SHP.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

23
static/fonts/Brazil/BRAZIL description and license.rtf

@ -0,0 +1,23 @@
{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf470
{\fonttbl\f0\fnil\fcharset0 Brazil-Regular;}
{\colortbl;\red255\green255\blue255;\red53\green53\blue53;\red251\green0\blue7;}
\paperw11900\paperh16840\margl1440\margr1440\vieww11460\viewh12380\viewkind0
\deftab560
\pard\pardeftab560\sl192\slmult1\partightenfactor0
\f0\fs48 \cf2 \
bRAZIL: motivated by a tag seen on the wall of an Amsterdam fondue cafE toilet in 2010. Designed in collaboration with Bram van den Berg. \cf0 \kerning1\expnd2\expndtw12
\dn4 \super \
\pard\pardeftab720\ri-2323\sl192\slmult1\partightenfactor0
\cf0 \
This work is licensed under an SIL Open Font License (OFL). For details see here {\field{\*\fldinst{HYPERLINK "http://scripts.sil.org/OFL"}}{\fldrslt \cf3 \expnd0\expndtw0\kerning0
\up0 \nosupersub \ul \ulc3 http://scripts.sil.org/OFL}} \
\
For a free printed type specimen, send a self addressed, return envelope to: Colophon, Willemsstraat 32, 1015 JD Amsterdam, The Netherlands.\
\pard\pardeftab720\ri-1780\sl288\slmult1\partightenfactor0
\cf0 \kerning1\expnd13\expndtw68
\up0 \nosupersub \
\pard\pardeftab720\ri-2323\sl192\slmult1\partightenfactor0
\cf0 \kerning1\expnd2\expndtw12
\dn4 \super \
}

BIN
static/fonts/Brazil/Brazil-Regular.otf

Binary file not shown.

95
static/fonts/Brazil/OFL Brazil.txt

@ -0,0 +1,95 @@
Copyright (c) <2016>, <Colophon> (<www.colophon.info>),
with Reserved Font Name <Brazil>.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

427
static/fonts/Brazil/OFL-FAQ.txt

@ -0,0 +1,427 @@
OFL FAQ - Frequently Asked Questions about the SIL Open Font License (OFL)
Version 1.1-update4 - Sept 2014
(See http://scripts.sil.org/OFL for updates)
CONTENTS OF THIS FAQ
1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL
2 USING OFL FONTS FOR WEB PAGES AND ONLINE WEB FONT SERVICES
3 MODIFYING OFL-LICENSED FONTS
4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL
5 CHOOSING RESERVED FONT NAMES
6 ABOUT THE FONTLOG
7 MAKING CONTRIBUTIONS TO OFL PROJECTS
8 ABOUT THE LICENSE ITSELF
9 ABOUT SIL INTERNATIONAL
APPENDIX A - FONTLOG EXAMPLE
1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL
1.1 Can I use the fonts for a book or other print publication, to create logos or other graphics or even to manufacture objects based on their outlines?
Yes. You are very welcome to do so. Authors of fonts released under the OFL allow you to use their font software as such for any kind of design work. No additional license or permission is required, unlike with some other licenses. Some examples of these uses are: logos, posters, business cards, stationery, video titling, signage, t-shirts, personalised fabric, 3D-printed/laser-cut shapes, sculptures, rubber stamps, cookie cutters and lead type.
1.1.1 Does that restrict the license or distribution of that artwork?
No. You remain the author and copyright holder of that newly derived graphic or object. You are simply using an open font in the design process. It is only when you redistribute, bundle or modify the font itself that other conditions of the license have to be respected (see below for more details).
1.1.2 Is any kind of acknowledgement required?
No. Font authors may appreciate being mentioned in your artwork's acknowledgements alongside the name of the font, possibly with a link to their website, but that is not required.
1.2 Can the fonts be included with Free/Libre and Open Source Software collections such as GNU/Linux and BSD distributions and repositories?
Yes! Fonts licensed under the OFL can be freely included alongside other software under FLOSS (Free/Libre and Open Source Software) licenses. Since fonts are typically aggregated with, not merged into, existing software, there is little need to be concerned about incompatibility with existing software licenses. You may also repackage the fonts and the accompanying components in a .rpm or .deb package (or other similar package formats or installers) and include them in distribution CD/DVDs and online repositories. (Also see section 5.9 about rebuilding from source.)
1.3 I want to distribute the fonts with my program. Does this mean my program also has to be Free/Libre and Open Source Software?
No. Only the portions based on the Font Software are required to be released under the OFL. The intent of the license is to allow aggregation or bundling with software under restricted licensing as well.
1.4 Can I sell a software package that includes these fonts?
Yes, you can do this with both the Original Version and a Modified Version of the fonts. Examples of bundling made possible by the OFL would include: word processors, design and publishing applications, training and educational software, games and entertainment software, mobile device applications, etc.
1.5 Can I include the fonts on a CD of freeware or commercial fonts?
Yes, as long some other font or software is also on the disk, so the OFL font is not sold by itself.
1.6 Why won't the OFL let me sell the fonts alone?
The intent is to keep people from making money by simply redistributing the fonts. The only people who ought to profit directly from the fonts should be the original authors, and those authors have kindly given up potential direct income to distribute their fonts under the OFL. Please honour and respect their contribution!
1.7 What about sharing OFL fonts with friends on a CD, DVD or USB stick?
You are very welcome to share open fonts with friends, family and colleagues through removable media. Just remember to include the full font package, including any copyright notices and licensing information as available in OFL.txt. In the case where you sell the font, it has to come bundled with software.
1.8 Can I host the fonts on a web site for others to use?
Yes, as long as you make the full font package available. In most cases it may be best to point users to the main site that distributes the Original Version so they always get the most recent stable and complete version. See also discussion of web fonts in Section 2.
1.9 Can I host the fonts on a server for use over our internal network?
Yes. If the fonts are transferred from the server to the client computer by means that allow them to be used even if the computer is no longer attached to the network, the full package (copyright notices, licensing information, etc.) should be included.
1.10 Does the full OFL license text always need to accompany the font?
The only situation in which an OFL font can be distributed without the text of the OFL (either in a separate file or in font metadata), is when a font is embedded in a document or bundled within a program. In the case of metadata included within a font, it is legally sufficient to include only a link to the text of the OFL on http://scripts.sil.org/OFL, but we strongly recommend against this. Most modern font formats include metadata fields that will accept the full OFL text, and full inclusion increases the likelihood that users will understand and properly apply the license.
1.11 What do you mean by 'embedding'? How does that differ from other means of distribution?
By 'embedding' we mean inclusion of the font in a document or file in a way that makes extraction (and redistribution) difficult or clearly discouraged. In many cases the names of embedded fonts might also not be obvious to those reading the document, the font data format might be altered, and only a subset of the font - only the glyphs required for the text - might be included. Any other means of delivering a font to another person is considered 'distribution', and needs to be accompanied by any copyright notices and licensing information available in OFL.txt.
1.12 So can I embed OFL fonts in my document?
Yes, either in full or a subset. The restrictions regarding font modification and redistribution do not apply, as the font is not intended for use outside the document.
1.13 Does embedding alter the license of the document itself?
No. Referencing or embedding an OFL font in any document does not change the license of the document itself. The requirement for fonts to remain under the OFL does not apply to any document created using the fonts and their derivatives. Similarly, creating any kind of graphic using a font under OFL does not make the resulting artwork subject to the OFL.
1.14 If OFL fonts are extracted from a document in which they are embedded (such as a PDF file), what can be done with them? Is this a risk to author(s)?
The few utilities that can extract fonts embedded in a PDF will typically output limited amounts of outlines - not a complete font. To create a working font from this method is much more difficult and time consuming than finding the source of the original OFL font. So there is little chance that an OFL font would be extracted and redistributed inappropriately through this method. Even so, copyright laws address any misrepresentation of authorship. All Font Software released under the OFL and marked as such by the author(s) is intended to remain under this license regardless of the distribution method, and cannot be redistributed under any other license. We strongly discourage any font extraction - we recommend directly using the font sources instead - but if you extract font outlines from a document, please be considerate: respect the work of the author(s) and the licensing model.
1.15 What about distributing fonts with a document? Within a compressed folder structure? Is it distribution, bundling or embedding?
Certain document formats may allow the inclusion of an unmodified font within their file structure which may consist of a compressed folder containing the various resources forming the document (such as pictures and thumbnails). Including fonts within such a structure is understood as being different from embedding but rather similar to bundling (or mere aggregation) which the license explicitly allows. In this case the font is conveyed unchanged whereas embedding a font usually transforms it from the original format. The OFL does not allow anyone to extract the font from such a structure to then redistribute it under another license. The explicit permission to redistribute and embed does not cancel the requirement for the Font Software to remain under the license chosen by its author(s). Even if the font travels inside the document as one of its assets, it should not lose its authorship information and licensing.
1.16 What about ebooks shipping with open fonts?
The requirements differ depending on whether the fonts are linked, embedded or distributed (bundled or aggregated). Some ebook formats use web technologies to do font linking via @font-face, others are designed for font embedding, some use fonts distributed with the document or reading software, and a few rely solely on the fonts already present on the target system. The license requirements depend on the type of inclusion as discussed in 1.15.
1.17 Can Font Software released under the OFL be subject to URL-based access restrictions methods or DRM (Digital Rights Management) mechanisms?
Yes, but these issues are out-of-scope for the OFL. The license itself neither encourages their use nor prohibits them since such mechanisms are not implemented in the components of the Font Software but through external software. Such restrictions are put in place for many different purposes corresponding to various usage scenarios. One common example is to limit potentially dangerous cross-site scripting attacks. However, in the spirit of libre/open fonts and unrestricted writing systems, we strongly encourage open sharing and reuse of OFL fonts, and the establishment of an environment where such restrictions are unnecessary. Note that whether you wish to use such mechanisms or you prefer not to, you must still abide by the rules set forth by the OFL when using fonts released by their authors under this license. Derivative fonts must be licensed under the OFL, even if they are part of a service for which you charge fees and/or for which access to source code is restricted. You may not sell the fonts on their own - they must be part of a larger software package, bundle or subscription plan. For example, even if the OFL font is distributed in a software package or via an online service using a DRM mechanism, the user would still have the right to extract that font, use, study, modify and redistribute it under the OFL.
1.18 I've come across a font released under the OFL. How can I easily get more information about the Original Version? How can I know where it stands compared to the Original Version or other Modified Versions?
Consult the copyright statement(s) in the license for ways to contact the original authors. Consult the FONTLOG (see section 6 for more details and examples) for information on how the font differs from the Original Version, and get in touch with the various contributors via the information in the acknowledgement section. Please consider using the Original Versions of the fonts whenever possible.
1.19 What do you mean in condition 4 of the OFL's permissions and conditions? Can you provide examples of abusive promotion / endorsement / advertisement vs. normal acknowledgement?
The intent is that the goodwill and reputation of the author(s) should not be used in a way that makes it sound like the original author(s) endorse or approve of a specific Modified Version or software bundle. For example, it would not be right to advertise a word processor by naming the author(s) in a listing of software features, or to promote a Modified Version on a web site by saying "designed by ...". However, it would be appropriate to acknowledge the author(s) if your software package has a list of people who deserve thanks. We realize that this can seem to be a grey area, but the standard used to judge an acknowledgement is that if the acknowledgement benefits the author(s) it is allowed, but if it primarily benefits other parties, or could reflect poorly on the author(s), then it is not.
1.20 I'm writing a small app for mobile platforms, do I need to include the whole package?
If you bundle a font under the OFL with your mobile app you must comply with the terms of the license. At a minimum you must include the copyright statement, the license notice and the license text. A mention of this information in your About box or Changelog, with a link to where the font package is from, is good practice, and the extra space needed to carry these items is very small. You do not, however, need to include the full contents of the font package - only the fonts you use and the copyright and license that apply to them. For example, if you only use the regular weight in your app, you do not need to include the italic and bold versions.
1.21 What about including OFL fonts by default in my firmware or dedicated operating system?
Many such systems are restricted and turned into appliances so that users cannot study or modify them. Using open fonts to increase quality and language coverage is a great idea, but you need to be aware that if there is a way for users to extract fonts you cannot legally prevent them from doing that. The fonts themselves, including any changes you make to them, must be distributed under the OFL even if your firmware has a more restrictive license. If you do transform the fonts and change their formats when you include them in your firmware you must respect any names reserved by the font authors via the RFN mechanism and pick your own font name. Alternatively if you directly add a font under the OFL to the font folder of your firmware without modifying or optimizing it you are simply bundling the font like with any other software collection, and do not need to make any further changes.
1.22 Can I make and publish CMS themes or templates that use OFL fonts? Can I include the fonts themselves in the themes or templates? Can I sell the whole package?
Yes, you are very welcome to integrate open fonts into themes and templates for your preferred CMS and make them more widely available. Remember that you can only sell the fonts and your CMS add-on as part of a software bundle. (See 1.4 for details and examples about selling bundles).
1.23 Can OFL fonts be included in services that deliver fonts to the desktop from remote repositories? Even if they contain both OFL and non-OFL fonts?
Yes. Some foundries have set up services to deliver fonts to subscribers directly to desktops from their online repositories; similarly, plugins are available to preview and use fonts directly in your design tool or publishing suite. These services may mix open and restricted fonts in the same channel, however they should make a clear distinction between them to users. These services should also not hinder users (such as through DRM or obfuscation mechanisms) from extracting and using the OFL fonts in other environments, or continuing to use OFL fonts after subscription terms have ended, as those uses are specifically allowed by the OFL.
1.24 Can services that provide or distribute OFL fonts restrict my use of them?
No. The terms of use of such services cannot replace or restrict the terms of the OFL, as that would be the same as distributing the fonts under a different license, which is not allowed. You are still entitled to use, modify and redistribute them as the original authors have intended outside of the sole control of that particular distribution channel. Note, however, that the fonts provided by these services may differ from the Original Versions.
2 USING OFL FONTS FOR WEBPAGES AND ONLINE WEB FONT SERVICES
NOTE: This section often refers to a separate paper on 'Web Fonts & RFNs'. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.1 Can I make webpages using these fonts?
Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are:
- referring directly in your stylesheet to open fonts which may be available on the user's system
- providing links to download the full package of the font - either from your own website or from elsewhere - so users can install it themselves
- using @font-face to distribute the font directly to browsers. This is recommended and explicitly allowed by the licensing model because it is distribution. The font file itself is distributed with other components of the webpage. It is not embedded in the webpage but referenced through a web address which will cause the browser to retrieve and use the corresponding font to render the webpage (see 1.11 and 1.15 for details related to embedding fonts into documents). As you take advantage of the @font-face cross-platform standard, be aware that web fonts are often tuned for a web environment and not intended for installation and use outside a browser. The reasons in favour of using web fonts are to allow design of dynamic text elements instead of static graphics, to make it easier for content to be localized and translated, indexed and searched, and all this with cross-platform open standards without depending on restricted extensions or plugins. You should check the CSS cascade (the order in which fonts are being called or delivered to your users) when testing.
2.2 Can I make and use WOFF (Web Open Font Format) versions of OFL fonts?
Yes, but you need to be careful. A change in font format normally is considered modification, and Reserved Font Names (RFNs) cannot be used. Because of the design of the WOFF format, however, it is possible to create a WOFF version that is not considered modification, and so would not require a name change. You are allowed to create, use and distribute a WOFF version of an OFL font without changing the font name, but only if:
- the original font data remains unchanged except for WOFF compression, and
- WOFF-specific metadata is either omitted altogether or present and includes, unaltered, the contents of all equivalent metadata in the original font.
If the original font data or metadata is changed, or the WOFF-specific metadata is incomplete, the font must be considered a Modified Version, the OFL restrictions would apply and the name of the font must be changed: any RFNs cannot be used and copyright notices and licensing information must be included and cannot be deleted or modified. You must come up with a unique name - we recommend one corresponding to your domain or your particular web application. Be aware that only the original author(s) can use RFNs. This is to prevent collisions between a derivative tuned to your audience and the original upstream version and so to reduce confusion.
Please note that most WOFF conversion tools and online services do not meet the two requirements listed above, and so their output must be considered a Modified Version. So be very careful and check to be sure that the tool or service you're using is compressing unchanged data and completely and accurately reflecting the original font metadata.
2.3 What about other web font formats such as EOT/EOTLite/CWT/etc.?
In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.4 Can I make OFL fonts available through web font online services?
Yes, you are welcome to include OFL fonts in online web font services as long as you properly meet all the conditions of the license. The origin and open status of the font should be clear among the other fonts you are hosting. Authorship, copyright notices and license information must be sufficiently visible to your users or subscribers so they know where the font comes from and the rights granted by the author(s). Make sure the font file contains the needed copyright notice(s) and licensing information in its metadata. Please double-check the accuracy of every field to prevent contradictory information. Other font formats, including EOT/EOTLite/CWT and superior alternatives like WOFF, already provide fields for this information. Remember that if you modify the font within your library or convert it to another format for any reason the OFL restrictions apply and you need to change the names accordingly. Please respect the author's wishes as expressed in the OFL and do not misrepresent original designers and their work. Don't lump quality open fonts together with dubious freeware or public domain fonts. Consider how you can best work with the original designers and foundries, support their efforts and generate goodwill that will benefit your service. (See 1.17 for details related to URL-based access restrictions methods or DRM mechanisms).
2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed?
Yes, it is permitted, but remember that these optimized versions are Modified Versions and so must follow OFL requirements like appropriate renaming. Also you need to bear in mind the other important parameters beyond compression, speed and responsiveness: you need to consider the audience of your particular website or web application, as choosing some optimization parameters may turn out to be less than ideal for them. Subsetting by removing certain glyphs or features may seriously limit functionality of the font in various languages that your users expect. It may also introduce degradation of quality in the rendering or specific bugs on the various target platforms compared to the original font from upstream. In other words, remember that one person's optimized font may be another person's missing feature. Various advanced typographic features (OpenType, Graphite or AAT) are also available through CSS and may provide the desired effects without the need to modify the font.
2.6 Is subsetting a web font considered modification?
Yes. Removing any parts of the font when delivering a web font to a browser, including unused glyphs and smart font code, is considered modification. This is permitted by the OFL but would not normally allow the use of RFNs. Some newer subsetting technologies may be able to subset in a way that allows users to effectively have access to the complete font, including smart font behaviour. See 2.8 and http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.7 Are there any situations in which a modified web font could use RFNs?
Yes. If a web font is optimized only in ways that preserve Functional Equivalence (see 2.8), then it may use RFNs, as it reasonably represents the Original Version and respects the intentions of the author(s) and the main purposes of the RFN mechanism (avoids collisions, protects authors, minimizes support, encourages derivatives). However this is technically very difficult and often impractical, so a much better scenario is for the web font service or provider to sign a separate agreement with the author(s) that allows the use of RFNs for Modified Versions.
2.8 How do you know if an optimization to a web font preserves Functional Equivalence?
Functional Equivalence is described in full in the 'Web fonts and RFNs' paper at http://scripts.sil.org/OFL_web_fonts_and_RFNs, in general, an optimized font is deemed to be Functionally Equivalent (FE) to the Original Version if it:
- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly.
- Provides the same smart font behavior. Any dynamic shaping behavior that works with the Original Version should work when optimized, unless the browser or environment does not support it. There does not need to be guaranteed support in the client, but there should be no forced degradation of smart font or shaping behavior, such as the removal or obfuscation of OpenType, Graphite or AAT tables.
- Presents text with no obvious degradation in visual quality. The lettershapes should be equally (or more) readable, within limits of the rendering platform.
- Preserves original author, project and license metadata. At a minimum, this should include: Copyright and authorship; The license as stated in the Original Version, whether that is the full text of the OFL or a link to the web version; Any RFN declarations; Information already present in the font or documentation that points back to the Original Version, such as a link to the project or the author's website.
If an optimized font meets these requirements, and so is considered to be FE, then it's very likely that the original author would feel that the optimized font is a good and reasonable equivalent. If it falls short of any of these requirements, the optimized font does not reasonably represent the Original Version, and so should be considered to be a Modified Version. Like other Modified Versions, it would not be allowed to use any RFNs and you simply need to pick your own font name.
2.9 Isn't use of web fonts another form of embedding?
No. Unlike embedded fonts in a PDF, web fonts are not an integrated part of the document itself. They are not specific to a single document and are often applied to thousands of documents around the world. The font data is not stored alongside the document data and often originates from a different location. The ease by which the web fonts used by a document may be identified and downloaded for desktop use demonstrates that they are philosophically and technically separate from the web pages that specify them. See http://scripts.sil.org/OFL_web_fonts_and_RFNs
2.10 So would it be better to not use RFNs at all if you want your font to be distributed by a web fonts service?
No. Although the OFL does not require authors to use RFNs, the RFN mechanism is an important part of the OFL model and completely compatible with web font services. If that web font service modifies the fonts, then the best solution is to sign a separate agreement for the use of any RFNs. It is perfectly valid for an author to not declare any RFNs, but before they do so they need to fully understand the benefits they are giving up, and the overall negative effect of allowing many different versions bearing the same name to be widely distributed. As a result, we don't generally recommend it.
2.11 What should an agreement for the use of RFNs say? Are there any examples?
There is no prescribed format for this agreement, as legal systems vary, and no recommended examples. Authors may wish to add specific clauses to further restrict use, require author review of Modified Versions, establish user support mechanisms or provide terms for ending the agreement. Such agreements are usually not public, and apply only to the main parties. However, it would be very beneficial for web font services to clearly state when they have established such agreements, so that the public understands clearly that their service is operating appropriately.
See the separate paper on 'Web Fonts & RFNs' for in-depth discussion of issues related to the use of RFNs for web fonts. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs
3 MODIFYING OFL-LICENSED FONTS
3.1 Can I change the fonts? Are there any limitations to what things I can and cannot change?
You are allowed to change anything, as long as such changes do not violate the terms of the license. In other words, you are not allowed to remove the copyright statement(s) from the font, but you could put additional information into it that covers your contribution. See the placeholders in the OFL header template for recommendations on where to add your own statements. (Remember that, when authors have reserved names via the RFN mechanism, you need to change the internal names of the font to your own font name when making your modified version even if it is just a small change.)
3.2 I have a font that needs a few extra glyphs - can I take them from an OFL licensed font and copy them into mine?
Yes, but if you distribute that font to others it must be under the OFL, and include the information mentioned in condition 2 of the license.
3.3 Can I charge people for my additional work? In other words, if I add a bunch of special glyphs or OpenType/Graphite/AAT code, can I sell the enhanced font?
Not by itself. Derivative fonts must be released under the OFL and cannot be sold by themselves. It is permitted, however, to include them in a larger software package (such as text editors, office suites or operating systems), even if the larger package is sold. In that case, you are strongly encouraged, but not required, to also make that derived font easily and freely available outside of the larger package.
3.4 Can I pay someone to enhance the fonts for my use and distribution?
Yes. This is a good way to fund the further development of the fonts. Keep in mind, however, that if the font is distributed to others it must be under the OFL. You won't be able to recover your investment by exclusively selling the font, but you will be making a valuable contribution to the community. Please remember how you have benefited from the contributions of others.
3.5 I need to make substantial revisions to the font to make it work with my program. It will be a lot of work, and a big investment, and I want to be sure that it can only be distributed with my program. Can I restrict its use?
No. If you redistribute a Modified Version of the font it must be under the OFL. You may not restrict it in any way beyond what the OFL permits and requires. This is intended to ensure that all released improvements to the fonts become available to everyone. But you will likely get an edge over competitors by being the first to distribute a bundle with the enhancements. Again, please remember how you have benefited from the contributions of others.
3.6 Do I have to make any derivative fonts (including extended source files, build scripts, documentation, etc.) publicly available?
No, but please consider sharing your improvements with others. You may find that you receive in return more than what you gave.
3.7 If a trademark is claimed in the OFL font, does that trademark need to remain in modified fonts?
Yes. Any trademark notices must remain in any derivative fonts to respect trademark laws, but you may add any additional trademarks you claim, officially registered or not. For example if an OFL font called "Foo" contains a notice that "Foo is a trademark of Acme", then if you rename the font to "Bar" when creating a Modified Version, the new trademark notice could say "Foo is a trademark of Acme Inc. - Bar is a trademark of Roadrunner Technologies Ltd.". Trademarks work alongside the OFL and are not subject to the terms of the licensing agreement. The OFL does not grant any rights under trademark law. Bear in mind that trademark law varies from country to country and that there are no international trademark conventions as there are for copyright. You may need to significantly invest in registering and defending a trademark for it to remain valid in the countries you are interested in. This may be costly for an individual independent designer.
3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names?
Only if there are declared RFNs. Making a public commit or publishing a public branch is effectively redistributing your modifications, so any change to the font will require that you do not use the RFNs. Even if there are no RFNs, it may be useful to change the name or add a suffix indicating that a particular version of the font is still in development and not released yet. This will clearly indicate to users and fellow designers that this particular font is not ready for release yet. See section 5 for more details.
4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL
4.1 Can I use the SIL OFL for my own fonts?
Yes! We heartily encourage everyone to use the OFL to distribute their own original fonts. It is a carefully constructed license that allows great freedom along with enough artistic integrity protection for the work of the authors as well as clear rules for other contributors and those who redistribute the fonts. The licensing model is used successfully by various organisations, both for-profit and not-for-profit, to release fonts of varying levels of scope and complexity.
4.2 What do I have to do to apply the OFL to my font?
If you want to release your fonts under the OFL, we recommend you do the following:
4.2.1 Put your copyright and Reserved Font Names information at the beginning of the main OFL.txt file in place of the dedicated placeholders (marked with the <> characters). Include this file in your release package.
4.2.2 Put your copyright and the OFL text with your chosen Reserved Font Name(s) into your font files (the copyright and license fields). A link to the OFL text on the OFL web site is an acceptable (but not recommended) alternative. Also add this information to any other components (build scripts, glyph databases, documentation, test files, etc). Accurate metadata in your font files is beneficial to you as an increasing number of applications are exposing this information to the user. For example, clickable links can bring users back to your website and let them know about other work you have done or services you provide. Depending on the format of your fonts and sources, you can use template human-readable headers or machine-readable metadata. You should also double-check that there is no conflicting metadata in the font itself contradicting the license, such as the fstype bits in the os2 table or fields in the name table.
4.2.3 Write an initial FONTLOG.txt for your font and include it in the release package (see Section 6 and Appendix A for details including a template).
4.2.4 Include the relevant practical documentation on the license by adding the current OFL-FAQ.txt file in your package.
4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website.
4.3 Will you make my font OFL for me?
We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you.
4.4 Will you distribute my OFL font for me?
No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness.
4.5 Why should I use the OFL for my fonts?
- to meet needs for fonts that can be modified to support lesser-known languages
- to provide a legal and clear way for people to respect your work but still use it (and reduce piracy)
- to involve others in your font project
- to enable your fonts to be expanded with new weights and improved writing system/language support
- to allow more technical font developers to add features to your design (such as OpenType, Graphite or AAT support)
- to renew the life of an old font lying on your hard drive with no business model
- to allow your font to be included in Libre Software operating systems like Ubuntu
- to give your font world status and wide, unrestricted distribution
- to educate students about quality typeface and font design
- to expand your test base and get more useful feedback
- to extend your reach to new markets when users see your metadata and go to your website
- to get your font more easily into one of the web font online services
- to attract attention for your commercial fonts
- to make money through web font services
- to make money by bundling fonts with applications
- to make money adjusting and extending existing open fonts
- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you
- to be part of a sharing design and development community
- to give back and contribute to a growing body of font sources
5 CHOOSING RESERVED FONT NAMES
5.1 What are Reserved Font Names?
These are font names, or portions of font names, that the author has chosen to reserve for use only with the Original Version of the font, or for Modified Version(s) created by the original author.
5.2 Why can't I use the Reserved Font Names in my derivative font names? I'd like people to know where the design came from.
The best way to acknowledge the source of the design is to thank the original authors and any other contributors in the files that are distributed with your revised font (although no acknowledgement is required). The FONTLOG is a natural place to do this. Reserved Font Names ensure that the only fonts that have the original names are the unmodified Original Versions. This allows designers to maintain artistic integrity while allowing collaboration to happen. It eliminates potential confusion and name conflicts. When choosing a name, be creative and avoid names that reuse almost all the same letters in the same order or sound like the original. It will help everyone if Original Versions and Modified Versions can easily be distinguished from one another and from other derivatives. Any substitution and matching mechanism is outside the scope of the license.
5.3 What do you mean by "primary name as presented to the user"? Are you referring to the font menu name?
Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer.
5.4 Am I not allowed to use any part of the Reserved Font Names?
You may not use individual words from the Reserved Font Names, but you would be allowed to use parts of words, as long as you do not use any word from the Reserved Font Names entirely. We do not recommend using parts of words because of potential confusion, but it is allowed. For example, if "Foobar" was a Reserved Font Name, you would be allowed to use "Foo" or "bar", although we would not recommend it. Such an unfortunate choice would confuse the users of your fonts as well as make it harder for other designers to contribute.
5.5 So what should I, as an author, identify as Reserved Font Names?
Original authors are encouraged to name their fonts using clear, distinct names, and only declare the unique parts of the name as Reserved Font Names. For example, the author of a font called "Foobar Sans" would declare "Foobar" as a Reserved Font Name, but not "Sans", as that is a common typographical term, and may be a useful word to use in a derivative font name. Reserved Font Names should also be single words for simplicity and legibility. A font called "Flowing River" should have Reserved Font Names "Flowing" and "River", not "Flowing River". You also need to be very careful about reserving font names which are already linked to trademarks (whether registered or not) which you do not own.
5.6 Do I, as an author, have to identify any Reserved Font Names?
No. RFNs are optional and not required, but we encourage you to use them. This is primarily to avoid confusion between your work and Modified Versions. As an author you can release a font under the OFL and not declare any Reserved Font Names. There may be situations where you find that using no RFNs and letting your font be changed and modified - including any kind of modification - without having to change the original name is desirable. However you need to be fully aware of the consequences. There will be no direct way for end-users and other designers to distinguish your Original Version from many Modified Versions that may be created. You have to trust whoever is making the changes and the optimizations to not introduce problematic changes. The RFNs you choose for your own creation have value to you as an author because they allow you to maintain artistic integrity and keep some control over the distribution channel to your end-users. For discussion of RFNs and web fonts see section 2.
5.7 Are any names (such as the main font name) reserved by default?
No. That is a change to the license as of version 1.1. If you want any names to be Reserved Font Names, they must be specified after the copyright statement(s).
5.8 Is there any situation in which I can use Reserved Font Names for a Modified Version?
The Copyright Holder(s) can give certain trusted parties the right to use any of the Reserved Font Names through separate written agreements. For example, even if "Foobar" is a RFN, you could write up an agreement to give company "XYZ" the right to distribute a modified version with a name that includes "Foobar". This allows for freedom without confusion. The existence of such an agreement should be made as clear as possible to downstream users and designers in the distribution package and the relevant documentation. They need to know if they are a party to the agreement or not and what they are practically allowed to do or not even if all the details of the agreement are not public.
5.9 Do font rebuilds require a name change? Do I have to change the name of the font when my packaging workflow includes a full rebuild from source?
Yes, all rebuilds which change the font data and the smart code are Modified Versions and the requirements of the OFL apply: you need to respect what the Author(s) have chosen in terms of Reserved Font Names. However if a package (or installer) is simply a wrapper or a compressed structure around the final font - leaving them intact on the inside - then no name change is required. Please get in touch with the author(s) and copyright holder(s) to inquire about the presence of font sources beyond the final font file(s) and the recommended build path. That build path may very well be non-trivial and hard to reproduce accurately by the maintainer. If a full font build path is made available by the upstream author(s) please be aware that any regressions and changes you may introduce when doing a rebuild for packaging purposes is your own responsibility as a package maintainer since you are effectively creating a separate branch. You should make it very clear to your users that your rebuilt version is not the canonical one from upstream.
5.10 Can I add other Reserved Font Names when making a derivative font?
Yes. List your additional Reserved Font Names after your additional copyright statement, as indicated with example placeholders at the top of the OFL.txt file. Be sure you do not remove any existing RFNs but only add your own. RFN statements should be placed next to the copyright statement of the relevant author as indicated in the OFL.txt template to make them visible to designers wishing to make their separate version.
6 ABOUT THE FONTLOG
6.1 What is this FONTLOG thing exactly?
It has three purposes: 1) to provide basic information on the font to users and other designers and developers, 2) to document changes that have been made to the font or accompanying files, either by the original authors or others, and 3) to provide a place to acknowledge authors and other contributors. Please use it!
6.2 Is the FONTLOG required?
It is not a requirement of the license, but we strongly recommend you have one.
6.3 Am I required to update the FONTLOG when making Modified Versions?
No, but users, designers and other developers might get very frustrated with you if you don't. People need to know how derivative fonts differ from the original, and how to take advantage of the changes, or build on them. There are utilities that can help create and maintain a FONTLOG, such as the FONTLOG support in FontForge.
6.4 What should the FONTLOG look like?
It is typically a separate text file (FONTLOG.txt), but can take other formats. It commonly includes these four sections:
- brief header describing the FONTLOG itself and name of the font family
- Basic Font Information - description of the font family, purpose and breadth
- ChangeLog - chronological listing of changes
- Acknowledgements - list of authors and contributors with contact information
It could also include other sections, such as: where to find documentation, how to make contributions, information on contributing organizations, source code details, and a short design guide. See Appendix A for an example FONTLOG.
7 MAKING CONTRIBUTIONS TO OFL PROJECTS
7.1 Can I contribute work to OFL projects?
In many cases, yes. It is common for OFL fonts to be developed by a team of people who welcome contributions from the wider community. Contact the original authors for specific information on how to participate in their projects.
7.2 Why should I contribute my changes back to the original authors?
It would benefit many people if you contributed back in response to what you've received. Your contributions and improvements to the fonts and other components could be a tremendous help and would encourage others to contribute as well and 'give back'. You will then benefit from other people's contributions as well. Sometimes maintaining your own separate version takes more effort than merging back with the original. Be aware that any contributions, however, must be either your own original creation or work that you own, and you may be asked to affirm that clearly when you contribute.
7.3 I've made some very nice improvements to the font. Will you consider adopting them and putting them into future Original Versions?
Most authors would be very happy to receive such contributions. Keep in mind that it is unlikely that they would want to incorporate major changes that would require additional work on their end. Any contributions would likely need to be made for all the fonts in a family and match the overall design and style. Authors are encouraged to include a guide to the design with the fonts. It would also help to have contributions submitted as patches or clearly marked changes - the use of smart source revision control systems like subversion, mercurial, git or bzr is a good idea. Please follow the recommendations given by the author(s) in terms of preferred source formats and configuration parameters for sending contributions. If this is not indicated in a FONTLOG or other documentation of the font, consider asking them directly. Examples of useful contributions are bug fixes, additional glyphs, stylistic alternates (and the smart font code to access them) or improved hinting. Keep in mind that some kinds of changes (esp. hinting) may be technically difficult to integrate.
7.4 How can I financially support the development of OFL fonts?
It is likely that most authors of OFL fonts would accept financial contributions - contact them for instructions on how to do this. Such contributions would support future development. You can also pay for others to enhance the fonts and contribute the results back to the original authors for inclusion in the Original Version.
8 ABOUT THE LICENSE ITSELF
8.1 I see that this is version 1.1 of the license. Will there be later changes?
Version 1.1 is the first minor revision of the OFL. We are confident that version 1.1 will meet most needs, but are open to future improvements. Any revisions would be for future font releases, and previously existing licenses would remain in effect. No retroactive changes are possible, although the Copyright Holder(s) can re-release the font under a revised OFL. All versions will be available on our web site: http://scripts.sil.org/OFL.
8.2 Does this license restrict the rights of the Copyright Holder(s)?
No. The Copyright Holder(s) still retain(s) all the rights to their creation; they are only releasing a portion of it for use in a specific way. For example, the Copyright Holder(s) may choose to release a 'basic' version of their font under the OFL, but sell a restricted 'enhanced' version under a different license. They may also choose to release the same font under both the OFL and some other license. Only the Copyright Holder(s) can do this, and doing so does not change the terms of the OFL as it applies to that font.
8.3 Is the OFL a contract or a license?
The OFL is a worldwide license based on international copyright agreements and conventions. It is not a contract and so does not require you to sign it to have legal validity. By using, modifying and redistributing components under the OFL you indicate that you accept the license.
8.4 I really like the terms of the OFL, but want to change it a little. Am I allowed to take ideas and actual wording from the OFL and put them into my own custom license for distributing my fonts?
We strongly recommend against creating your very own unique open licensing model. Using a modified or derivative license will likely cut you off - along with the font(s) under that license - from the community of designers using the OFL, potentially expose you and your users to legal liabilities, and possibly put your work and rights at risk. The OFL went though a community and legal review process that took years of effort, and that review is only applicable to an unmodified OFL. The text of the OFL has been written by SIL (with review and consultation from the community) and is copyright (c) 2005-2013 SIL International. You may re-use the ideas and wording (in part, not in whole) in another non-proprietary license provided that you call your license by another unambiguous name, that you do not use the preamble, that you do not mention SIL and that you clearly present your license as different from the OFL so as not to cause confusion by being too similar to the original. If you feel the OFL does not meet your needs for an open license, please contact us.
8.5 Can I translate the license and the FAQ into other languages?
SIL certainly recognises the need for people who are not familiar with English to be able to understand the OFL and its use. Making the license very clear and readable has been a key goal for the OFL, but we know that people understand their own language best.
If you are an experienced translator, you are very welcome to translate the OFL and OFL-FAQ so that designers and users in your language community can understand the license better. But only the original English version of the license has legal value and has been approved by the community. Translations do not count as legal substitutes and should only serve as a way to explain the original license. SIL - as the author and steward of the license for the community at large - does not approve any translation of the OFL as legally valid because even small translation ambiguities could be abused and create problems.
SIL gives permission to publish unofficial translations into other languages provided that they comply with the following guidelines:
- Put the following disclaimer in both English and the target language stating clearly that the translation is unofficial:
"This is an unofficial translation of the SIL Open Font License into <language_name>. It was not published by SIL International, and does not legally state the distribution terms for fonts that use the OFL. A release under the OFL is only valid when using the original English text. However, we recognize that this unofficial translation will help users and designers not familiar with English to better understand and use the OFL. We encourage designers who consider releasing their creation under the OFL to read the OFL-FAQ in their own language if it is available. Please go to http://scripts.sil.org/OFL for the official version of the license and the accompanying OFL-FAQ."
- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion.
If you start such a unofficial translation effort of the OFL and OFL-FAQ please let us know.
8.6 Does the OFL have an explicit expiration term?
No, the implicit intent of the OFL is that the permissions granted are perpetual and irrevocable.
9 ABOUT SIL INTERNATIONAL
9.1 Who is SIL International and what do they do?
SIL serves language communities worldwide, building their capacity for sustainable language development, by means of research, translation, training and materials development. SIL makes its services available to all without regard to religious belief, political ideology, gender, race, or ethnic background. SIL's members and volunteers share a Christian commitment.
9.2 What does this have to do with font licensing?
The ability to read, write, type and publish in one's own language is one of the most critical needs for millions of people around the world. This requires fonts that are widely available and support lesser-known languages. SIL develops - and encourages others to develop - a complete stack of writing systems implementation components available under open licenses. This open stack includes input methods, smart fonts, smart rendering libraries and smart applications. There has been a need for a common open license that is specifically applicable to fonts and related software (a crucial component of this stack), so SIL developed the SIL Open Font License with the help of the Free/Libre and Open Source Software community.
9.3 How can I contact SIL?
Our main web site is: http://www.sil.org/
Our site about complex scripts is: http://scripts.sil.org/
Information about this license (and contact information) is at: http://scripts.sil.org/OFL
APPENDIX A - FONTLOG EXAMPLE
Here is an example of the recommended format for a FONTLOG, although other formats are allowed.
-----
FONTLOG for the GlobalFontFamily fonts
This file provides detailed information on the GlobalFontFamily Font Software. This information should be distributed along with the GlobalFontFamily fonts and any derivative works.
Basic Font Information
GlobalFontFamily is a Unicode typeface family that supports all languages that use the Latin script and its variants, and could be expanded to support other scripts.
NewWorldFontFamily is based on the GlobalFontFamily and also supports Greek, Hebrew, Cyrillic and Armenian.
More specifically, this release supports the following Unicode ranges...
This release contains...
Documentation can be found at...
To contribute to the project...
ChangeLog
10 December 2010 (Fred Foobar) GlobalFontFamily-devel version 1.4
- fix new build and testing system (bug #123456)
1 August 2008 (Tom Parker) GlobalFontFamily version 1.2.1
- Tweaked the smart font code (Branch merged with trunk version)
- Provided improved build and debugging environment for smart behaviours
7 February 2007 (Pat Johnson) NewWorldFontFamily Version 1.3
- Added Greek and Cyrillic glyphs
7 March 2006 (Fred Foobar) NewWorldFontFamily Version 1.2
- Tweaked contextual behaviours
1 Feb 2005 (Jane Doe) NewWorldFontFamily Version 1.1
- Improved build script performance and verbosity
- Extended the smart code documentation
- Corrected minor typos in the documentation
- Fixed position of combining inverted breve below (U+032F)
- Added OpenType/Graphite smart code for Armenian
- Added Armenian glyphs (U+0531 -> U+0587)
- Released as "NewWorldFontFamily"
1 Jan 2005 (Joe Smith) GlobalFontFamily Version 1.0
- Initial release
Acknowledgements
If you make modifications be sure to add your name (N), email (E), web-address (if you have one) (W) and description (D). This list is in alphabetical order.
N: Jane Doe
E: jane@university.edu
W: http://art.university.edu/projects/fonts
D: Contributor - Armenian glyphs and code
N: Fred Foobar
E: fred@foobar.org
W: http://foobar.org
D: Contributor - misc Graphite fixes
N: Pat Johnson
E: pat@fontstudio.org
W: http://pat.fontstudio.org
D: Designer - Greek & Cyrillic glyphs based on Roman design
N: Tom Parker
E: tom@company.com
W: http://www.company.com/tom/projects/fonts
D: Engineer - original smart font code
N: Joe Smith
E: joe@fontstudio.org
W: http://joe.fontstudio.org
D: Designer - original Roman glyphs
Fontstudio.org is an not-for-profit design group whose purpose is...
Foobar.org is a distributed community of developers...
Company.com is a small business who likes to support community designers...
University.edu is a renowned educational institution with a strong design department...
-----

BIN
static/fonts/go-mono/Go-Mono-Bold-Italic.ttf

Binary file not shown.

BIN
static/fonts/go-mono/Go-Mono-Bold.ttf

Binary file not shown.

BIN
static/fonts/go-mono/Go-Mono-Italic.ttf

Binary file not shown.

BIN
static/fonts/go-mono/Go-Mono.ttf

Binary file not shown.

36
static/fonts/go-mono/README

@ -0,0 +1,36 @@
These fonts were created by the Bigelow & Holmes foundry specifically for the
Go project. See https://blog.golang.org/go-fonts for details.
They are licensed under the same open source license as the rest of the Go
project's software:
Copyright (c) 2016 Bigelow & Holmes Inc.. All rights reserved.
Distribution of this font is governed by the following license. If you do not
agree to this license, including the disclaimer, do not distribute or modify
this font.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Google Inc. nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

137
static/js/bezpath.js

@ -0,0 +1,137 @@
// Copyright 2018 Raph Levien
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Utilities for representing and manipulating bezier paths.
const MOVETO = "m";
const LINETO = "l";
const CURVETO = "c";
const CLOSEPATH = "z";
const MARK = "#";
class BezPath {
constructor() {
this.cmds = [];
}
// Construction mutations (builder could be separate but oh well).
moveto(x, y) {
this.cmds.push([MOVETO, x, y]);
}
lineto(x, y) {
this.cmds.push([LINETO, x, y]);
}
curveto(x1, y1, x2, y2, x3, y3) {
this.cmds.push([CURVETO, x1, y1, x2, y2, x3, y3]);
}
closepath() {
this.cmds.push([CLOSEPATH]);
}
mark(i) {
this.cmds.push([MARK, i]);
}
renderSvg() {
let path = "";
for (let cmd of this.cmds) {
let op = cmd[0];
if (op === MOVETO) {
path += `M${cmd[1]} ${cmd[2]}`;
} else if (op === LINETO) {
path += `L${cmd[1]} ${cmd[2]}`;
} else if (op === CURVETO) {
path += `C${cmd[1]} ${cmd[2]} ${cmd[3]} ${cmd[4]} ${cmd[5]} ${cmd[6]}`;
} else if (op === CLOSEPATH) {
path += "Z";
}
}
return path;
}
hitTest(x, y) {
let result = new HitTestResult(x, y);
let curX;
let curY;
let curMark = null;
for (let cmd of this.cmds) {
let op = cmd[0];
if (op === MOVETO) {
curX = cmd[1];
curY = cmd[2];
} else if (op === LINETO) {
result.accumLine(curX, curY, cmd[1], cmd[2], curMark);
curX = cmd[1];
curY = cmd[2];
} else if (op === CURVETO) {
result.accumCurve(curX, curY, cmd[1], cmd[2], cmd[3], cmd[4],
cmd[5], cmd[6], curMark);
curX = cmd[5];
curY = cmd[6];
} else if (op === MARK) {
curMark = cmd[1];
}
}
return result;
}
}
class HitTestResult {
constructor(x, y) {
this.x = x;
this.y = y;
this.bestDist = 1e12;
this.bestMark = null;
}
accumulate(dist, pt, mark) {
if (dist < this.bestDist) {
this.bestDist = dist;
this.bestMark = mark;
}
}
accumLine(x0, y0, x1, y1, mark) {
let dx = x1 - x0;
let dy = y1 - y0;
let dotp = (this.x - x0) * dx + (this.y - y0) * dy;
let linDotp = dx * dx + dy * dy;
let r = Math.hypot(this.x - x0, this.y - y0);
let rMin = r;
r = Math.hypot(this.x - x1, this.y - y1);
rMin = Math.min(rMin, r);
if (dotp > 0 && dotp < linDotp) {
let norm = (this.x - x0) * dy - (this.y - y0) * dx;
r = Math.abs(norm / Math.sqrt(linDotp));
rMin = Math.min(rMin, r);
}
if (rMin < this.bestDist) {
this.bestDist = rMin;
this.bestMark = mark;
}
}
accumCurve(x0, y0, x1, y1, x2, y2, x3, y3, mark) {
let n = 32; // TODO: be adaptive
let dt = 1.0 / n;
let lastX = x0;
let lastY = y0;
for (let i = 0; i < n; i++) {
let t = (i + 1) * dt;
let mt = 1 - t;
let x = (x0 * mt * mt + 3 * (x1 * mt * t + x2 * t * t)) * mt + x3 * t * t * t;
let y = (y0 * mt * mt + 3 * (y1 * mt * t + y2 * t * t)) * mt + y3 * t * t * t;
this.accumLine(lastX, lastY, x, y, mark);
lastX = x;
lastY = y;
}
}
}

770
static/js/curves.js

@ -0,0 +1,770 @@
// Copyright 2018 Raph Levien
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A library of primitives for curves and splines.
/// A simple container for 2-vectors
class Vec2 {
constructor(x, y) {
this.x = x;
this.y = y;
}
norm() {
return Math.hypot(this.x, this.y);
}
dot(other) {
return this.x * other.x + this.y * other.y;
}
cross(other) {
return this.x * other.y - this.y * other.x;
}
}
class CubicBez {
/// Argument is array of coordinate values [x0, y0, x1, y1, x2, y2, x3, y3].
constructor(coords) {
this.c = coords;
}
weightsum(c0, c1, c2, c3) {
let x = c0 * this.c[0] + c1 * this.c[2] + c2 * this.c[4] + c3 * this.c[6];
let y = c0 * this.c[1] + c1 * this.c[3] + c2 * this.c[5] + c3 * this.c[7];
return new Vec2(x, y);
}
eval(t) {
let mt = 1 - t;
let c0 = mt * mt * mt;
let c1 = 3 * mt * mt * t;
let c2 = 3 * mt * t * t;
let c3 = t * t * t;
return this.weightsum(c0, c1, c2, c3);
}
deriv(t) {
let mt = 1 - t;
let c0 = -3 * mt * mt;
let c3 = 3 * t * t;
let c1 = -6 * t * mt - c0;
let c2 = 6 * t * mt - c3;
return this.weightsum(c0, c1, c2, c3);
}
deriv2(t) {
let mt = 1 - t;
let c0 = 6 * mt;
let c3 = 6 * t;
let c1 = 6 - 18 * mt;
let c2 = 6 - 18 * t;
return this.weightsum(c0, c1, c2, c3);
}
curvature(t) {
let d = this.deriv(t);
let d2 = this.deriv2(t);
return d.cross(d2) / Math.pow(d.norm(), 3);
}
atanCurvature(t) {
let d = this.deriv(t);
let d2 = this.deriv2(t);
return Math.atan2(d.cross(d2), Math.pow(d.norm(), 3));
}
// de Casteljau's algorithm
leftHalf() {
let c = new Float64Array(8);
c[0] = this.c[0];
c[1] = this.c[1];
c[2] = 0.5 * (this.c[0] + this.c[2]);
c[3] = 0.5 * (this.c[1] + this.c[3]);
c[4] = 0.25 * (this.c[0] + 2 * this.c[2] + this.c[4]);
c[5] = 0.25 * (this.c[1] + 2 * this.c[3] + this.c[5]);
c[6] = 0.125 * (this.c[0] + 3 * (this.c[2] + this.c[4]) + this.c[6]);
c[7] = 0.125 * (this.c[1] + 3 * (this.c[3] + this.c[5]) + this.c[7]);
return new CubicBez(c);
}
rightHalf() {
let c = new Float64Array(8);
c[0] = 0.125 * (this.c[0] + 3 * (this.c[2] + this.c[4]) + this.c[6]);
c[1] = 0.125 * (this.c[1] + 3 * (this.c[3] + this.c[5]) + this.c[7]);
c[2] = 0.25 * (this.c[2] + 2 * this.c[4] + this.c[6]);
c[3] = 0.25 * (this.c[3] + 2 * this.c[5] + this.c[7]);
c[4] = 0.5 * (this.c[4] + this.c[6]);
c[5] = 0.5 * (this.c[5] + this.c[7]);
c[6] = this.c[6];
c[7] = this.c[7];
return new CubicBez(c);
}
}
function testCubicBez() {
let c = new Float64Array(8);
for (var i = 0; i < 8; i++) {
c[i] = Math.random();
}
let cb = new CubicBez(c);
let t = Math.random();
let epsilon = 1e-6;
let xy0 = cb.eval(t);
let xy1 = cb.eval(t + epsilon);
console.log(new Vec2((xy1.x - xy0.x) / epsilon, (xy1.y - xy0.y) / epsilon));
console.log(cb.deriv(t));
let dxy0 = cb.deriv(t);
let dxy1 = cb.deriv(t + epsilon);
console.log(new Vec2((dxy1.x - dxy0.x) / epsilon, (dxy1.y - dxy0.y) / epsilon));
console.log(cb.deriv2(t));
}
class Polynomial {
constructor(c) {
this.c = c;
}
eval(x) {
let xi = 1;
let s = 0;
for (let a of this.c) {
s += a * xi;
xi *= x;
}
return s;
}
deriv() {
let c = new Float64Array(this.c.length - 1);
for (let i = 0; i < c.length; i++) {
c[i] = (i + 1) * this.c[i + 1];
}
return new Polynomial(c);
}
}
function hermite5(x0, x1, v0, v1, a0, a1) {
return new Polynomial([x0,
v0,
0.5 * a0,
-10 * x0 + 10 * x1 - 6 * v0 - 4 * v1 - 1.5 * a0 + 0.5 * a1,
15 * x0 - 15 * x1 + 8 * v0 + 7 * v1 + 1.5 * a0 - a1,
-6 * x0 + 6 * x1 - 3 * v0 - 3 * v1 + -.5 * a0 + 0.5 * a1]);
}
/// Solve tridiagonal matrix system. Destroys inputs, leaves output in x.
///
/// Solves a[i] * x[i - 1] + b[i] * x[i] + c[i] * x[i + 1] = d[i]
///
/// Inputs are array-like objects (typed arrays are good for performance).
///
/// Note: this is not necessarily the fastest, see:
/// https://en.wikibooks.org/wiki/Algorithm_Implementation/Linear_Algebra/Tridiagonal_matrix_algorithm
function tridiag(a, b, c, d, x) {
let n = x.length;
for (var i = 1; i < n; i++) {
let m = a[i] / b[i - 1];
b[i] -= m * c[i - 1];
d[i] -= m * d[i - 1];
}
x[n - 1] = d[n - 1] / b[n - 1];
for (var i = n - 2; i >= 0; i--) {
x[i] = (d[i] - c[i] * x[i + 1]) / b[i];
}
}
function testTridiag(n) {
let a = new Float64Array(n);
let b = new Float64Array(n);
let c = new Float64Array(n);
let d = new Float64Array(n);
let x = new Float64Array(n);
for (var i = 0; i < n; i++) {
a[i] = Math.random();
b[i] = 2 + Math.random();
c[i] = Math.random();
d[i] = Math.random();
x[i] = Math.random();
}
let bsave = new Float64Array(b);
let dsave = new Float64Array(d);
let xsave = new Float64Array(x);
tridiag(a, b, c, d, x);
b = bsave; d = dsave;
console.log(b[0] * x[0] + c[0] * x[1] - d[0]);
for (var i = 1; i < n - 1; i++) {
console.log(a[i] * x[i - 1] + b[i] * x[i] + c[i] * x[i + 1] - d[i]);
}
console.log(a[n - 1] * x[n - 2] + b[n - 1] * x[n - 1] - d[n - 1]);
}
//testTridiag(10);
//testCubicBez();
/// Create a smooth cubic bezier.
function myCubic(th0, th1) {
function myCubicLen(th0, th1) {
let offset = 0.3 * Math.sin(th1 * 2 - 0.4 * Math.sin(th1 * 2));
let newShape = true;
if (newShape) {
let scale = 1.0 / (3 * 0.8);
let len = scale * (Math.cos(th0 - offset) - 0.2 * Math.cos((3 * (th0 - offset))));
return len;
} else {
let drive = 2.0;
let scale = 1.0 / (3 * Math.tanh(drive));
let len = scale * Math.tanh(drive * Math.cos(th0 - offset));
return len;
}
}
var coords = new Float64Array(8);
let len0 = myCubicLen(th0, th1);
coords[2] = Math.cos(th0) * len0;
coords[3] = Math.sin(th0) * len0;
let len1 = myCubicLen(th1, th0);
coords[4] = 1 - Math.cos(th1) * len1;
coords[5] = Math.sin(th1) * len1;
coords[6] = 1;
return coords;
}
//! Base class for two parameter curve families
class TwoParamCurve {
/// Render the curve, providing an array of _interior_ cubic bezier
/// control points only. Return value is an array of 3n-1 Vec2's.
// render(th0, th1)
/// Compute curvature.
///
/// Result is an object with ak0 and ak1 (arctan of curvature at endpoints).
/// Quadrant is significant - a value outside -pi/2 to pi/2 means a reversal
/// of direction.
// computeCurvature(th0, th1)
/// Get endpoint condition.
///
/// Return tangent at endpoint given next-to-endpoint tangent.
// endpointTangent(th)
/// Compute curvature derivatives.
///
/// Result is an object with dak0dth0 and friends.
/// Default implementation is approximate through central differencing, but
/// curves can override.
computeCurvatureDerivs(th0, th1) {
let epsilon = 1e-6;
let scale = 2.0 / epsilon;
let k0plus = this.computeCurvature(th0 + epsilon, th1);
let k0minus = this.computeCurvature(th0 - epsilon, th1);
let dak0dth0 = scale * (k0plus.ak0 - k0minus.ak0);
let dak1dth0 = scale * (k0plus.ak1 - k0minus.ak1);
let k1plus = this.computeCurvature(th0, th1 + epsilon);
let k1minus = this.computeCurvature(th0, th1 - epsilon);
let dak0dth1 = scale * (k1plus.ak0 - k1minus.ak0);
let dak1dth1 = scale * (k1plus.ak1 - k1minus.ak1);
return {dak0dth0: dak0dth0, dak1dth0: dak1dth0, dak0dth1: dak0dth1, dak1dth1: dak1dth1};
}
}
class MyCurve extends TwoParamCurve {
render(th0, th1) {
let c = myCubic(th0, th1);
return [new Vec2(c[2], c[3]), new Vec2(c[4], c[5])];
}
/// Render as a 4-parameter curve with optional adjusted endpoint curvatures.
render4Quintic(th0, th1, k0, k1) {
//let cb = new CubicBez(myCubic(th0, th1));
let cb = this.convCubic(this.render4Cubic(th0, th1, k0, k1));
// compute second deriv tweak to match curvature
function curvAdjust(t, th, k) {
if (k === null) return new Vec2(0, 0);
let c = Math.cos(th);
let s = Math.sin(th);
let d2 = cb.deriv2(t);
let d2cross = d2.y * c - d2.x * s;
let d = cb.deriv(t);
let ddot = d.x * c + d.y * s;
// TODO: if ddot = 0, cusp, no adjustment
let oldK = d2cross / (ddot * ddot);
let kAdjust = k - oldK;
let aAdjust = kAdjust * (ddot * ddot);
return new Vec2(-s * aAdjust, c * aAdjust);
}
let a0 = curvAdjust(0, th0, k0);
let a1 = curvAdjust(1, -th1, k1);
let hx = hermite5(0, 0, 0, 0, a0.x, a1.x);
let hy = hermite5(0, 0, 0, 0, a0.y, a1.y);
let hxd = hx.deriv();
let hyd = hy.deriv();
// This really would be cleaner if we had arbitrary deCasteljau...
let c0 = cb.leftHalf();
let c1 = cb.rightHalf();
let cs = [c0.leftHalf(), c0.rightHalf(), c1.leftHalf(), c1.rightHalf()];
let result = [];
let scale = 1./12;
for (let i = 0; i < 4; i++) {
let t = 0.25 * i;
let t1 = t + 0.25;
let c = cs[i].c;
let x0 = hx.eval(t);
let y0 = hy.eval(t);
let x1 = x0 + scale * hxd.eval(t);
let y1 = y0 + scale * hyd.eval(t);
let x3 = hx.eval(t1);
let y3 = hy.eval(t1);
let x2 = x3 - scale * hxd.eval(t1);
let y2 = y3 - scale * hyd.eval(t1);
if (i != 0) {
result.push(new Vec2(c[0] + x0, c[1] + y0));
}
result.push(new Vec2(c[2] + x1, c[3] + y1));
result.push(new Vec2(c[4] + x2, c[5] + y2));
}
return result;
}
convCubic(pts) {
let coords = new Float64Array(8);
coords[2] = pts[0].x;
coords[3] = pts[0].y;
coords[4] = pts[1].x;
coords[5] = pts[1].y;
coords[6] = 1;
return new CubicBez(coords);
}
// Ultimately we want to exactly match the endpoint curvatures (probably breaking
// into two cubic segments), but for now, just approximate...
render4Cubic(th0, th1, k0, k1) {
let cb = new CubicBez(myCubic(th0, th1));
let result = [];
function deriv_scale(t, th, k) {
if (k === null) return 1/3;
let c = Math.cos(th);
let s = Math.sin(th);
let d = cb.deriv(t);
let d2 = cb.deriv2(t);
let d2cross = d2.y * c - d2.x * s;
let ddot = d.x * c + d.y * s;
let oldK = d2cross / (ddot * ddot);
// fudge to avoid divide-by-zero
if (Math.abs(oldK) < 1e-6) oldK = 1e-6;
let ratio = k / oldK;
// TODO: fine tune this dodgy formula
//let scale = ratio < 1 ? 1/2 - ratio/6 : 1/(3*ratio);
let scale = 1/(2 + ratio);
return scale;
}
let scale0 = deriv_scale(0, th0, k0);
let d0 = cb.deriv(0);
result.push(new Vec2(d0.x * scale0, d0.y * scale0));
let d1 = cb.deriv(1);
let scale1 = deriv_scale(1, -th1, k1);
result.push(new Vec2(1 - d1.x * scale1, - d1.y * scale1));
return result;
}
render4(th0, th1, k0, k1) {
if (k0 === null && k1 === null) {
return this.render(th0, th1);
}
return this.render4Quintic(th0, th1, k0, k1);
}
computeCurvature(th0, th1) {
let cb = new CubicBez(myCubic(th0, th1));
function curv(t, th) {
let c = Math.cos(th);
let s = Math.sin(th);
let d2 = cb.deriv2(t);
let d2cross = d2.y * c - d2.x * s;
let d = cb.deriv(t);
let ddot = d.x * c + d.y * s;
return Math.atan2(d2cross, ddot * Math.abs(ddot));
}
//let ak0 = cb.atanCurvature(0);
//let ak1 = cb.atanCurvature(1);
let ak0 = curv(0, th0);
let ak1 = curv(1, -th1);
return {ak0: ak0, ak1: ak1};
}
endpointTangent(th) {
// Same value as parabola:
//return Math.atan(2 * Math.tan(th)) - th;
return 0.5 * Math.sin(2 * th);
}
}
//! Global spline solver
// normalize theta to -pi..pi
function mod2pi(th) {
let twopi = 2 * Math.PI;
let frac = th * (1 / twopi);
return twopi * (frac - Math.round(frac));
}
class TwoParamSpline {
constructor(curve, ctrlPts) {
this.curve = curve;
this.ctrlPts = ctrlPts;
this.startTh = null;
this.endTh = null;
}
/// Determine initial tangent angles, given array of Vec2 control points.
initialThs() {
var ths = new Float64Array(this.ctrlPts.length);
for (var i = 1; i < ths.length - 1; i++) {
let dx0 = this.ctrlPts[i].x - this.ctrlPts[i - 1].x;
let dy0 = this.ctrlPts[i].y - this.ctrlPts[i - 1].y;
let l0 = Math.hypot(dx0, dy0);
let dx1 = this.ctrlPts[i + 1].x - this.ctrlPts[i].x;
let dy1 = this.ctrlPts[i + 1].y - this.ctrlPts[i].y;
let l1 = Math.hypot(dx1, dy1);
let th0 = Math.atan2(dy0, dx0);
let th1 = Math.atan2(dy1, dx1);
let bend = mod2pi(th1 - th0);
let th = mod2pi(th0 + bend * l0 / (l0 + l1));
ths[i] = th;
if (i == 1) { ths[0] = th0; }
if (i == ths.length - 2) { ths[i + 1] = th1; }
}
if (this.startTh !== null) {
ths[0] = this.startTh;
}
if (this.endTh !== null) {
ths[ths.length - 1] = this.endTh;
}
this.ths = ths;
return ths;
}
/// Get tangent angles relative to endpoints, and chord length.
getThs(i) {
let dx = this.ctrlPts[i + 1].x - this.ctrlPts[i].x;
let dy = this.ctrlPts[i + 1].y - this.ctrlPts[i].y;
let th = Math.atan2(dy, dx);
let th0 = mod2pi(this.ths[i] - th);
let th1 = mod2pi(th - this.ths[i + 1]);
let chord = Math.hypot(dx, dy);
return {th0: th0, th1: th1, chord: chord};
}
/// Crawl towards a curvature continuous solution.
iterDumb(iter) {
function computeErr(ths0, ak0, ths1, ak1) {
// rescale tangents by geometric mean of chordlengths
let ch0 = Math.sqrt(ths0.chord);
let ch1 = Math.sqrt(ths1.chord);
let a0 = Math.atan2(Math.sin(ak0.ak1) * ch1, Math.cos(ak0.ak1) * ch0);
let a1 = Math.atan2(Math.sin(ak1.ak0) * ch0, Math.cos(ak1.ak0) * ch1);
return a0 - a1;
/*
return ths1.chord * Math.sin(ak0.ak1) * Math.cos(ak1.ak0)
- ths0.chord * Math.sin(ak1.ak0) * Math.cos(ak0.ak1);
*/
}
let n = this.ctrlPts.length;
// Fix endpoint tangents; we rely on iteration for this to converge
if (this.startTh === null) {
let ths0 = this.getThs(0);
this.ths[0] += this.curve.endpointTangent(ths0.th1) - ths0.th0;
}
if (this.endTh === null) {
let ths0 = this.getThs(n - 2);
this.ths[n - 1] -= this.curve.endpointTangent(ths0.th0) - ths0.th1;
}
if (n < 3) return 0;
var absErr = 0;
var x = new Float64Array(n - 2);
var ths0 = this.getThs(0);
var ak0 = this.curve.computeCurvature(ths0.th0, ths0.th1);
//console.log('');
for (var i = 0; i < n - 2; i++) {
let ths1 = this.getThs(i + 1);
let ak1 = this.curve.computeCurvature(ths1.th0, ths1.th1);
let err = computeErr(ths0, ak0, ths1, ak1);
absErr += Math.abs(err);
let epsilon = 1e-3;
let ak0p = this.curve.computeCurvature(ths0.th0, ths0.th1 + epsilon);
let ak1p = this.curve.computeCurvature(ths1.th0 - epsilon, ths1.th1);
let errp = computeErr(ths0, ak0p, ths1, ak1p);
let derr = (errp - err) * (1 / epsilon);
//console.log(err, derr, ak0, ak1, ak0p, ak1p);
x[i] = err / derr;
ths0 = ths1;
ak0 = ak1;
}
for (var i = 0; i < n - 2; i++) {
let scale = Math.tanh(0.25 * (iter + 1));
this.ths[i + 1] += scale * x[i];
}
return absErr;
}
/// Perform one step of a Newton solver.
// Not yet implemented
iterate() {
let n = this.ctrlPts.length;
if (n < 3) return;
var a = new Float64Array(n - 2);
var b = new Float64Array(n - 2);
var c = new Float64Array(n - 2);
var d = new Float64Array(n - 2);
var x = new Float64Array(n - 2);
let ths0 = this.getThs(0);
var last_ak = this.curve.computeCurvature(ths0.th0, ths0.th1);
var last_dak = this.curve.computeCurvatureDerivs(ths0.th0, ths0.th1);
var last_a = Math.hypot(this.ctrlPts[1].x - this.ctrlPts[0].x,
this.ctrlPts[1].y - this.ctrlPts[0].y);
for (var i = 0; i < n - 2; i++) {
let ths = this.getThs(i + 1);
let ak = this.curve.computeCurvature(ths.th0, ths.th1);
let dak = this.curve.computeCurvatureDerivs(ths.th0, ths.th1);
var a = Math.hypot(this.ctrlPts[i + 2].x - this.ctrlPts[i + 1].x,
this.ctrlPts[i + 2].y - this.ctrlPts[i + 1].y);
let c0 = Math.cos(last_ak.ak1);
let s0 = Math.sin(last_ak.ak1);
let c1 = Math.cos(ak.ak0);
let s1 = Math.sin(ak.ak0);
// TODO: fill in derivatives properly
d[i] = a * s0 * c1 - last_a * s1 * c0;
last_ak = ak;
last_dak = dak;
last_a = a;
}
tridiag(a, b, c, d, x);
for (var i = 0; i < n - 2; i++) {
this.ths[i + 1] -= x[i];
}
}
/// Return an SVG path string.
renderSvg() {
let c = this.ctrlPts;
if (c.length == 0) { return ""; }
let path = `M${c[0].x} ${c[0].y}`;
let cmd = " C";
for (var i = 0; i < c.length - 1; i++) {
let ths = this.getThs(i);
let render = this.curve.render(ths.th0, ths.th1);
let dx = c[i + 1].x - c[i].x;
let dy = c[i + 1].y - c[i].y;
for (var j = 0; j < render.length; j++) {
let pt = render[j];
let x = c[i].x + dx * pt.x - dy * pt.y;
let y = c[i].y + dy * pt.x + dx * pt.y;
path += `${cmd}${x} ${y}`;
cmd = " ";
}
path += ` ${c[i + 1].x} ${c[i + 1].y}`;
}
return path;
}
}
/// Spline handles more general cases, including corners.
class Spline {
constructor(ctrlPts, isClosed) {
this.ctrlPts = ctrlPts;
this.isClosed = isClosed;
this.curve = new MyCurve();
}
pt(i, start) {
let length = this.ctrlPts.length;
return this.ctrlPts[(i + start + length) % length];
}
startIx() {
if (!this.isClosed) {
return 0;
}
for (let i = 0; i < this.ctrlPts.length; i++) {
let pt = this.ctrlPts[i];
if (pt.ty === "corner" || pt.lth !== null) {
return i;
}
}
// Path is all-smooth and closed.
return 0;
}
solve() {
let start = this.startIx();
let length = this.ctrlPts.length - (this.isClosed ? 0 : 1);
let i = 0;
while (i < length) {
let ptI = this.pt(i, start);
let ptI1 = this.pt(i + 1, start);
if ((i + 1 == length || ptI1.ty === "corner")
&& ptI.rth === null && ptI1.lth === null) {
let dx = ptI1.pt.x - ptI.pt.x;
let dy = ptI1.pt.y - ptI.pt.y;
let th = Math.atan2(dy, dx);
ptI.rTh = th;
ptI1.lTh = th;
i += 1;
} else {
// We have a curve.
let innerPts = [ptI.pt];
let j = i + 1;
while (j < length + 1) {
let ptJ = this.pt(j, start);
innerPts.push(ptJ.pt);
j += 1;
if (ptJ.ty === "corner" || ptJ.lth !== null) {
break;
}
}
//console.log(innerPts);
let inner = new TwoParamSpline(this.curve, innerPts);
inner.startTh = this.pt(i, start).rth;
inner.endTh = this.pt(j - 1, start).lth;
let nIter = 10;
inner.initialThs();
for (let k = 0; k < nIter; k++) {
inner.iterDumb(k);
}
for (let k = i; k + 1 < j; k++) {
this.pt(k, start).rTh = inner.ths[k - i];
this.pt(k + 1, start).lTh = inner.ths[k + 1 - i];
// Record curvatures (for blending, not all will be used)
let ths = inner.getThs(k - i);
let aks = this.curve.computeCurvature(ths.th0, ths.th1);
this.pt(k, start).rAk = aks.ak0;
this.pt(k + 1, start).lAk = aks.ak1;
}
i = j - 1;
}
}
}
chordLen(i) {
let ptI = this.pt(i, 0).pt;
let ptI1 = this.pt(i + 1, 0).pt;
return Math.hypot(ptI1.x - ptI.x, ptI1.y - ptI.y);
}
// Determine whether a control point requires curvature blending, and if so,
// the blended curvature. To be invoked after solving.
computeCurvatureBlending() {
function myTan(th) {
if (th > Math.PI / 2) {
return Math.tan(Math.PI - th);
} else if (th < -Math.PI / 2) {
return Math.tan(-Math.PI - th);
} else {
return Math.tan(th);
}
}
for (let pt of this.ctrlPts) {
pt.kBlend = null;
}
let length = this.ctrlPts.length - (this.isClosed ? 0 : 1);
for (let i = 0; i < length; i++) {
let pt = this.pt(i, 0);
if (pt.ty === "smooth" && pt.lth !== null) {
let thresh = Math.PI / 2 - 1e-6;
//if (Math.abs(pt.rAk) > thresh || Math.abs(pt.lAk) > thresh) {
// // Don't blend reversals. We might reconsider this, but punt for now.
// continue;
//}
if (Math.sign(pt.rAk) != Math.sign(pt.lAk)) {
pt.kBlend = 0;
} else {
let rK = myTan(pt.rAk) / this.chordLen(i - 1);
let lK = myTan(pt.lAk) / this.chordLen(i);
pt.kBlend = 2 / (1 / rK + 1 / lK);
//console.log(`point ${i}: kBlend = ${pt.kBlend}`);
}
}
}
}
render() {
let path = new BezPath;
if (this.ctrlPts.length == 0) {
return path;
}
let pt0 = this.ctrlPts[0];
path.moveto(pt0.pt.x, pt0.pt.y);
let length = this.ctrlPts.length - (this.isClosed ? 0 : 1);
let i = 0;
for (let i = 0; i < length; i++) {
path.mark(i);
let ptI = this.pt(i, 0);
let ptI1 = this.pt(i + 1, 0);
let dx = ptI1.pt.x - ptI.pt.x;
let dy = ptI1.pt.y - ptI.pt.y;
let chth = Math.atan2(dy, dx);
let chord = Math.hypot(dy, dx);
let th0 = mod2pi(ptI.rTh - chth);
let th1 = mod2pi(chth - ptI1.lTh);
// Apply curvature blending
let k0 = ptI.kBlend !== null ? ptI.kBlend * chord : null;
let k1 = ptI1.kBlend !== null ? ptI1.kBlend * chord : null;
//console.log(`segment ${i}: ${k0} ${k1}`);
let render = this.curve.render4(th0, th1, k0, k1);
let c = [];
for (let j = 0; j < render.length; j++) {
let pt = render[j];
c.push(ptI.pt.x + dx * pt.x - dy * pt.y);
c.push(ptI.pt.y + dy * pt.x + dx * pt.y);
}
c.push(ptI1.pt.x);
c.push(ptI1.pt.y);
for (let j = 0; j < c.length; j += 6) {
path.curveto(c[j], c[j + 1], c[j + 2], c[j + 3], c[j + 4], c[j + 5]);
}
}
if (this.isClosed) {
path.closepath();
}
return path;
}
renderSvg() {
return this.render().renderSvg();
}
}
/// ControlPoint is a lot like `Knot` but has no UI, is used for spline solving.
class ControlPoint {
constructor(pt, ty, lth, rth) {
this.pt = pt;
this.ty = ty;
this.lth = lth;
this.rth = rth;
}
}

860
static/js/splineui.js

@ -0,0 +1,860 @@
// Copyright 2018 Raph Levien
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! UI for drawing splines
/// Fancy name for something that just detects double clicks, but might expand.
class GestureDet {
constructor(ui) {
this.ui = ui;
this.lastEv = null;
this.lastPt = null;
this.clickCount = 0;
}
onPointerDown(ev) {
let dblClickThreshold = 550; // ms
let radiusThreshold = 5;
let pt = this.ui.getCoords(ev);
if (this.lastEv !== null) {
if (ev.timeStamp - this.lastEv.timeStamp > dblClickThreshold
|| Math.hypot(pt.x - this.lastPt.x, pt.y - this.lastPt.y) > radiusThreshold) {
this.clickCount = 0;
}
}
this.lastEv = ev;
this.lastPt = pt;
this.clickCount++;
}
}
// Dimensions of the tangent target
// Inside this radius, convert to corner point
let tanR1 = 5;
// Radius of drawn tangent marker
let tanR2 = 15;
// Outside this radius, remove explicit tangent
let tanR3 = 45;
/// State and UI for an editable spline
class SplineEdit {
constructor(ui) {
this.ui = ui;
this.knots = [];
this.isClosed = false;
this.bezpath = new BezPath;
this.selection = new Set();
this.mode = "start";
this.grid = 20;
this.shiftOnDrag = false;
}
renderGrid(visible) {
let grid = document.getElementById("grid");
this.ui.removeAllChildren(grid);
if (!visible) return;
let w = 640;
let h = 480;
for (let i = 0; i < w; i += this.grid) {
let line = this.ui.createSvgElement("line");
line.setAttribute("x1", i);
line.setAttribute("y1", 0);
line.setAttribute("x2", i);
line.setAttribute("y2", h);
grid.appendChild(line);
}
for (let i = 0; i < h; i += this.grid) {
let line = this.ui.createSvgElement("line");
line.setAttribute("x1", 0);
line.setAttribute("y1", i);
line.setAttribute("x2", w);
line.setAttribute("y2", i);
grid.appendChild(line);
}
}
setSelection(sel) {
this.selection = sel;
}
roundToGrid(pt) {
let g = this.grid;
return new Vec2(g * Math.round(pt.x / g), g * Math.round(pt.y / g));
}
onPointerDown(ev, obj) {
let pt = this.ui.getCoords(ev);
if (obj === null) {
let subdivideDist = 5;
if (ev.shiftKey) {
pt = this.roundToGrid(pt);
}
let ty = ev.altKey ? "smooth" : "corner";
let hit = this.bezpath.hitTest(pt.x, pt.y);
let insIx = this.knots.length;
if (hit.bestDist < subdivideDist) {
ty = "smooth";
insIx = hit.bestMark + 1;
}
let knot = new Knot(this, pt.x, pt.y, ty);
this.knots.splice(insIx, 0, knot);
this.ui.attachReceiver(knot.handleEl, this, knot);
// TODO: setter rather than state change?
this.ui.receiver = this;
this.setSelection(new Set([knot]));
if (ty === "corner") {
this.mode = "creating";
} else {
this.mode = "dragging";
}
this.initPt = pt;
} else if (obj instanceof TanHandle) {
this.mode = "tanhandle";
this.isRight = obj.isRight;
// This suggests maybe we should use selected point, not initPt
this.initPt = new Vec2(obj.knot.x, obj.knot.y);
this.updateTan(obj.knot, pt, ev);
this.setSelection(new Set([obj.knot]));
} else {
if (this.selection.size == 1
&& this.selection.has(this.knots[this.knots.length - 1])
&& this.knots.length >= 3
&& obj === this.knots[0])
{
this.isClosed = true;
}
this.mode = "dragging";
if (this.ui.gestureDet.clickCount > 1) {
if (obj.ty === "corner") {
obj.setTy("smooth");
} else {
obj.setTy("corner");
}
obj.setTan(null);
}
let sel;
this.shiftOnDrag = ev.shiftKey;
if (ev.shiftKey) {
// toggle point selection
sel = new Set(this.selection);
if (sel.has(obj)) {
sel.delete(obj);
} else {
sel.add(obj);
}
} else {
if (this.selection.has(obj)) {
sel = this.selection;
} else {
sel = new Set([obj]);
}
}
this.setSelection(sel);
}
this.render();
this.lastPt = pt;
}
onPointerMove(ev) {
let pt = this.ui.getCoords(ev);
let dx = pt.x - this.lastPt.x;
let dy = pt.y - this.lastPt.y;
if (this.mode === "dragging") {
for (let knot of this.selection) {
if (ev.shiftKey && !this.shiftOnDrag && this.selection.size == 1) {
pt = this.roundToGrid(pt);
knot.updatePos(pt.x, pt.y);
} else {
knot.updatePos(knot.x + dx, knot.y + dy);
}
}
} else if (this.mode === "creating") {
let r = Math.hypot(pt.x - this.initPt.x, pt.y - this.initPt.y);
let ty = r < tanR1 ? "corner" : "smooth";
for (let knot of this.selection) {
knot.setTy(ty);
}
} else if (this.mode === "tanhandle") {
let r = Math.hypot(pt.x - this.initPt.x, pt.y - this.initPt.y);
for (let knot of this.selection) {
this.updateTan(knot, pt, ev);
}
}
this.render();
this.lastPt = pt;
}
onPointerUp(ev) {
this.mode = "start";
this.render();
}
onPointerHover(ev) {
let pt = this.ui.getCoords(ev);
// TODO: hover the knots and the visible tangent handles
let hit = this.bezpath.hitTest(pt.x, pt.y);
// TODO: display proposed knot
}
onKeyDown(ev) {
if (ev.key === "Backspace" || ev.key === "Delete") {
this.delete();
return true;
} else if (ev.key === "ArrowLeft") {
this.nudge(-1, 0, ev);
return true;
} else if (ev.key === "ArrowRight") {
this.nudge(1, 0, ev);
return true;
} else if (ev.key === "ArrowUp") {
this.nudge(0, -1, ev);
return true;
} else if (ev.key === "ArrowDown") {
this.nudge(0, 1, ev);
return true;
}
return false;
}
delete() {
for (let i = 0; i < this.knots.length; i++) {
let knot = this.knots[i];
if (this.selection.has(knot)) {
this.knots.splice(i, 1);
knot.handleEl.remove();
i--;
}
}
if (this.knots.length < 3) {
this.isClosed = false;
}
this.selection = new Set();
this.render();
}
nudge(dx, dy, ev) {
if (ev && ev.shiftKey) {
dx *= 10;
dy *= 10;
}
for (let knot of this.selection) {
knot.updatePos(knot.x + dx, knot.y + dy);
}
this.render();
}
updateTan(knot, pt, ev) {
let dx = pt.x - this.initPt.x;
let dy = pt.y - this.initPt.y;
if (!this.isRight) {
dx = -dx;
dy = -dy;
}
let r = Math.hypot(dx, dy);
let th = null;
if (r < tanR3) {
th = Math.atan2(dy, dx);
if (ev.shiftKey) {
th = Math.PI / 2 * Math.round(th * (2 / Math.PI));
}
}
knot.setTan(th, this.isRight);
}
renderSpline() {
let ctrlPts = [];
for (let knot of this.knots) {
let pt = new ControlPoint(new Vec2(knot.x, knot.y), knot.ty, knot.lth, knot.rth);
ctrlPts.push(pt);
}
this.spline = new Spline(ctrlPts, this.isClosed);
this.spline.solve();
// Should this be bundled into solve?
this.spline.computeCurvatureBlending();
this.bezpath = this.spline.render();
let path = this.bezpath.renderSvg();
document.getElementById("spline").setAttribute("d", path);
}
renderSel() {
for (let i = 0; i < this.knots.length; i++) {
let knot = this.knots[i];
knot.computedLTh = this.spline.pt(i, 0).lTh;
knot.computedRTh = this.spline.pt(i, 0).rTh;
knot.updateSelDecoration(this.selection.has(knot), this.mode);
}
}
render() {
this.renderSpline();
this.renderSel();
}
/// Return a JSON object (suitable for stringify)
serialize() {
function r(x, adj) {
return Math.round(x * adj) / adj;
}
let pts = [];
for (let knot of this.knots) {
let pt = {"x": r(knot.x, 100), "y": r(knot.y, 100)};
if (knot.ty === "corner") {
pt["c"] = 0;
if (knot.lth !== null) {
pt["l"] = r(knot.lth, 1000);
}
if (knot.rth !== null) {
pt["r"] = r(knot.rth, 1000);
}
} else {
pt["c"] = 1;
if (knot.lth !== null) {
pt["t"] = r(knot.lth, 1000);
}
}
pts.push(pt);
}
let sp = {"closed": this.isClosed, "pts": pts};
let result = {"subpaths": [sp]};
return result;
}
/// Take either a JSON object or a string, and set UI state to it
deserialize(data) {
if (typeof data === 'string') {
data = JSON.parse(data);
}
let sp = data.subpaths[0];
let knots = [];
for (let pt of sp.pts) {
let ty = pt.c ? "smooth" : "corner";
let knot = new Knot(this, pt.x, pt.y, ty);
if (pt.c) {
if ("t" in pt) {
knot.lth = pt.t;
knot.rth = knot.lth;
}
} else {
if ("l" in pt) {
knot.lth = pt.l;
}
if ("r" in pt) {
knot.lth = pt.r;
}
}
knots.push(knot);
}
// Hopefully for invalid data an exception would have occurred by here.
for (let knot of this.knots) {
knot.handleEl.remove();
}
this.knots = knots;
for (let knot of knots) {
this.ui.attachReceiver(knot.handleEl, this, knot);
}
this.isClosed = sp.closed;
console.log(sp.closed);
this.selection = new Set();
this.render();
}
}
class Knot {
/// ty is one of 'corner', 'smooth'.
constructor(se, x, y, ty) {
this.se = se;
this.x = x;
this.y = y;
this.ty = ty;
this.lth = null;
this.rth = null;
this.selected = false;
this.lthLine = null;
this.rthLine = null;
this.lthCircle = null;
this.rthCircle = null;
this.handleEl = this.createHandleEl();
}
createHandleEl() {
let handle = this.se.ui.createSvgElement("g", true);
handle.setAttribute("class", "handle");
handle.setAttribute("transform", `translate(${this.x} ${this.y})`);
// TODO: handles group should probably be variable in ui
document.getElementById("handles").appendChild(handle);
let inner = this.renderHandleEl();
handle.appendChild(inner);
return handle;
}
renderHandleEl() {
let r = 4;
let inner;
if (this.ty === "corner") {
inner = this.se.ui.createSvgElement("rect", true);
inner.setAttribute("x", -r);
inner.setAttribute("y", -r);
inner.setAttribute("width", r * 2);
inner.setAttribute("height", r * 2);
} else {
inner = this.se.ui.createSvgElement("circle", true);
inner.setAttribute("cx", 0);
inner.setAttribute("cy", 0);
inner.setAttribute("r", r);
}
inner.setAttribute("class", "handle");
return inner;
}
setTan(th, isRight) {
if (this.ty === "smooth" || !isRight) {
this.lth = th;
}
if (this.ty === "smooth" || isRight) {
this.rth = th;
}
}
updateSelLine(th, el, r) {
if (th === null && el !== null) {
el.remove();
el = null;
} else if (th !== null && el === null) {
el = this.se.ui.createSvgElement("line");
el.setAttribute("x1", 0);
el.setAttribute("y1", 0);
el.setAttribute("class", "tan");
this.handleEl.appendChild(el);
}
if (el !== null) {
el.setAttribute("x2", r * Math.cos(th));
el.setAttribute("y2", r * Math.sin(th));
}
return el;
}
updateSelCircle(th, el, r, computed) {
if (th === null && el !== null) {
el.remove();
el = null;
} else if (th !== null && el === null) {
el = this.se.ui.createSvgElement("circle", true);
el.setAttribute("r", 3);
el.setAttribute("class", "tanhandle");
this.handleEl.appendChild(el);
let tanHandle = new TanHandle(this, r > 0);
// To be more object oriented, receiver might be the knot or tanHandle. Ah well.
this.se.ui.attachReceiver(el, this.se, tanHandle);
}
if (el !== null) {
if (computed) {
el.classList.add("computed");
} else {
el.classList.remove("computed");
}
el.setAttribute("cx", r * Math.cos(th));
el.setAttribute("cy", r * Math.sin(th));
}
return el;
}
updateSelDecoration(selected, mode) {
if (!selected && this.selected) {
this.handleEl.classList.remove("selected");
} else if (selected && !this.selected) {
this.handleEl.classList.add("selected");
}
let lComputed = this.lth === null;
let rComputed = this.rth === null;
let drawCirc = selected && (mode !== "creating" && mode !== "dragging");
let drawLTan = !lComputed || drawCirc;
let drawRTan = !rComputed || drawCirc;
let lth = null;
if (drawLTan && this.lth != null) {
lth = this.lth;
} else if (drawLTan && this.computedLTh !== undefined) {
lth = this.computedLTh;
}
let rth = null;
if (drawRTan && this.rth != null) {
rth = this.rth;
} else if (drawRTan && this.computedRTh !== undefined) {
rth = this.computedRTh;
}
this.rthLine = this.updateSelLine(rth, this.rthLine, tanR2 - 3);
this.lthLine = this.updateSelLine(lth, this.lthLine, -tanR2 + 3);
if (!drawCirc) {
lth = null;
rth = null;
}
this.lthCircle = this.updateSelCircle(lth, this.lthCircle, -tanR2, lComputed);
this.rthCircle = this.updateSelCircle(rth, this.rthCircle, tanR2, rComputed);
this.selected = selected;
}
setTy(ty) {
if (ty !== this.ty) {
this.ty = ty;
let oldHandle = this.handleEl.querySelector(".handle");
this.handleEl.replaceChild(this.renderHandleEl(), oldHandle);
}
}
updatePos(x, y) {
this.x = x;
this.y = y;
this.handleEl.setAttribute("transform", `translate(${x} ${y})`);
}
}
class TanHandle {
constructor(knot, isRight) {
this.knot = knot;
this.isRight = isRight;
}
}
// TODO: create UI base class rather than cutting and pasting.
class Ui {
constructor() {
this.svgNS = "http://www.w3.org/2000/svg";
this.setupHandlers();
this.controlPts = [];
this.se = new SplineEdit(this);
this.gestureDet = new GestureDet(this);
this.showGrid = true;
this.se.renderGrid(this.showGrid);
this.setupDialogs();
this.keyHandlerActive = true;
}
setupHandlers() {
let svg = document.getElementById("s");
if ("PointerEvent" in window) {
svg.addEventListener("pointermove", e => this.pointerMove(e));
svg.addEventListener("pointerup", e => this.pointerUp(e));
svg.addEventListener("pointerdown", e => this.pointerDown(e));
} else {
// Fallback for ancient browsers
svg.addEventListener("mousemove", e => this.mouseMove(e));
svg.addEventListener("mouseup", e => this.mouseUp(e));
svg.addEventListener("mousedown", e => this.mouseDown(e));
// TODO: add touch handlers
}
window.addEventListener("keydown", e => this.keyDown(e));
this.mousehandler = null;
this.receiver = null;
}
attachHandler(element, handler) {
let svg = document.getElementById("s");
if ("PointerEvent" in window) {
element.addEventListener("pointerdown", e => {
svg.setPointerCapture(e.pointerId);
this.mousehandler = handler;
e.preventDefault();
e.stopPropagation();
});
} else {
element.addEventListener("mousedown", e => {
this.mousehandler = handler;
e.preventDefault();
e.stopPropagation();
});
// TODO: add touch handlers
}
}
/// This is the pattern for the new object-y style.
// Maybe just rely on closures to capture obj?
attachReceiver(element, receiver, obj) {
let svg = document.getElementById("s");
if ("PointerEvent" in window) {
element.addEventListener("pointerdown", e => {
this.gestureDet.onPointerDown(e);
svg.setPointerCapture(e.pointerId);
this.receiver = receiver;
receiver.onPointerDown(e, obj);
e.preventDefault();
e.stopPropagation();
});
} else {
element.addEventListener("mousedown", e => {
this.gestureDet.onPointerDown(e);
this.receiver = receiver;
receiver.onPointerDown(e, obj);
e.preventDefault();
e.stopPropagation();
});
// TODO: add touch handlers
}
}
pointerDownCommon(e) {
this.gestureDet.onPointerDown(e);
this.se.onPointerDown(e, null);
e.preventDefault();
}
pointerDown(e) {
let svg = document.getElementById("s");
svg.setPointerCapture(e.pointerId);
this.pointerDownCommon(e);
}
mouseDown(e) {
this.pointerDownCommon(e);
}
pointerMove(e) {
if (this.receiver !== null) {
this.receiver.onPointerMove(e);
} else if (this.mousehandler !== null) {
this.mousehandler(e);
} else {
this.se.onPointerHover(e);
}
e.preventDefault();
}
mouseMove(e) {
this.pointerMove(e);
}
pointerUpCommon(e) {
if (this.receiver !== null) {
this.receiver.onPointerUp(e);
}
this.mousehandler = null;
this.receiver = null;
e.preventDefault();
}
pointerUp(e) {
e.target.releasePointerCapture(e.pointerId);
this.pointerUpCommon(e);
}
mouseUp(e) {
this.pointerUpCommon(e);
}
keyDown(e) {
// Maybe would be better to use focus instead of explicit logic...
if (!this.keyHandlerActive) { return; }
let handled = this.se.onKeyDown(e);
if (handled) {
e.preventDefault();
}
}
// On Chrome, just offsetX, offsetY work, but on FF it takes the group transforms
// into account. We always want coords relative to the SVG.
getCoords(e) {
let svg = document.getElementById("s");
let rect = svg.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
return new Vec2(x, y);
}
createSvgElement(tagName, isRaw = false) {
let element = document.createElementNS(this.svgNS, tagName);
if (!isRaw) {
element.setAttribute("pointer-events", "none");
}
return element;
}
resetPlots() {
this.removeAllChildren(document.getElementById("plots"));
}
plotCircle(x, y, r = 2, color = "black", isRaw = false) {
let circle = this.createSvgElement("circle", isRaw);
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
circle.setAttribute("r", r);
if (color !== null) {
circle.setAttribute("fill", color);
}
document.getElementById("plots").appendChild(circle);
return circle;
}
tangentMarker(x, y, th) {
let len = 8;
let dx = len * Math.cos(th);
let dy = len * Math.sin(th);
let line = this.createSvgElement("line");
line.setAttribute("x1", x - dx);
line.setAttribute("y1", y + dy);
line.setAttribute("x2", x + dx);
line.setAttribute("y2", y - dy);
line.setAttribute("stroke", "green");
document.getElementById("plots").appendChild(line);
}
redraw() {
this.resetPlots();
let path = "";
let cmd = "M";
for (let pt of this.controlPts) {
path += `${cmd}${pt.x} ${pt.y}`;
cmd = " L";
}
document.getElementById("ctrlpoly").setAttribute("d", path);
let showMyCurve = true;
let showBiParabola = true;
let spline2Offset = 200;
let nIter = 10;
if (showMyCurve) {
let spline = new TwoParamSpline(new MyCurve, this.controlPts);
let ths = spline.initialThs();
for (let i = 0; i < nIter; i++) {
spline.iterDumb(i);
}
let splinePath = spline.renderSvg();
document.getElementById("spline").setAttribute("d", splinePath);
}
if (showBiParabola) {
let pts = [];
for (let pt of this.controlPts) {
pts.push(new Vec2(pt.x + spline2Offset, pt.y));
}
let spline2 = new TwoParamSpline(new BiParabola, pts);
spline2.initialThs();
for (let i = 0; i < nIter; i++) {
let absErr = spline2.iterDumb(i);
if (i == nIter - 1) {
console.log(`biparabola err: ${absErr}`);
}
}
let spline2Path = spline2.renderSvg();
document.getElementById("spline2").setAttribute("d", spline2Path);
}
/*
for (let i = 0; i < ths.length; i++) {
let pt = this.controlPts[i]
this.tangentMarker(pt.x, pt.y, -ths[i]);
}
*/
}
// TODO: extend so it can insert at an arbitrary location
addPoint(x, y) {
let ix = this.controlPts.length;
this.controlPts.push(new Vec2(x, y));
let handle = this.createSvgElement("circle", true);
handle.setAttribute("cx", x);
handle.setAttribute("cy", y);
handle.setAttribute("r", 4);
handle.setAttribute("class", "handle");
document.getElementById("handles").appendChild(handle);
this.attachHandler(handle, e => {
this.movePoint(handle, ix, e.offsetX, e.offsetY);
});
this.mousehandler = e => this.movePoint(handle, ix, e.offsetX, e.offsetY);
}
movePoint(handle, ix, x, y) {
handle.setAttribute("cx", x);
handle.setAttribute("cy", y);
this.controlPts[ix] = new Vec2(x, y);
this.redraw();
}
updateShowGrid(showGrid) {
if (showGrid) {
document.getElementById("show-grid-check").classList.remove("invisible");
} else {
document.getElementById("show-grid-check").classList.add("invisible");
}
this.se.renderGrid(showGrid);
this.showGrid = showGrid;
}
addMenuHandler(id, handler) {
document.getElementById(id).addEventListener("click", e => {
handler(e);
let el = e.target;
while (el.nodeName != "UL") {
el = el.parentNode;
}
// Hacks like this make me think we should just control the menu logic with
// JS instead of trying to leverage CSS. Oh well, it works...
el.classList.add("off");
window.setTimeout(() => el.classList.remove("off"), 50);
});
}
saveToJson() {
document.getElementById("save-json-modal").style.display = "block";
let jsonStr = JSON.stringify(this.se.serialize());
let el = document.getElementById("save-json-content");
this.removeAllChildren(el);
el.appendChild(document.createTextNode(jsonStr));
}
/// This displays the load dialog, doesn't do the load action.
loadFromJson() {
document.getElementById("load-json-modal").style.display = "block";
document.getElementById("load-text").focus();
this.keyHandlerActive = false;
}
doLoadAction(data) {
// TODO: error handling
this.se.deserialize(data);
document.getElementById("load-json-modal").style.display = "none";
this.keyHandlerActive = true;
}
setupDialogs() {
this.addMenuHandler("menu-save", e =>
this.saveToJson());
this.addMenuHandler("menu-load", e =>
this.loadFromJson());
this.addMenuHandler("menu-show-grid", e =>
this.updateShowGrid(!this.showGrid));
this.addMenuHandler("menu-delete", e =>
this.se.delete());
this.addMenuHandler("menu-help", e =>
document.getElementById("help-modal").style.display = "block");
document.getElementById("help-close").addEventListener("click", e =>
document.getElementById("help-modal").style.display = "none");
document.getElementById("save-json-close").addEventListener("click", e =>
document.getElementById("save-json-modal").style.display = "none");
document.getElementById("load-json-close").addEventListener("click", e => {
document.getElementById("load-json-modal").style.display = "none";
this.keyHandlerActive = true;
});
document.getElementById("load-button").addEventListener("click", e =>
this.doLoadAction(document.getElementById("load-text").value));
}
removeAllChildren(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
}
let ui = new Ui();

BIN
static/visual-traces/0033d7f98e0a5c733b16013ecd8005fa.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/visual-traces/diversity-2019.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
static/visual-traces/etherpump.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

40
templates/base.html

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="utf-8" />
<title>Iterative Annotation Tools</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/stylesheet.css')}}">
{% block head %}
{% endblock %}
</head>
<body>
<div id="tools-nav">
{% if tool is defined %}
<a href="/"><button></button></a>
{% else %}
{% block toolsnav %}
<a href="/"><button style="visibility: hidden;"></button></a>
{% endblock %}
{% endif %}
{% if tools %}
{% for t in tools %}
{% if tool == t %}
<a href="/{{ t }}/"><button style="color:magenta;">{{ t }}</button></a>
{% else %}
<a href="/{{ t }}/"><button>{{ t }}</button></a>
{% endif %}
{% endfor %}
{% endif %}
</div>
<div id="tools-extra">
{% block toolsextra %}
{% endblock %}
</div>
<div id="main">
{% block page %}
{% endblock %}
</div>
</body>
{% block footer %}
{% endblock %}
</html>

48
templates/color.html

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% block page %}
<div class="info">
<p><strong class="handle">*2</strong>: “we”, temporary structures that condition work (who? where? how? why?)</p>
<p><strong class="annotation">color</strong>: What colors are used to engage with <strong class="handle">*2</strong>?</p>
</div>
<hr>
<form action="" method="GET">
<div id="inputfields">
Select a color: <input type="color" name="color"/><br>
</div>
<br>
<input class="submit" type="submit" value="submit"/>
<input type="reset">
<hr>
<div id="colors">
<h2><strong class="annotation">colors</strong> gathered so far include:</h2>
<br>
<table>
<thead>
<th>marker</th>
<th>description</th>
<th>status</th>
</thead>
<tbody>
{% for x in db.keys() %}
<tr>
<td>
<div id="color-preview" style="background-color: {{ db[x]['color'] }}"></div>
</td>
<td>
<textarea cols="100" rows="8" name="description-{{x}}"></textarea>
</td>
<td>
<select name="status-{{x}}">
<option value="include" selected="selected">include</option>
<option value="unsure">unsure</option>
<option value="delete">delete</option>
</select>
</td>
{% endfor %}
</tbody>
</table>
</form>
</div>
{% endblock %}

78
templates/curves.bak.html

@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block head %}
<!-- <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/curves.css')}}"> -->
{% endblock %}
{% block page %}
<div class="block">
<p><strong class="handle">*1</strong>: systematics, temporalities (group formations)</p>
<p><strong class="annotation">curves</strong>: What curves are used to engage with <strong class="handle">*1</strong>?</p>
</div>
<hr>
<form action="" method="GET">
<!-- https://spline.technology/demo/# -->
<!-- <div class="modal" id="help-modal">
<div class="modal-content">
<span class="close" id="help-close">&times;</span>
<p><b>A new spline</b></p>
<p>Add a new corner point: click. Add a new smooth point, Alt + click, or click and drag.</p>
<p>Select multiple points: Shift + click.</p>
<p>Refine a curve: click on the curve and drag.</p>
<p>Set an explicit tangent: click and drag on the handles. Set axis-aligned: Shift + click. Unset an explicit tangent: drag away.</p>
<p>Toggle between smooth and corner points: double-click.</p>
<p>Delete a point: delete (or backspace) key.
<p>Nudge points by 1 px: arrow keys. Nudge by 10 px: Shift + arrow keys.
<p>Copyright 2018 Raph Levien</p>
<p>Github repo: <a href="https://github.com/raphlinus/spline-research">raphlinus/spline-research</a></p>
</div>
</div>
<div class="modal" id="save-json-modal">
<div class="modal-content">
<span class="close" id="save-json-close">&times;</span>
<pre id="save-json-content"></pre>
</div>
</div>
<div class="modal" id="load-json-modal">
<div class="modal-content">
<span class="close" id="load-json-close">&times;</span>
<p><b>Load from JSON</b></p>
<textarea id="load-text" placeholder="Paste JSON data here..." rows=20></textarea>
<p><button id="load-button" type="button">Load</button></p>
</div>
</div>
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none" />
<path id="spline" d="" stroke="black" fill="none" stroke-width="2" />
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2" />
<g id="handles" />
<g id="plots" />
</svg> -->
<!-- -->
<!-- <input class="ready" type="button" value="ready" onclick="ready()" /> -->
<input type="file" name="curve" accept=".svg"><br>
<!-- <textarea id="curve" name="curve" style="display:none;" required></textarea> -->
<input class="submit" type="submit" value="submit"/>
<a id="reset" href="/curves/"><input class="reset" type="button" value="reset"></a>
</form>
<hr>
<div id="shapes">
<h2><strong class="annotation">shapes</strong> gathered so far include:</h2>
{% for x in db.keys() %}
{{ db[x]['shape'] | safe }}
{% endfor %}
</div>
{% endblock %}
{% block footer %}
<!-- <script type="text/javascript">
function ready(){
var svg = document.getElementById("s").outerHTML;
console.log(svg);
// var svg = svg.innerHTML();
var shape = document.getElementById("shape").innerHTML = svg;
}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bezpath.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/curves.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/splineui.js')}}"></script> -->
{% endblock %}

99
templates/curves.html

@ -0,0 +1,99 @@
{% extends "base.html" %}
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/curves.css')}}">
{% endblock %}
{% block page %}
<div class="info">
<p><strong class="handle">*1</strong>: systematics, temporalities (group formations)</p>
<p><strong class="annotation">curves</strong>: What curves are used to engage with <strong class="handle">*1</strong>?</p>
</div>
<hr>
<form action="" method="GET">
<!-- https://spline.technology/demo/# -->
<div class="modal" id="help-modal">
<div class="modal-content">
<span class="close" id="help-close">&times;</span>
<p><b>A new spline</b></p>
<p>Add a new corner point: click. Add a new smooth point, Alt + click, or click and drag.</p>
<p>Select multiple points: Shift + click.</p>
<p>Refine a curve: click on the curve and drag.</p>
<p>Set an explicit tangent: click and drag on the handles. Set axis-aligned: Shift + click. Unset an explicit tangent: drag away.</p>
<p>Toggle between smooth and corner points: double-click.</p>
<p>Delete a point: delete (or backspace) key.
<p>Nudge points by 1 px: arrow keys. Nudge by 10 px: Shift + arrow keys.
<p>Copyright 2018 Raph Levien</p>
<p>Github repo: <a href="https://github.com/raphlinus/spline-research">raphlinus/spline-research</a></p>
</div>
</div>
<div class="modal" id="save-json-modal">
<div class="modal-content">
<span class="close" id="save-json-close">&times;</span>
<pre id="save-json-content"></pre>
</div>
</div>
<div class="modal" id="load-json-modal">
<div class="modal-content">
<span class="close" id="load-json-close">&times;</span>
<p><b>Load from JSON</b></p>
<textarea id="load-text" placeholder="Paste JSON data here..." rows=20></textarea>
<p><button id="load-button" type="button">Load</button></p>
</div>
</div>
<svg id="s" class="canvas" width="640" height="480" pointer-events="all">
<path id="ctrlpoly" d="" stroke="none" fill="none" />
<path id="spline" d="" stroke="black" fill="none" stroke-width="2" />
<path id="spline2" d="" stroke="blue" fill="none" stroke-width="2" />
<g id="handles" />
<g id="plots" />
</svg>
<!-- -->
<input class="ready" type="button" value="ready" onclick="ready()" /><br>
<textarea id="curve" name="curve" cols="89" rows="10" required></textarea><br>
<input class="submit" type="submit" value="submit"/>
<a id="reset" href="/curves/"><input class="reset" type="button" value="reset"></a>
<hr>
<div id="curves">
<h2><strong class="annotation">curves</strong> gathered so far include:</h2>
<br>
<table>
<thead>
<th>marker</th>
<th>description</th>
<th>status</th>
</thead>
<tbody>
{% for x in db.keys() %}
<tr>
<td>
{{ db[x]['curve'] | safe }}
</td>
<td>
<textarea cols="100" rows="25" name="description-{{x}}"></textarea>
</td>
<td>
<select name="status-{{x}}">
<option value="include" selected="selected">include</option>
<option value="unsure">unsure</option>
<option value="delete">delete</option>
</select>
</td>
{% endfor %}
</tbody>
</table>
</div>
</form>
{% endblock %}
{% block footer %}
<script type="text/javascript">
function ready(){
var svg = document.getElementById("s").outerHTML;
// console.log(svg);
var curve = document.getElementById("curve").innerHTML = svg;
}
</script>
<script type="text/javascript" src="{{ url_for('static', filename='js/bezpath.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/curves.js')}}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/splineui.js')}}"></script>
{% endblock %}

58
templates/index.html

@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block page %}
<svg id="index" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path id="title" fill="none" stroke="blue"
d="M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50" />
<text>
<textPath id="title" href="#title">
Iterative Annotation Tools
</textPath>
</text>
</svg>
<hr>
<div class="block">
<!-- <p><strong class="handle">*</strong></p> -->
<h2>handles</h2>
<p><strong class="handle">*1</strong>: systematics, temporalities (group formations)</p>
<p><strong class="handle">*2</strong>: “we”, temporary structures that condition work (who? where? how? why?)</p>
<p><strong class="handle">*3</strong>: conditions of possibility, units of measurement for succes, sharing how-to’s (how to co-exist, basically)</p>
<p><strong class="handle">*4</strong>: transitional moments of handing over, generative troublematics (collective response-ability)</p>
<br>
</div>
<div class="block">
<!-- <p><strong class="annotation">&nbsp;</strong></p> -->
<h2>annotations</h2>
<p><strong class="annotation">curves</strong>: What curves are used to engage with <strong class="handle">*1</strong>?</p>
<p><strong class="annotation">color</strong>: What colors are used to engage with <strong class="handle">*2</strong>?</p>
<p><strong class="annotation">text</strong>: Which anecdotes/questions/vocabulary/glossary/tags engage with <strong class="handle">*3</strong>?</p>
<p><strong class="annotation">visual traces</strong>: Which visual traces can we “cut out” that engage with <strong class="handle">*4</strong>? <!-- What questions can we formulate and attach as annotations to image documentation of the hand-over-moments? --></p>
</div>
<hr>
<p><u>stage a</u> - making markers: based upon each contribution using these tools</p>
<p><u>stage b</u> - annotate: throughout the publication</p>
<hr>
<!-- <p>< < iterations documentation from where the contributions were extracted > handles > annotation tools < contributions < handles > ></p>
-->
<!-- <hr> -->
<div class="info">
<p><strong class="handle">*1 shapes</strong></p>
<p><strong class="handle">*2 color</strong></p>
<p><strong class="handle">*3 text</strong></p>
<p><strong class="handle">*4 visual traces</strong></p>
</div>
<hr>
{% endblock %}

52
templates/text-scans.html

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/text-scan.css')}}">
{% endblock %}
{% block toolsnav %}
<a href="/"><button></button></a>
{% endblock %}
{% block toolsextra %}
<a href="/text-scans/"><button style="color:magenta;">text-scans</button></a>
<!-- <a href="/text-specifics/"><button>text-specifics</button></a> -->
{% endblock %}
{% block page %}
<div class="info">
<strong>text-scans</strong>
</div>
<hr>
<div id="wrapper">
<div id="crossings">
{% if data == {} %}
<div>I have no words for this.</div>
{% else %}
<!-- <h1 style="margin-bottom:0;margin-top:1.25em;line-height: 0;">attachments</h1> -->
{% for word_type, ranking in data.items() %}
<div id="{{ word_type }}">
<h1>{{ word_type }}</h1>
{% for rank, words in ranking.items() %}
{% for word in words.keys() %}
<a href="/text-scans/{{ word_type }}/{{ word }}">{{ word }}</a><sup>{{ words[word]['count'] }}</sup>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
{% endif %}
</div>
<div id="sentences">
{% if results == {} %}
<h1></h1>
{% else %}
<h1><em>{{ word }}</em> ({{ word_type }})</h1>
{% for document, sentences in results['sentences'].items() %}
{% for sentence in sentences %}
<div class="{{ word_type }} sentence">{{ sentence|markdown }} <small>({{ document }})</small></div>
{% endfor %}
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}

38
templates/text-specifics.html

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/text-scan.css')}}">
{% endblock %}
{% block toolsnav %}
<a href="/"><button></button></a>
{% endblock %}
{% block toolsextra %}
<a href="/text-scans/"><button>text-scans</button></a>
<a href="/text-specifics/"><button style="color:magenta;">text-specifics</button></a>
{% endblock %}
{% block page %}
<div class="info">
<strong>text-specifics</strong>
<div>words with a TF-IDF score > 0.01</div>
<br>
<div>NOTE: the "specificity" scanning does not work when different languages are mixed</div>
</div>
<hr>
<div id="wrapper">
{% for filename in index.keys() %}
<div>
<h1>{{filename}}</h1>
{% if 'tfidf' in index[filename].keys() %}
{% for word, value in index[filename]['tf'].items() %}
{% if value > 0.01 %}
{{ word }}<sup>({{ value}})</sup><br>
{% endif %}
{% endfor %}
{% endif %}
</div>
{% endfor %}s
</div>
{% endblock %}

56
templates/text.html

@ -0,0 +1,56 @@
{% extends "base.html" %}
{% block toolsextra %}
<a href="/text-scans/"><button>text-scans</button></a>
<!-- <a href="/text-specifics/"><button>text-specifics</button></a> -->
{% endblock %}
{% block page %}
<div class="info">
<p><strong class="handle">*3</strong>: conditions of possibility, units of measurement for succes, sharing how-to’s (how to co-exist, basically)</p>
<p><strong class="annotation">text</strong>: Which anecdotes/questions/vocabulary/glossary/tags engage with <strong class="handle">*3</strong>?</p>
</div>
<hr>
<form action="" method="GET">
<textarea cols="100" rows="15" name="marker"></textarea><br>
type:
<select name="marker-type">
<option value="anecdote">anecdote</option>
<option value="question">question</option>
<option value="glossary">glossary</option>
<option value="tag">tag</option>
</select>
<input class="submit" type="submit" value="submit"/>
<input class="reset" type="reset">
</form>
<hr>
<div id="markers">
<h2><strong class="annotation">textual markers</strong> gathered so far include:</h2>
<br>
<table>
<thead>
<th>marker</th>
<th>description</th>
<th>status</th>
</thead>
<tbody>
{% for x in db.keys() %}
<tr>
<td>
<div>({{ db[x]['type'] }}) {{ db[x]['marker'] }}</div>
</td>
<td>
<textarea cols="100" rows="5" name="description-{{x}}"></textarea>
</td>
<td>
<select name="status-{{x}}">
<option value="delete">delete</option>
<option value="unsure">unsure</option>
<option value="include" selected="selected">include</option>
</select>
</td>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

50
templates/visual-traces.html

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block page %}
<div class="info">
<p><strong class="handle">*4</strong>: transitional moments of handing over, generative troublematics (collective response-ability)</p>
<p><strong class="annotation">visual traces</strong>: Which visual traces can we “cut out” that engage with <strong class="handle">*4</strong>? <!-- What questions can we formulate and attach as annotations to image documentation of the hand-over-moments? --></p>
</div>
<hr>
<form method="POST" enctype=multipart/form-data>
<input type="file" name="trace" accept=".png, .gif|image/*"><br>
<input type="submit" value="upload"/>
<a id="reset" href="/visual-traces/"><input class="reset" type="button" value="reset"></a>
<br>
<small>
<strong>Works with file formats</strong>: png, gif<br>
<strong>Note</strong>: please submit traces with transparent backgrounds!
</small>
<hr>
<div id="traces">
<h2><strong class="annotation">visual-traces</strong> gathered so far include:</h2>
<br>
<table>
<thead>
<th>marker</th>
<th>description</th>
<th>status</th>
</thead>
<tbody>
{% for x in db.keys() %}
<tr>
<td>
<img class="traces" src="{{ url_for('static', filename='visual-traces/')}}{{ db[x]['trace'] }}">
</td>
<td>
<textarea cols="100" rows="15" name="description-{{x}}"></textarea>
</td>
<td>
<select name="status-{{x}}">
<option value="include" selected="selected">include</option>
<option value="unsure">unsure</option>
<option value="delete">delete</option>
</select>
</td>
{% endfor %}
</tbody>
</table>
</form>
</div>
{% endblock %}

0
text.json

30
text.py

@ -0,0 +1,30 @@
# text annotation tool
# see handles on line 532 and further: https://pad.constantvzw.org/p/iterations-publication
def intensify(string, intensity):
intensified = ''
for character in string:
if character != ' ':
intensified += (character * intensity)
else:
intensified += ' '
return intensified
def scopify(string, reach):
marker = ':' * reach
scope = '{} {} {}\n'.format(marker, string, marker)
return scope
def temporalify(string, duration):
temporalified = string * duration
return temporalified
def translate(string, intensity=3, duration=2, reach=5):
string = intensify(string, intensity)
string = scopify(string, reach)
string = temporalify(string, duration)
return string
if __name__ == '__main__':
string = 'hello'
print(translate(string))

131
tfidf.py

@ -0,0 +1,131 @@
import os, json, re
from math import log, exp
import nltk
from nltk import sent_tokenize
from nltk.tokenize import RegexpTokenizer
tokenizer = RegexpTokenizer(r'\w+') # initialize tokenizer
import pprint
pp = pprint.PrettyPrinter(indent=4)
def tfidf(query, words, corpus):
# Term Frequency
tf_count = 0
for word in words:
if query == word:
tf_count += 1
tf = tf_count/len(words)
# print('TF count:', tf_count)
# print('Total number of words:', len(words))
# print('TF - count/total', tf_count/len(words))
# Inverse Document Frequency
idf_count = 0
for words in corpus:
if query in words:
idf_count += 1
# print('count:', idf_count)
idf = log(len(corpus)/idf_count)
# print('Total number of documents:', len(corpus))
# print('documents/count', len(corpus)/idf_count)
# print('IDF - log(documents/count)', log(len(corpus)/idf_count))
tfidf_value = tf * idf
# print('TF-IDF:', tfidf_value)
return tf, idf_count, tfidf_value
def get_language(document):
match = re.search(r'\[.*\]', document, flags=re.IGNORECASE)
if match:
language = match.group().replace('[','').replace(']','').lower()
else:
language = 'undefined'
return language
def load_text_files():
files = []
corpus = []
sentences = {}
wordlists = {}
dir = 'static/contributions/'
for document in sorted(os.listdir(dir)):
if not 'annotated' in document:
document = document.replace('.md','')
# print('document:', document)
lines = open('{}/{}.md'.format(dir, document), "r").read() # list of lines in .txt file
# lines = lines.replace(' •', '. ') # turn custom linebreaks into full-stops to let the tokenizer recognize them as end-of-lines
words = [word.lower() for word in tokenizer.tokenize(lines)] # all words of one document, in reading order + lowercased! (!important)
wordlists[document] = words
corpus.append(words)
s = sent_tokenize(lines)
sentences[document] = s
files.append(document) # list of filenames
print('---------')
print('*md files loaded*')
return files, corpus, sentences, wordlists
def make_human_readable_name(document):
name = document.replace('_', ' ').replace('-', ' ')
return name
def create_index():
files, corpus, sentences, wordlists = load_text_files()
index = {}
# index = {
# Fem document : {
# 'sentences' : [],
# 'tf' : {
# 'aap': 4,
# 'beer': 6,
# 'citroen': 2
# },
# 'idf' : {
# 'aap': 2,
# 'beer': 1,
# 'citroen': 5
# },
# 'tfidf' : {
# 'aap': 39.2,
# 'beer': 20.456,
# 'citroen': 3.21
# },
# 'name': 'Feminist document (2000)',
# 'language': 'en'
# }
# }
for document in files:
print('---------')
print('document:', document)
index[document] = {}
index[document]['sentences'] = sentences[document]
words = wordlists[document]
for word in words:
tf_count, idf_count, tfidf_value = tfidf(word, words, corpus)
if 'tf' not in index[document]:
index[document]['tf'] = {}
index[document]['tf'][word] = tf_count
if 'idf' not in index[document]:
index[document]['idf'] = {}
index[document]['idf'][word] = idf_count
if 'tfidf' not in index[document]:
index[document]['tfidf'] = {}
index[document]['tfidf'][word] = tfidf_value
index[document]['language'] = get_language(document)
index[document]['name'] = make_human_readable_name(document)
with open('index.json','w+') as out:
out.write(json.dumps(index, indent=4, sort_keys=True))
out.close()
print('---------')
print('*index created*')
print('---------')
# create_index()

0
visual-traces.json

Loading…
Cancel
Save