Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support style properties with a uniform mechanism #263

Closed
joeberkovitz opened this issue Nov 12, 2021 · 27 comments
Closed

Support style properties with a uniform mechanism #263

joeberkovitz opened this issue Nov 12, 2021 · 27 comments
Milestone

Comments

@joeberkovitz
Copy link
Contributor

joeberkovitz commented Nov 12, 2021

Preamble

The idea of MNX style properties and classes was first presented in the original draft spec. That proposal was rejected some time ago, and is not part of 1.0. It relied on CSS syntax, it was overcomplicated, and it didn't anticipate <system-layout> or <score>.

However, there's still a need for style properties as evidenced by the fact that styles and classes have been proposed multiple times in the CG to solve problems with text, dynamics, graphics, repeats. Each proposal has been a little different. It makes sense to revisit styles and address them in a way that's more uniform. That way we can have a single useful tool that can be rolled out repeatedly when we need it. This proposal tries to put forth a new vision of styles and how they fit into MNX 1.0.

This proposal is big enough to need a fair amount of up-front rationales and definitions before getting into the meat of it all, so thanks for your patience in reading. I can't see how to do this more briefly. At least, this proposal is a lot simpler than CSS both conceptually and syntactically.

What do style properties control?

Here are some possible examples that illustrate the variety of possible styles:

  • color
  • stem orientation
  • note head size
  • music or text font family
  • fine-grained X/Y offsets
  • visibility / audibility
  • enharmonic spelling variants

Let's not debate the full set of style properties here. Those can be broken out as separate issues once the mechanism is clarified.

Design Goals

  • A set of style properties can be computed to control each element's rendering. A set of optional property values must be available to control the presentation and performance of each element. We speak of these properties as "styling" the element in question.
  • Style computation is independent of semantics. The determination of style properties should be driven by a single mechanism that works the same way for all notation elements, and all properties.
  • Style properties may be semantics-dependent. It's to be expected that not all style properties apply to every element. Example: an italic font style does not affect the appearance of noteheads.
  • Straightforward computation of effective properties. The computation of the effective set of properties for an element must be straightforward and easy for encoders/decoders to understand and implement.
  • No special style syntax beyond XML. No full-on CSS syntax parsing. However property value types e.g. RGBA colors or dimensions may require simple parsing.
  • Simple, explicit styles can be encoded directly on an element. Example: change the color of one note; set the music font of the entire document.
  • Some style properties can be inherited from an ancestor element. Example: apply a different note spacing to all events in one specific measure. Set the music font family for the entire score.
  • Some style properties can be applied by a layout. Example: alter the music font size for a single-part layout of a score vs. a conductor score.
  • Some style properties can be applied by a score. Example: alter the music font size for large-print edition of a single-part score, vs. the regular-print edition (both use the same layout).
  • Styles can affect both graphical and performance rendering. Example of a performance style: fine-tune the dynamics of some events for a particular interpretation of cresc.

Architecture

For the latest architecture please jump ahead to this post: #263 (comment)

Click on the text below to expand the originally-posted architecture which has been superseded:

Original architecture proposal

Style computation

"Target elements" are things you can see and/or hear. For example, <note> is a kind of target element. A target's appearance and/or sound has default characteristics, but that can be altered by one or more style properties e.g. color, volume, and so on.

Any element can serve as a "style source". This means that it defines a particular set of style properties, which in turn affect a set of one or more target elements.

Obviously, a target element can always be a style source for itself. A note might specify that its own color is blue, for example.

However, other elements can also be style sources for the same target element. For example, our note's parent <event> might specify that all its child notes are blue. Or a system layout that's in effect, might specify that all note heads in some part are shown smaller than normal. Or the document as a whole might specify a uniform music font to be used everywhere. In these examples, one style source controls many target elements.

Style sources can include elements living in <global>, <part>, <system-layout> and <score>. To control how these many sources work on each target element, we need a well-defined algorithm. Therefore, the set of style properties for any single target element are derived from a number of style sources, consulted in the following order, highest priority first:

  • an applicable element within a <score>, followed by its ancestors
  • an applicable element within a <system-layout>, followed by its ancestors
  • the target element itself, followed by its ancestors (except <global>)
  • within <global>, any <measure> to which the target element belongs
  • the <global> element

Much of the utility of styles lies in this priority order. It establishes a useful scheme, in which specific values take precedence over basic defaults.

Sources, properties, and classes

A style source can supply a target element's properties directly. To do this, it defines some name/value pairs of style properties, attached directly to the style source in XML. The style source in effect is saying, "here are the style properties for target elements that I apply to". When the style source is consulted as part of style computation, these properties are simply applied to the target (if they weren't already defined with a higher priority).

However, target elements can also name "style classes" to which they belongs. A style class lets the target element say, "I am a certain kind of thing", without declaring any style properties. In turn, other elements like layouts or scores can say, "Here are the style properties for that kind of thing", by supplying a definition of that same style class.

