-
Notifications
You must be signed in to change notification settings - Fork 35
cascading properties sheets
Demo of CPS: http://metapolator.com/red-pill-demo/
Metapolator is a parametric font tool. To make working with parameters familiar to web developers, we use the language of Cascading Style Sheets.
This file is a work in progress. It will lead us through the development of our CPS Tools and eventually become the documentation thereof.
CPS is not CSS and this name conveys that you can't use the properties that you know from CSS. However the syntax is very much the same and in fact we use a CSS Parser program to read CPS.
How CPS is similar to CSS:
- CSS syntax means you can write CPS in a familiar way, with a familiar way of thinking
- CSS selectors allow us to specify rules for a set of elements, or for just one distinct element
- CSS cascading so that when more than one rule can apply to an element, the most specific one wins
How CPS is not like CSS:
- CSS properties are different, as we have our own structure, MOM
- CSS media-queries are different, with our own @-rules
- CSS selectors are only partially available today, so selecting the glyph after the glyph 'a' with
glyph#a + glyphis not yet possible - You can define CPS properties with CPS - it is self-extensible!
Why is this a good thing?
- If you know CSS, you will feel comfortable with CPS
- CPS has broad possibilities, so we hope other applications can use it
To create the maximum of interoperability of CPS tools we try to preserve as much information found when parsing CPS as possible. That means: We keep what we don't understand and just output it again when serializing CPS
This matters because CPS is self extensible, so when new properties and selectors are defined, they will always be loaded without being lost. You can even mix CSS and CPS. But that is for the future. Today a practical use of this is for loading CPS from another Metapolator Project that defines other properties, and being able to operate on the available ones: We can load properties from Lobster into Libre Baskerville.
We are planning to support multi-user scenarios, where a lot of people can edit one project simultaneously. Therefore we need to restrict the access to the data-model using special APIs. Since our planning for multiuser editing is not finished yet, we have approached the problem by setting some rules, that in the best case will just hold true when we encounter the real problem and in the worst case shields us from having a lot to rewrite.
So far, the plan is that we are round-tripping a change from the UI to the model and beyond. The UI will commission changes on the data, but the authority to change that data will be in the responsibility of other code, namely, a concept that we call the "Broker" for now. The Model, from the perspective of the UI will be an API of the Broker. The Broker, will inform the UI of changes on the model (using an event/message like system probably). There should be no difference from where the change was made, either from the local computer or from one far away. So when the UI changes a parameter, the value will be in an "unresolved" state, unless the broker sets it to its new value. This must be, because we don't want to reset the display of the value after we submitted the change-request, this would only cause confusion. This, however, implies that we should lock the value until we receive the answer, otherwise the user might be confused by the control element moving autonomously.
In general the items in a CPS Object Model are divided into two big categories:
- the ones we understand
- the ones we don't (even try to) understand, represented by the one single object-type, the
GenericCPSNode.
The items we don't understand will have a string representation, that resembles how the rule displayed in the source CSS. So we can use this for the interface, or let the user choose to hide these pieces. We keep these items around, so that we can just reinsert them in new serializations of the CPS.
The items we understand will provide all the APIs we need to create and change the parameters of Metapolator.
The base for the CPS Object-Model is established in
app/lib/models/parameters
- parse a CPS/CSS string into the object structure as outlined below
- serialize the object structure back into an equivalent CPS string (formatting differs, some comments may have another position, but at the element where they originally where found)
- the object structure is ready to be filled with useful methods ;-)
- we have ways to just keep unknown data around
- understand properties, parse
ParameterValue-objects orSelector-Objects - query the
ParameterCollectionfor rules that apply for a certain object, this will need the MOM (Metapolator Object Model) for interaction anyways. - change data in the object structure (we'll add APIs for that of course)
- receive values from the structure
After some tests the Gonzales CSS Parser was chosen. Not only because the name implies that it is fast ;-) Also, because the Abstract Syntax Tree (AST) that it creates looks like a very complete CSS implementation and it allows us to keep the parts we don't interpret around, so we can obey the "We don't delete!" rule. First tests showed, that we might even be able to restore the line numbers from the AST (which it sadly does not provide by itself). That would be a great help with debugging the CPS, the method used, however, still has to be tested against real-life situations.
The parsing of CPS/CSS with gonzales, reading the AST into the CPS-Object-Model and serializing the result again can currently be seen in http://localhost:8000/playground.html.
Besides some reformatting into a much prettier form, the important information here is that the output block is the result of a complete roundtrip: string -> gonzales AST -> CPS-Object-Model -> string
input:
body{
background-color /*property comment*/: #fff;
margin: 0 auto;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='a.png',sizingMethod='scale')}/* And a comment */ strange>.selector/* selector comment
with linebreak */,and another onemissing.delim >{ any-param
: unheard-of(style + css);/* a comment within a block */another-one: def-unheard-of(style + css)/*this param has a comment*/;}@media whatever{ hi{name:val} }output:
body {
background-color /*property comment*/: #fff;
margin: 0 auto;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='a.png',sizingMethod='scale');
}
/* And a comment */
strange>.selector/* selector comment
with linebreak */,
and another onemissing.delim > {
any-param: unheard-of(style + css);
/* a comment within a block */
another-one: /*this param has a comment*/ def-unheard-of(style + css);
}
@media whatever{ hi{name:val} }The most intersting part happens in
app/lib/models/parameters/factories.js.
Where all the logic for the conversion from AST to CPS-Object-Model is contained.
The code is richly documented, so it may be worth a look.
One of the best things with the factory approach here is, that we could change the parser without touching the CPS-Object-Model.
We should work in that direction and reduce all touching points with the AST of gonzales to the factories module--this is not fully the case at the moment, because some Object Model classes still receive GenericCPSNode-objects where there should be processable values instead.
To parse CPS into the object model, the module app/lib/models/parameters/factories provides the methods rulesFromString and rulesFromAST. Internally rulesFromString uses gonzales to parse the string and then invoke rulesFromAST with the AST from gonzales.
The resulting object is an instance of ParameterCollection.
All objects used in the CPS Object-Model so far are:
-
Sourceevery_Nodehas one, so we can track the origin of it. -
_Nodeat the moment all items in the CPS-Object-Model inherit from_Node._Nodeitself inherits from_BaseModel._Nodehas theSourceand line-number information of each CPS Object-Model item. -
ParameterCollectionconsists at the highest level of a list ofComment,GenericCPSNodeandRule-objects.GenericCPSNode-objects that only contain whitespace (type == "s") are dumped.equivalent gonzales AST item:
stylesheet -
Commentcan occur at different positions in the CPS. So far, nodes aware of comments are:ParameterCollectionSelectorParameterDictParameterNameParameterValue
equivalent gonzales AST item:
comment -
GenericCPSNodeThis node stores the parts of the AST that we don't interpret. It's the basic concept to achieve the "We don't delete!" rule. A GenericCPSNode can be converted back to a CPS-string without loss of information. GenericCPSNodes occur at different positions in the Collection:-
ParameterCollectionfor example @-Rules as in@media screen {}are not yet interpreted, they are just stored as GenericCPSNodes and recreated upon serialization -
ParameterDictunknown parameters are/will be kept as GenericCPSNodes
Also at the moment:
SelectorParameterValue
Both keep their contents as a mixed list
GenericCPSNodes andComments, but this will change as soon as we start to interpret their values for our uses.equivalent gonzales AST item:
(anything else) -
-
RuleContainer for oneSelectorListand oneParameterDictequivalent gonzales AST item:
ruleset -
SelectorListContainer for a list ofSelector-objectsequivalent gonzales AST item:
selector -
Selectorone CPS selector. The value of the selector is currently not further processed and consists ofGenericCPSNode-objects and Comments. Whitespace is not removed because that could change the meaning of the selector. We'll have to interpret the value of aSelectorfurther, waiting for the first use case.equivalent gonzales AST item:
simpleselector -
ParameterDicta list ofParameter,CommentandGenericCPSNodeAnd it will provide a dictionary like access to theParameter-objectsequivalent gonzales AST item:
block -
Parametera container for the key-value pair ofParameterNameandParameterValueequivalent gonzales AST item:
declaration -
ParameterNamethe name of aParameterand a list ofComment-objects (that's possible in CSS). The list of comments can be empty of course.equivalent gonzales AST item:
parameter -
ParameterValuethe value of aParameter, currently list ofGenericCPSNode-objects and a list ofComment-objects. This current state with theGenericCPSNode-objects will change when we start to interpret the value. Also, at the moment, this results in an unfortunate situation when serializing, because we have to keep the whitespace information around. When the values are interpreted, the whitespace situation will resolve.equivalent gonzales AST item:
value
AtRuleNameAtRuleCollectionAtNamespaceCollection- Namespaces in general
I suggest 2 stages: The first as a pure display of the model. The second stage would add the ability to change values. There a more features that we are going to add subsequently.
We will start with a very simple viewer, that takes a CascadedParameters object and displays its values. We will differentiate between raw AST items and items that we understand.
- The Generic AST-Items (and others that we don't touch, if there is more) will have a text only representation, and may be relocatable and deletable manually by the user.
- Plugins will define the available parameters AND how to display and change them, this includes information like: value ranges, kind of value, tooltips, further documentation etc. So we can, in the end, have interfaces like sliders, switches, text-fields etc.
Both Modes Change RuleSets (CRUD), Reorder stuff, Edit Comments (CRUD)
Both modes load using a CascadedParameters object, the CascadedParameters may expose useful API-methods, so that we can keep knowledge away from the UI wherever possible.
File Mode Display one "file" or more exact "source" of CPS. This will make it possible to change the order of RuleSets.
Entity Mode This will display all Rulesets that apply to one entity, pretty much like Firebug does when selecting one single element. The RuleSets will be expose including their information about their source (name and line number). This will, similar like in Firebug, display which parameters are responsible for the final value and where overridden. The ordering will not be created by the UI, but by Model API, as well as the selection of the applying rules are made in Model-land.
This a developer documentation wiki. It can only be edited by Metapolator team members, but joining our team is easy - just ask in the Metapolator G+ Community
Pro Tip: This wiki can eat your work if you click submit after someone else also edited this page and already clicked submit before you! So select all and copy your version first, before clicking submit.