This lets semantic elements be very specific about their differences from other elements, while leaving the details up to <score> or <system-layout> elements. For example a <note> might say it belongs to a class named small. A <staff-layout> being used for some score might specify that within it, a small note is 0.75 the size of a regular note; another layout might instead specify 0.85 because it just looks better in that layout.

Specifying attributes in a style source

A basic choice confronting us is how to specify a set of property values in MNX for some style source. Here are several approaches that could be taken; they all work at a mechanical level. For simplicity we stick to one example: changing the color of all elements in a sequence to make them blue, and also making all notes in the sequence smaller than normal. Thus, we are supplying style properties for a sequence (which itself is invisible), to be inherited by its descendants (which are things we can see like notes and directions).

Note that more than one of these could be allowed... not that that is necessarily a good thing.

1. Direct attributes

In this approach all style properties are represented as XML attribute-value pairs on an element being styled:

<sequence color="#0000FF" note-size="0.75"/>
  [...affected elements...]
</sequence> 

Notes:

  • Most compact representation, compatible with existing attributes like color on <note>.
  • This approach allows any style property of any possible descendant to be used as an XML attribute (in this case note-size is inherited by all <note> elements below <sequence>). So it may have the effect of requiring a huge style-attribute group that applies to almost every element in the schema.

2. Style element attributes

<sequence>
  <style color="#0000FF" note-size="0.75"/>
  [...affected elements...]
</sequence> 

Notes:

  • Not as compact.
  • Segregates all style definitions in a distinct element, which must be able to carry any style attributes.
  • Fewer XSD side-effects.

3. Style element children

<sequence>
    <style name="color" value="#0000FF"/>
    <style name="note-size" value="0.75"/>
    [...affected elements...]
</sequence>

Notes:

  • Extremely verbose
  • No effect at all on XSD schema
  • Style properties are not attributes, so they would need to be documented outside the schema somehow.

For compactness, only choice 1 (Direct attributes) will be used in remaining examples.

Declaring style classes

Target elements may list the classes they belong to using the class attribute, which takes a whitespace-delimited list of class identifiers:

<event value="1/8">
    <note pitch="C4"/>
    <note class="emphasis" pitch="E4"/>
    <note class="emphasis small" pitch="G4"/>
</event>

Target elements can declare more than one class. Style properties declared by a target element are applied in the order in which they were named, allowing subsequent class names to override earlier ones.

Defining style classes

Style sources may include one or more <style-class> children, each of which defines a set of properties for some class name; here a system layout defines what the classes emphasis and small mean for that particular layout:

<system-layout id="editorial">
    <style-class name="emphasis" color="#0000FF"/>
    <style-class name="small" note-size="0.75"/>
    [rest of layout definition...]
</system-layout>

Class definitions are always optional; if a style source doesn't supply one, then the class's properties are unaffected by that source.

Defining element classes

Target elements are considered to automatically belong to a special "element class" determined by the target element's name alone. This permits style sources to control the appearance of different elements by name.

Element-name-based classes can be defined in a style source as follows. In this example, a <score> element defines some default fonts for instruction text and expression text:

<score>
    <style-class element="instruction" font-family="Palatino" font-style="italic" font-size="12"/>
    <style-class element="expression" font-family="Palatino" font-weight="bold" font-size="14"/>
    [rest of score definition...]
</score>

A target's element class is always processed first, before iterating through the explicitly named classes. This means that element classes are defaults, and they are overridden by named classes.

Relationship to existing and future MNX concepts

  • We already have some attributes on semantic elements that are de facto styles like color and stem-direction. These would need to be retrofitted as style properties. Depending on the choice of syntax (asee above), this might not have any effect, or it might add new ways of specifying these values, or we might retire these as first-class XML attributes.
  • <global> seems like a natural place to define default style properties for an entire document.
  • Once we figure out the disposition of this issue, I would look to first apply it to layouts, scores, text, dynamics, transposed vs concert parts
  • Attributes that are envisioned as belonging to things like <staff-layout> and other layout elements proposed in How to group parts? #185 might simply be regular old style properties. There is no reason we need to limit what style properties a layout might or might not wish to control. Any style property that's fair game can be controlled by a layout.
  • In the future we might consider performance-related elements analogous to <layout> (say, <performance>) which use style properties to define specific mixes or subsets of parts to be used for audio rendering.
@cecilios
Copy link

I've read your proposal slowly and in detail and I only have one brainstorming comment, for your consideration. I'm not sure about the priority hierarchy. I have no experience in dealing with styles so my naive first idea would be to give maximum priority to any style in the target element itself:

• the target element itself
• an applicable element within a <score>, followed by its ancestors
• an applicable element within a <system-layout>, followed by its ancestors
• the ancestors (except <global>) of the target element itself
• within <global>, any <measure> to which the target element belongs
• the <global> element

As I deduce from your proposal, any style in the target element itself will be overridden by style in <score>, in <system-layout> or in their ancestors. In this situation, a consumer app. wising, for instance, to display in red a particular note, would have to do a lot of deductions and inferences to find the best place to insert the color="red" style attribute. A possibility would be to include in the note a class="in-red" and to insert in <score> or in the applicable <system-layout> the color attribute applicable to class "in-red". But, even with this approach, there could be conflicts, as the note could have assigned different classes and it is not clear to me how to enforce the style in a class over the styles from other applicable classes.

@joeberkovitz
Copy link
Contributor Author

joeberkovitz commented Nov 13, 2021

Here are some examples. They include many freely invented style properties, so please don't take those literally: these are just meant to illustrate the structure.

Explicit properties on a target

<mnx>
    <part>
        <measure>
            <sequence>
                <directions>
                    <expression color="#0000FF">...</expression>
                <event>
                    <note color="#0000FF" .../>
                    ...
  • A specific expression and note are styled

Properties inherited from an ancestor

<mnx>
    <part>...</part>
    <part>
        <measure color="#0000FF">
            <sequence>
                <directions>
                    <expression>...</expression>
                <event color="#FF0000">
                    <note .../>
                    ...
  • An entire measure (in one part only) is styled
  • An <event> overrides this style for itself and its descendant notes

Properties inherited from a global measure

<mnx>
    <global>
        <measure index="1" color="#0000FF">...</measure>
    </global>
    <part>...</part>
    <part>...</part>
    ...
  • Measure 1 (in all parts) is styled differently

Document-wide properties inherited from <global>

<mnx>
    <global music-font="Bravura" staff-line-spacing="9px" staff-spacing="10sl">...</global>
    <part>...</part>
    <part>...</part>
</mnx>

Notes:

  • a <global> element specifies properties that apply across the entire score by inheritance.
  • px is a pixel unit, sl is a staff line unit

Part-specific overrides

<mnx>
    <global staff-spacing="10sl">...</global>
    <part>...</part>
    <part staff-spacing="5sl">...</part>
    <part>...</part>
</mnx>
  • A specific part uses a different staff line spacing from the others.

Global properties specified by layouts

<mnx>
    <global music-font="Bravura">...</global>
    <part id="vln" name="Violin">
        ... <note class="small" .../> ...
    </part>
    <part id="vla" name="Viola">...</part>
    <score name="Full Score" layout="conductor">...</score>
    <score name="Violin Part" layout="part">...</score>
    <score name="Viola Part" layout="part">...</score>
    ...
    <system-layout id="conductor"  staff-line-spacing="6px">
        <style-class name="small" notehead-size="0.75"/>
        <staff-layout>
            <part-layout part="vln" stem-direction="up"/>
            <part-layout part="vla" stem-direction="down"/>
        </staff-layout>
        [...]
    </system-layout>
    <system-layout id="part" staff-line-spacing="9px">
        <style-class name="small" notehead-size="0.8"/>
        [...]
    </system-layout>
</mnx>
  • Document-wide defaults for all layouts and scores go in <global>
  • Some <note> elements belong to the named class small
  • Different layouts specify different styles for conductor and part
  • styles directly defined by layout elements affect the whole score
  • some layout styles are applied to a small class, affecting only elements belonging to small
  • stem direction in the full-score layout is realized via a stem-direction style property; any other style props or classes could have been defined by the <part-layout> elements

@joeberkovitz
Copy link
Contributor Author

@cecilios I agree that the priority hierarchy might be wrong. I think it's important to have one, but my initial stab at it might be flawed (and particularly on this point of explicit style properties). Let's continue looking at examples and fleshing out what behavior is most useful.

@cecilios
Copy link

I'd propose to reconsider the abbreviation sl for staff line units. The l (lower case L) always causes transcription problems by confusing it with lower case I and number 1. I would propose other alternatives, such as ss (staff-spaces), sp (spaces) or other.

Apart of that, your examples looks great to me. Nothing to comment.

@cecilios
Copy link

Trying to understand how styles will work, and the priority hierarchy for styles, I've done the following analogy to html/css

<global> & <part> - these MNX elements are somehow equivalent to the html <head> & <body>, with embedded and inline styles. They define the music content, and might include some embedded and inline styles.
<score> & <system-layout> - these MNX elements are somehow equivalent to the external CSS style sheet to apply (external styles). They define the layout to use and the styles for this layout.

In CSS/Html the priority of styles that apply to a specific element is as follows (highest priority first):

• Inline styles
• Embedded styles
• External styles

Thus, if we would like to mimic CSS/Html behavior, probably the best ordering for MNX would be (highest priority first):

• the target element itself, followed by its ancestors (except <global>)
• within <global>, any <measure> to which the target element belongs
• the <global> element
• an applicable element within a <system-layout>, followed by its ancestors
• an applicable element within a <score>, followed by its ancestors

@clnoel
Copy link

clnoel commented Nov 15, 2021

Hmm... I feel like the layout/score should be able to override properties explicitly defined on the target elements, especially things like stem direction and maybe color. What if you want a score display with every measure a different color of the rainbow, but the score has each element specifically encoded as "black"?

@cecilios
Copy link

cecilios commented Nov 15, 2021

@clnoel This is exactly the same situation as with CSS/HTML: HTML is the document content and should not contain style. And CSS is the layout/styling. HTML should not include any style, for good separation between content and layout/style. If HTML does not include any style, you have full control of how content is displayed by choosing another css style sheet. But if HTML contains style, it has priority and the style sheet cannot override it. It is not a problem of HTML/CSS design but a problem created by not putting layout/style were it should be: in the style sheet.

Here we have the same situation. We would like (I hope) to achieve good separation between music content and layout/style. Thus:

  • <score> & <system-layout> are somehow equivalent to CSS style sheet to apply. They define the layout to use and the styles for this layout.

  • <global> & <part> are the content, equivalent to the HTML content. It should not include any style, for good separation between layout/style and content. But if it contains styles, they have precedence.

We could do it the other way, <score> & <system-layout> to have priority, but the behavior will not be analogous to that of HTML/CSS (it could cause confusion) and in any case there will scenarios in which the contrary approach would be better.

In any case, to enforce good separation between content and layout/style I think it is better to give priority to style in the <global> & <part>: the less style you put there the more control you have in the <score> & <system-layout> part and more effective is the separation. But if we choose the other path, applications exporting MNX could overload <global> & <part> with style definitions and nothing wrong happens, as the <score> & <system-layout> part have priority ==> no incentives to remove style from <global> & <part> and do things right.

A second argument, is my first example (#263 (comment)): if an application would like to override the style for an specific element, by giving priority to style in the <global> & <part> it is always simple to do it. But if the <score> & <system-layout> part has the priority, then it could be difficult to override without a lot of inferences to find a suitable way of doing it.

@joeberkovitz
Copy link
Contributor Author

I am starting to come around to @cecilios's proposal that style properties in <global> and <part> need to always take precedence. It makes sense: if the content says a style is such-and-such, then it stays. This has some implications:

  • Any style of an element that is going to vary by score or by layout, needs to be left unspecified in <global>/<part>, or specified as a class rather than as an explicit property. Otherwise it cannot be overridden.
  • Therefore, score-wide defaults that are overridable by scores and layouts, must not be attached to <global>.
  • However, there is no single place to put these defaults, because there may be multiple <score> and <system-layout> elements. Where can document-wide defaults go? Either on <mnx> (not nice) or on some new <scores> or <layouts> element whose children are scores/layouts. I believe we may be in the land of Default <score> for an MNX document #252 here: the solution to that problem may also solve the question of where global default styles go.

But here's a potential fly in that soup: some properties like stem-direction or orient need to be overridden by things like <part-layout> even though they are part of the semantic content. Clearly we don't want to be doing ugly things like class="stem-up" on notes.

Fortunately the set of overridables here seems small and bounded. Perhaps we allow the layouts to apply a different set of properties that are understood to have a special override behavior (e.g. layout-stem-direction or layout-orient). Or perhaps these few things, as much as I want them to be styles, aren't.

@samuelbradshaw
Copy link

samuelbradshaw commented Nov 15, 2021

With HTML and CSS, global CSS styles do override HTML attributes. Only inline CSS (in the element's style="" attribute) can override a global CSS style (in the HTML <style> tag).

For example, here is a basic HTML document without any CSS styles. The <div> will be 150px wide:

<html>
  <head>
  </head>
  <body>
    <div width="150">Hello</div>
  </body>
</html>

In this example, the global CSS defined in the <style> tag takes precedence, so the <div> will be 200px wide:

<html>
  <head>
    <style>
      div { width: 200px; }
    </style>
  </head>
  <body>
    <div width="150">Hello</div>
  </body>
</html>

But here, inline CSS in the style="" attribute wins, making the <div> 100px wide:

<html>
  <head>
    <style>
      div { width: 200px; }
    </style>
  </head>
  <body>
    <div width="150" style="width: 100px;">Hello</div>
  </body>
</html>

Regular HTML attributes are overridden by global styles, but both HTML attributes and global styles are overridden by inline styles.

Could this paradigm be adapted to help solve the problem?

@joeberkovitz
Copy link
Contributor Author

@samuelbradshaw Essentially that's the exact behavior being proposed by @cecilios, which I agree with. style="" in CSS would be equivalent to the direct specification of style properties on an element in MNX, and would take the highest priority.

@notator
Copy link
Contributor

notator commented Nov 18, 2021

I'm also broadly in agreement with @cecilios proposal but, as @joeberkovitz said:

I believe we may be in the land of Default <score> for an MNX document #252 here: the solution to that problem may also solve the question of where global default styles go.

I think #252 needs to be settled before we can continue here, so am continuing there...

@joeberkovitz
Copy link
Contributor Author

Here is an update of the proposed styles architecture. It accounts the feedback on priorities, and has been harmonized with the CSS specification so that the things MNX does line up with a subset of CSS, conceptually speaking. (We are still not using any CSS syntax, no worries on that score.) I don't think this changes any of the examples given already.

Of necessity this is somewhat technical, even though it is a tiny subset of the CSS mechanism. If you don't want to get into the weeds, you can look at the examples elsewhere.

Target elements and style properties

"Target elements" are notational content to which styles may be applied. They are restricted to the semantic hierarchy at/under <global> and <part>. For example, <part>, <measure>, <note> and <expression> are all target elements; <system-layout> and <page> are not.

Style properties are meant to control a target's appearance and/or performance. Simple examples are color, volume, font, size.

Style properties are optional, and have default values supplied by the specification and perhaps by the rendering application. Style properties are declared in the document only when they override these defaults. As a corollary, the style mechanism can be completely ignored by documents that are not interested in modifying the defaults.

Style sources

Most elements in an MNX document can serve as a style source. This means that they include one or more declarations that apply style properties to a set of target elements. The following kinds of style sources exist:

  • A target element may include specific values of its own style properties, as in "this particular note is blue".
  • An element in a score or layout may define a named class giving style property values for all elements bearing a given class name, as in "all elements in the class small are displayed at 0.8 of normal size".
  • An element in a score or layout may define an element class giving style property values for all elements with a given name, as in of "all instruction elements are Times New Roman Bold".

(Note: unlike CSS, class definitions cannot match on both element name and class name. That's a possible future enhancement.)

Computing values

The first step in determining the value of a style property for some target element is to find all of its style sources, and collect the declared values in a list. The list is sorted into the following categories in the given priority order:

  • specific values provided in the target element itself
  • named class definitions in:
    • an applicable score element
    • an applicable layout element
  • element class definitions in:
    • an applicable score element
    • an applicable layout element

Each of these categories is then sorted internally so that later declarations in the document take priority over earlier ones. For scores and layouts, this has the effect that more specific elements override their ancestors. It also means that when multiple class names or specific values are provided in a target element, later ones override earlier ones.

The first value in the final sorted list is adopted as the computed value of the style property. If the list is empty, then there is no computed value.

Defaulting values

If no computed value is found for the target element in the previous step, and the property is inherited (most will be), the ancestors of the target element are searched by ascending the element chain. If <part> is reached during this ascent, for style computation purposes its ancestor is the <global-measure> containing the target element. The ascent stops at <global>. Thus, inheritance only applies within the semantic hierarchy.

For each ancestor consulted during this search, its computed value is examined. If one is found, the search terminates and that value is used as the target element's default value.

If no computed value is found on an ancestor and the search completes at <global>, then the property assumes an initial value determined by the host application, or by the property's initial value as given in the MNX specification.

@samuelbradshaw
Copy link

"Target elements" are notational content to which styles may be applied. They are restricted to the semantic hierarchy at/under <global> and <part>. For example, <part>, <measure>, <note> and <expression> are all target elements; <system-layout> and <page> are not.

Would it make sense for <page> to be a target element – setting the dimensions, margins, color, etc. of the page?

@joeberkovitz
Copy link
Contributor Author

@samuelbradshaw I think this change is possible, but my guess is it doesn’t give us much because a page does not need to belong to classes or inherit properties from ancestors (the only one is <score>) and this would complicate the model. At the moment things can either be target elements and have explicit properties only, or they can be part of scores/layouts and define classes only. It’s a bit like the difference between HTML content (explicit properties only) and CSS style sheets (style selection rules only).

Note: A page can already override default styles by defining classes, and so could affect the style properties of the content inside it as in your color example.

@cecilios
Copy link

As to the “Computing values” section, it looks ok to me but I have doubts about if it is just illustrative, to understand priorities, or it is also some kind of guidelines for implementing the algorithm to determine the value of a style property for some target element.

And this raises an off-topic issue: Will the MNX specification provide some algorithm for processing styles? If the group considers this to be something worth exploring it would be good to open an issue for it, in due time.

@joeberkovitz
Copy link
Contributor Author

joeberkovitz commented Nov 22, 2021

@cecilios The language in my post is illustrative, but it's as accurate as I can make it, and you should try it out on examples to see if it behaves correctly. Yes, I think there has to be an actual algorithm, which I was planning to spec out in a PR rather than in this issue. I think the pull request is where the really fine details will be worked out.

For the latest worked example please jump ahead to this post: #263 (comment)

Click on the text below to expand the originally-posted worked examples which have been superseded:

Original worked example Here is a fully worked example of the algorithm with a toy document:
<mnx>
    <global>
        <measure index="1" preferred-width="10st"/>
        <measure/>
    </global>
    <part name="Violin">
        <measure>
            <directions>
                <clef sign="G" line="2"/>
                <instruction text-font-size="2.5st">Allegro</instruction>
            </directions>
            <sequence>
                <event value="/4">
                   <note pitch="F4" class="emphasis"/>
                </event>
                ...
            </sequence>
        </measure>
        <measure>...</measure>
    </part>
    <part name="Viola">...</part>
    <layouts>
        <style-class element="global"
            music-font="Bravura"
            text-font="Times New Roman"/>
        <system-layout id="violin-part">
            <style-class element="global"
                staff-line-spacing="8px"
                system-spacing="5st"
                text-font="Georgia"
                text-font-size="14px"/>
            <style-class element="instruction"
                text-font-size="12px"/>
            <part-layout part="Violin"/>
        </system-layout>
        <system-layout id="full-score">
            <style-class element="global"
                staff-line-spacing="5px"
                system-spacing="3st"
                music-font="Petaluma"
                text-font-size="10px"/>
            <part-layout part="Violin"/>
            <part-layout part="Viola"/>
        </system-layout>
    </layouts>
    <score name="Full score">
        <style-class name="emphasis"
            color="#0000FF"/>
        <page>
            <system measure="1" layout="full-score"/>
        </page>
    </score>
    <score name="Violin Part">
        <style-class text-font="Arial"/>
        <page>
            <system measure="1" layout="violin-part"/>
        </page>
    </score>

Notes:

  • The <layouts> container is a new element collecting all system layouts. It serves as a place to define style properties that are common to all layouts.
  • Properties on some element in a layout or score only serve as sources for a target if that target "belongs to" that element (e.g. is on a specific page of a score, or in a specific part within a layout).

Example: text-font of the <global> element, violin part

All style sources in priority order, later elements ranked first:

  • score/style-class: "Arial"
  • layouts/system-layout/style-class: "Georgia"
  • layouts/style-class: "Times New Roman"

Value: "Arial" (the score wins)

Example: music-font of the <global> element, full score

All style sources in priority order, later elements ranked first:

  • layouts/system-layout/style-class: "Petaluma"
  • layouts/style-class: "Bravura"

Value: "Petaluma" (the specific layout wins)

Example: text-font of the <instruction> element, violin part

No style sources exist. Defaulting via the special inheritance rules under Defaulting values in the algorithm leads to the <global> ancestor which has a computed value of "Georgia".

Value: "Georgia"

Example: text-font-size of the <instruction> element, violin part

All style sources in priority order, later elements ranked first:

  • instruction: 2.5st
  • layouts/system-layout/style-class: 12px

Value: 2.5st (value directly on the <instruction> wins)

Example: color of the <note> element, full score

All style sources in priority order, later elements ranked first:

  • score/style-class: #0000FF (by way of the emphasis named class)

Value: #0000FF

@cecilios
Copy link

@joeberkovitz Thank you for the examples! They are always very useful. If my understanding is correct, I think there are a couple of issues in the examples:

Example: text-font of the element, full score
All style sources in priority order, later elements ranked first:

layouts/system-layout/style-class: "Georgia" <--------- This is not applicable, it is for violin part
layouts/style-class: "Times New Roman"

Value: "Times New Roman" (the specific layout wins)

Example: color of the element, violin part
Should be "full score". Violin part has no emphasis class

@cecilios
Copy link

@joeberkovitz A question: how do you infer that <instruction> inherits from <global>? The content tree is:

    <global>
        <measure>
    <part>
        <measure>
            <directions>
                <clef>
                <instruction>
            <sequence>
                <event>
                   <note>

Thus <instruction> inherits from <part>. I am missing anything?

@joeberkovitz
Copy link
Contributor Author

@cecilios First, thanks for catching the mistakes, I directly fixed them by editing the example.
Second, <instruction> inherits from <global> because of the special definition of inheritance under Defaulting values.

@cecilios
Copy link

@joeberkovitz Thanks! I forgot about the section Defaulting values

@joeberkovitz
Copy link
Contributor Author

As we've moved ahead on other fronts some changes are needed, I'm revising the basic styles proposal here to keep in step. Note: I'm going to keep this issue focused solely on the generic style mechanism and begin to break out other side issues to deal with specific groups of properties for page layout, dimensions, display, text and so forth. There's already a side issue for style property units (#247).

@joeberkovitz
Copy link
Contributor Author

Here is the updated spec on the generic calculation of style properties. This has been changed to allow the direct specification of styles on layout and score elements which keeps things simple-looking and allows us to use styling to control many aspects of layouts and scores with one scheme, including dimensions, margins etc.

Target elements and style properties

"Target elements" are any content to which style properties may be applied. They are not restricted to any particular section of an MNX document: semantic elements, layout elements and score elements may all possess style properties.

"Style properties" are meant to control a target's rendering and/or performance. Simple examples are color, volume, font, size.

Style properties are optional, and have default values supplied by the specification and perhaps by the rendering application. Style properties are declared in the document only when they override these defaults. As a corollary, the style mechanism can be completely ignored by documents that are not interested in modifying the defaults.

Style sources

Most elements in an MNX document can serve as a style source. This means that they include one or more declarations that apply style properties to a set of target elements. The following kinds of style sources exist:

  • A target element may include specific values of its own style properties, as in "this particular note is blue".
  • An element in a score or layout may define a named class giving style property values for all elements bearing a given class name, as in "all elements in the class small are displayed at 0.8 of normal size".
  • An element in a score or layout may define an element class giving style property values for all elements with a given name, as in of "all instruction elements are Times New Roman Bold".

(Note: unlike CSS, class definitions cannot match on both element name and class name. That's a possible future enhancement.)

Computing values

The first step in determining the value of a style property for some target element is to find all of its style sources, and collect the declared values in a list. The list is sorted into the following categories in the given priority order:

  • specific values provided in the target element itself
  • named class definitions in:
    • an applicable score element
    • an applicable layout element
  • element class definitions in:
    • an applicable score element
    • an applicable layout element

Each of these categories is then sorted internally so that later declarations in the document take priority over earlier ones. For scores and layouts, this has the effect that more specific elements override their ancestors. It also means that when multiple class names or specific values are provided in a target element, later ones override earlier ones.

The first value in the final sorted list is adopted as the computed value of the style property. If the list is empty, then there is no computed value.

Defaulting values

If no computed value is found for the target element in the previous step, and the property is inherited (other than dimensions and margins/padding, most are), the ancestors of the target element are searched by ascending the property inheritance chain looking for an element that does have a computed value. This chain is defined as follows:

  • If the target element is within <part>:
    • all ancestors of the target up to and including <part>
    • the <global-measure> element with the same measure index of the target and its ancestors up to <global>
    • the most specific layout element that applies to the target, and its ancestors up to <layouts>
    • the most specific score element that applies to the target, and its ancestors up to <score>
  • If the target element is within <global>:
    • all ancestors of the target up to <global>
    • the most specific layout element that applies to the target, and its ancestors up to <layouts>
    • the most specific score element that applies to the target, and its ancestors up to <score>
  • If the target element is a layout element:
    • all ancestors of the target up to <layouts>
    • the most specific element within a <score> that employs this layout, and its ancestors up to <score>
  • If the target element is a score element:
    • all ancestors of the target up to <score>

For each ancestor consulted during this search, its computed value is examined. If one is found, the search terminates and that value is used as the target element's default value.

If no computed value is found on an ancestor and the search completes at <global>, then the property assumes an initial value determined by the host application, or by the property's initial value as given in the MNX specification.

Worked Examples

Here is a fully worked example of the algorithm with a toy document:

<mnx>
    <global>
        <measure index="1" preferred-width="10st"/>
        <measure/>
    </global>
    <part name="Violin">
        <measure>
            <directions>
                <clef sign="G" line="2"/>
                <instruction text-font-size="2.5st">Allegro</instruction>
            </directions>
            <sequence>
                <event value="/4">
                   <note pitch="F4" class="emphasis"/>
                </event>
                ...
            </sequence>
        </measure>
        <measure>...</measure>
    </part>
    <part name="Viola">...</part>
    <layouts music-font="Bravura" text-font="Times New Roman"/>
        <system-layout id="violin-part"
          staff-line-spacing="8px" system-spacing="5st"
          text-font="Georgia" text-font-size="14px">
            <style-class element="instruction" text-font-size="12px"/>
            <part-layout part="Violin"/>
        </system-layout>
        <system-layout id="full-score"
          staff-line-spacing="5px" system-spacing="3st"
          text-font-size="10px">
            <part-layout part="Violin"/>
            <part-layout part="Viola"/>
        </system-layout>
    </layouts>
    <score name="Full score" page-size="A4" page-margin="25px 25px 25px 25px">
        <style-class name="emphasis"
            color="#0000FF"/>
        <page>
            <system measure="1" layout="full-score"/>
        </page>
    </score>
    <score name="Violin Part">
        <page page-size="A4" page-margin="25px 25px 25px 25px">
            <system measure="1" layout="violin-part"/>
        </page>
    </score>

Notes:

  • Properties on some element in a layout or score only serve as sources for a target if that target "belongs to" that element (e.g. is on a specific page of a score, or in a specific part within a layout).

Example: text-font of any semantic element, violin part

There are no style sources for text-font of any semantic element, so the inheritance chain is consulted to find a default value:

  • layouts/system-layout: "Georgia"
  • layouts: "Times New Roman"

Value: "Georgia" (the specific layout wins)

Example: music-font of any semantic element, full score

There is no computed value for music-font, so the inheritance chain includes:

  • layouts: "Bravura"

Value: "Bravura" (the specific layout wins)

Example: text-font-size of the <instruction> element, violin part

All style sources in priority order, later elements ranked first:

  • instruction: 2.5st
  • layouts/system-layout/style-class: 12px

Value: 2.5st (value directly on the <instruction> wins)

Example: text-font-size of the <instruction> element, full score

All style sources in priority order, later elements ranked first:

  • layouts/system-layout/style-class: 12px

Value: 12px (specified by the system layout)

Example: color of the <note> element, full score

All style sources in priority order, later elements ranked first:

  • score/style-class: #0000FF (by way of the emphasis named class)

Value: #0000FF

Example: size of page 1, full score

Although there are no dimensions on page elements, the value is inherited from <score>
Value: A4

@clnoel
Copy link

clnoel commented Feb 2, 2022

Assuming we can table direct changes to strings, as in #263 (comment) (see #278 and #280 for this), I think this is a good proposal. I would like to reword this to see if I understand it. In this explanation, the word "set" is used in the typographic sense of determining the exact placement, font, size, color, and other visual attributes of a visible element.


Possibly incorrect reworded explanation/algorithm:

All elements in the layouts hierarchy and the score hierarchy gain the <style-class> child, which can appear multiple times to define the styling of different elements and classes of elements. All stylable elements (in the global/part hierarchy) gain a subset of the styling attributes that are applicable to them, and a class attribute that serves as an id for the <style-class> element.

When trying to set each visible element in a score:

  1. Establish a hierarchy of element parents, skipping those not applicable to the target element (like event for an instruction)
  • target element
  • event
  • sequence
  • measure
  • part
  • measure-global
  • global
  • part-layout
  • voice-layout
  • staff-layout
  • group-layout
  • system-layout
  • system
  • page
  • score
  1. Establish a hierarchy of layout parents, skipping those not applicable (frequently voice-layout will be non-existent), and allowing group-layout to recurse if necessary.
  • system
  • page
  • score
  • part-layout
  • voice-layout
  • staff-layout
  • group-layout
  • system-layout

Note: For any given element being set on a particular score, these two hierarchies are well-defined.

  1. For each style variable included on the target-element, search for a value from the following list, stopping as soon as you find one:
    a. The value of the style variable in the target element
    b. If the target element has a named class, the first value of the style variable in a matching <style-class name="..."> element of a parent in the layout hierarchy
    c. The first value of the style variable in a <style-class element="..."> element of a parent in the layout hierarchy that matches the target element's type.
    d. For each parent in the element hierarchy,
    i. The value of the style variable in the parent element, if that variable is also defined for the parent.
    ii. If the parent element has a named class, the first value of the style variable in a matching <style-class name="..."> element of a parent in the layout hierarchy.
    iii. The first value of the style variable in a <style-class element="..."> element of a parent in the layout hierarchy that matches the parent element's type.

If all of that is correct, the I have the following notes:

  1. We should have two different <style-class> elements, <style-class> and <style-element>. This will help distinguish their purpose.

  2. In the layout hierarchy and element hierarchy, <score> and it's children are in different locations relative to ` and it's children. Is that intentional?

  3. In the examples above, there's no place where the following case is demonstrated.

Example: text-font-size of the <instruction> element, full score

All style sources in priority order, later elements ranked first:

  • layouts/system-layout/style-class: 12px

Value: 12px (specified by the system layout)

If I understand correctly, since the only <instruction> shown in the example has a text-font-size, that will get used for all layouts and all scores. If that is intended to be the case, we should have another instruction that doesn't specify text-font-size so we can demonstrate the inheritance hierarchy. If that is not intended to be the case, I would appreciate an explicit description of which hierarchy levels are allowed to override the style on the element itself.

@adrianholovaty
Copy link
Contributor

Thanks for putting this together, @joeberkovitz. It makes intuitive sense (especially for folks coming from an HTML/CSS background) and is powerful yet elegant. I think we should run with it!

Just a few reactions regarding details:

  • Given the hierarchy of styles, there should be a way to "reset" or "clear" the computed styles for a given element. For example, if a <measure> has defined a color (hence apply to all of the notes within), it ought to be possible for an individual note to ignore the higher-level color. CSS handles this with initial.

  • Regarding @clnoel's suggestion of separating <style-class> and <style-element>, I'd personally prefer to keep it as a single element and make it less verbose: <style element=""> and <style class="">. The former would target elements and the latter would target classes.

  • For the purposes of making a pull request, we should choose at least one style property to include in examples. My vote would be for "color", as it's reasonably simple.

How is everybody feeling about this proposal? In absence of any pushback, I can make a pull request with the appropriate changes to the spec, along with an example or two.

@adrianholovaty
Copy link
Contributor

I've added pull request #282 to encode these ideas into the spec. Feedback welcome!

@adrianholovaty
Copy link
Contributor

Quick update: there's been a non-trivial proposed tweak to this. See my comment here and please leave feedback there on the pull request!

@adrianholovaty
Copy link
Contributor

Closed via pull request #282 and commit f89fc27.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants