# CSS-Properties-p6
The CSS::Properties module is a set of related classes for parsing, manipulation and generation of CSS property sets, including inheritance, and defaults.
Classess in this module
--------
* `CSS::Properties` - property list manipulation class.
* `CSS::Properties::Font` - property font manipulation
* `CSS::Units` - units and postfix operators (e.g. `12pt`)
* `CSS::Box` - CSS Box model implementation.
* `CSS::PageBox` - CSS Box model for paged media
See Also
--------
* [CSS](https://github.com/p6-css/CSS-raku) - Top level CSS manipulation class
## Basic Construction
```
use v6;
use CSS::Units :pt;
use CSS::Properties;
my $style = "color:red !important; padding: 1pt";
my CSS::Properties $css .= new( :$style );
say $css.important("color"); # True
$css.border-color = 'red';
$css.margin = [5pt, 2pt, 5pt, 2pt];
$css.margin = 5pt; # set margin on all 4 sides
# set text alignment
$css.text-align = 'right';
say ~$css; # border-color:red; color:red!important; margin:5pt; padding:1pt; text-align:right;
```
## Methods
### new
method new(
Str :$style,
CSS::Properties() :$inherit,
CSS::Properties() :$copy,
Str :$units = 'pt',
Numeric :$em = $inherit.em // 12,
Numeric :$viewport-width,
Numeric :$viewport-height,
Numeric :$reference-width,
*%props,
) returns CSS::Properties
Options:
- `Str :$style` CSS property list to parse
- `CSS::Properties() :$inherit` Properties to be formally inherited
- `CSS::Properties() :$copy` Additional properties to be copied in
- `Str :$units` # measurement units, such as 'pt', 'px', 'in', etc
- `Numeric :$em = 12` initial font size
- `Numeric :$viewport-width` for use as `vw` length units
- `Numeric :$viewport-height` for use as `vh` length units
- `Numeric :$reference-width` for use in box values
- `*%props` - CSS property settings
### measure
# Converts a value to a numeric quantity;
my Numeric $font-size = $css.measure: :font-size; # get current font size
$font-size = $css.measure: :font-size; # compute a smaller font
$font-size = $css.measure: :font-size(120%); # compute a larger font
my $weight = $css.measure: :font-weight; # get current font weight 100..900
$weight = $css.measure: :font-weight; # compute bold font weight
This function is implemented for `font-size`, `font-weight`, `letter-spacing`, `line-height`, and `word-spacing`.
It also works for box related properties: `width`, `height`, `{min|max}-{width|height}`, `border-{top|right|bottom|left}-width`, and `{padding|margin}-{top|right|bottom|left}`.
The `reference-width` attribute represents the width of a containing element; which needs to set for correct calculation of percentage box related quantities:
$css.reference-width = 80pt;
say $css.measure: :width(75%); # 60
## CSS Property Accessors
- color values are converted to Color objects
- other values are converted to strings or numeric, as appropriate
- the .type method returns additional type information
- box properties are arrays that contain four sides. For example, 'margin' contains 'margin-top', 'margin-right', 'margin-bottom' and 'margin-left';
- there are also some container properties that may be accessed directly or via a hash; for example, The 'font' accessor returns a hash containing 'font-size', 'font-family', and other font properties.
```
use CSS::Properties;
my CSS::Properties $css .= new: :style("color: orange; text-align: CENTER; margin: 2pt; font: 12pt Helvetica");
say $css.color.hex; # (FF A5 00)
say $css.color.type; # 'rgb'
say $css.text-align; # 'center'
say $css.text-align.type; # 'keyw' (keyword)
# access margin-top, directly and through margin container
say $css.margin-top; # '2'
say $css.margin-top.type; # 'pt'
say $css.margin; # [2 2 2 2]
say $css.margin[0]; # '2'
say $css.margin[0].type; # 'pt'
# access font-family directly and through font container
say $css.font-family; # 'Helvetica'
say $css.font-family.type; # 'ident'
say $css.font; # 'Helvetica;
```
- The simplest ways of setting a property is to assign a string value which is parsed as CSS.
- Unit values are also recognized. E.g. `16pt`.
- Colors can be assigned to color objects
- Also the type and value can be assigned as a pair.
```
use CSS::Properties;
use CSS::Units :pt;
use Color;
my CSS::Properties $css .= new;
# assign to container
$css.font = "14pt Helvetica";
# assign to component properties
$css.font-weight = 'bold'; # string
$css.line-height = 16pt; # unit value
$css.border-color = Color.new(0, 255, 0);
$css.font-style = :keyw; # type/value pair
say ~$css; # font:italic bold 14pt/16pt Helvetica;
```
## CSS Modules
## Conformance Levels
Processing defaults to CSS level 3 (class CSS::Module::CSS3). This can be configured via the :module option:
```
use CSS::Properties;
use CSS::Module::CSS1;
use CSS::Module::CSS21;
my $style = 'color: red; azimuth: left';
my $module = CSS::Module::CSS1.module;
my CSS::Properties $css1 .= new( :$style, :$module);
## warnings: dropping unknown property: azimuth
$module = CSS::Module::CSS21.module;
my CSS::Properties $css21 .= new( :$style, :$module);
## (no warnings)
```
### '@font-face' Properties
`@font-face` is a sub-module of `CSS3`. To process a set of `@font-face` declarations, such as:
```
@font-face {
font-family: myFirstFont;
src: url(sansation_light.woff);
}
```
```
use CSS::Properties;
use CSS::Module::CSS3;
my $style = "font-family: myFirstFont; src: url(sansation_light.woff)";
my $module = CSS::Module::CSS3.module.sub-module<@font-face>;
my CSS::Properties $font-face-css .= new( :$style, :$module);
```
## Default values
Most properties have a default value. If a property is reset to its default value it will be omitted from stringification:
my $css = (require CSS::Properties).new;
say $css.background-image; # none
$css.background-image = 'url(camellia.png)';
say ~$css; # "background-image: url(camellia.png);"
$css.background-image = $css.info("background-image").default;
say ~$css; # ""
## Deleting properties
Properties can be deleted via the `delete` method, or by assigning the property to `Nil`:
my CSS::Properties $css .= new: :style("background-position:top left; border-top-color:red; border-bottom-color: green; color: blue");
# delete background position
$css.background-position = Nil;
# delete all border colors
$css.delete: "border-color";
## Inheritance
A child class can inherit from one or more parent classes. This follows CSS standards:
- not all properties are inherited by default; for example `color` is, but `margin` is not.
- the `inherit` keyword can be used in the child property to ensure inheritance.
- `initial` will reset the child property to the default value
To inherit a css object or style string:
- pass it as a `:inherit` option, when constructing the object, or
- use the `inherit` method
```
use CSS::Properties;
my $parent-style = "margin-top:5pt; margin-left: 15pt; color:rgb(0,0,255) !important";
my $style = "margin-top:25pt; margin-right: initial; margin-left: inherit; color:purple";
my CSS::Properties $css .= new: :$style, :inherit($parent-style);
say $css.color; # #7F007Frgb (purple)
say $css.handling("margin-left"); # inherit
say $css.margin-left; # 15pt
```
## Optimization and Serialization
The `.write` or `.Str` methods can be used to produce CSS. Properties are optimized and normalized:
- properties with default values are omitted
- multiple component properties are generally consolidated to container properties (e.g. `font-family: Courier` and `font-size: 12pt`
are consolidated to `font: 12pt Courier`).
- rgb masks are translated to color-names, where possible
```
use CSS::Properties;
my CSS::Properties $css .= new( :style("background-repeat:repeat; border-style: groove; border-width: 2pt 2pt; color: rgb(255,0,0);") );
# - 'border-width' and 'border-style' are consolidated to the 'border' container property
# - rgb(255,0,0) is mapped to 'red'
say $css.write; # "border:2pt groove; color: red;"
```
Notice that:
- `background-repeat` was omitted because it has the default value
- `border-style` and `border-width` have been consolidated to the `border` container property. This is possible
because all four borders have common values
- `color` has been translated from a color mask to a color
`$.write` Options include:
- `:!optimize` - turn off optimization. Don't, combine component properties into container properties (`border-style`, `border-width`, ... => `border`), or combine edges (`margin-top`, `margin-left`, ... => `margin`).
- `:!terse` - enable multi-line output
- `:!color-names` - don't translate RGB values to color-names
ASTs can also be directly optimized:
```
use CSS::Properties;
use CSS::Module::CSS3;
use CSS::Writer;
my CSS::Properties $css .= new;
my $module = CSS::Module::CSS3.module;
my $actions = $module.actions.new;
my CSS::Writer $writer .= new: :color-names, :terse;
my $declarations = "border-bottom-color:red; border-bottom-style:solid; border-bottom-width:1px; border-left-color:red; border-left-style:solid; border-left-width:1px; border-right-color:red; border-right-style:solid; border-right-width:1px; border-top-color:red; border-top-style:solid; border-top-width:1px;";
my $p = $module.grammar.parse($declarations, :$actions, :rule);
my %ast = $css.optimize($p.ast);
say $writer.write(|%ast); # border:1px solid red;
```
## Property Meta-data
The `info` method gives property specific meta-data, on all (component or container properties). It returns an object of type CSS::Properties::Property:
```
use CSS::Properties;
my CSS::Properties $css .= new;
my $margin-info = $css.info("margin");
say $margin-info.synopsis; # {1,4}
say $margin-info.edges; # [margin-top margin-right margin-bottom margin-left]
say $margin-info.inherit; # True (property is inherited)
```
## Data Introspection
The `properties` method, gives a list of current properties. Only component properties
are returned. E.g. `font-family` may be returned; but `font` never is.
```
use CSS::Properties;
my $style = "margin-top: 10%; margin-right: 5mm; margin-bottom: auto";
my CSS::Properties $css .= new: :$style;
for $css.properties -> $prop {
my $val = $css."$prop"();
say "$prop: $val {$val.type}";
}
```
Gives:
```
margin-top: 10 percent
margin-bottom: auto keyw
margin-right: 5 mm
```
## Length Units
CSS::Units is a convenience module that provides some simple post-fix length unit definitions.
The `:ops` export overloads `+` and `-` to perform unit
calculations. `+css` and `-css` are also available as
more explicit infix operators:
All infix operators convert to the left-hand operand's units.
```
use CSS::Units :ops, :pt, :px, :in, :mm;
my $css = (require CSS::Properties).new: :margin[5pt, 10px, .1in, 2mm];
# display margins in millimeters
say "%.2f mm".sprintf(.scale("mm")) for $css.margin.list;
```
## Box Model
### Overview
Excerpt from [CSS 2.2 Specification Chapter 8 - Box Model](https://www.w3.org/TR/CSS22/box.html#box-dimensions):

The margin, border, and padding can be broken down into top, right, bottom, and left segments (e.g., in the diagram, "LM" for left margin, "RP" for right padding, "TB" for top border, etc.).
The perimeter of each of the four areas (content, padding, border, and margin) is called an "edge", so each box has four edges:
- *Content Edge* or *Inner Edge* -
The content edge surrounds the rectangle given by the width and height of the box, which often depend on the element's rendered content. The four content edges define the box's content box.
- *Padding Edge* -
The padding edge surrounds the box padding. If the padding has 0 width, the padding edge is the same as the content edge. The four padding edges define the box's padding box.
- *Border Edge* -
The border edge surrounds the box's border. If the border has 0 width, the border edge is the same as the padding edge. The four border edges define the box's border box.
- *Margin Edge or Outer Edge* -
The margin edge surrounds the box margin. If the margin has 0 width, the margin edge is the same as the border edge. The four margin edges define the box's margin box.
### `CSS::Box`
`CSS::Box` is an abstract class for modelling Box Model elements.
use CSS::Box;
use CSS::Units :px, :pt, :em, :percent;
use CSS::Properties;
my $style = q:to"END";
width: 300px;
border: 25px solid green;
padding: 25px;
margin: 25px;
font: italic bold 10pt/12pt times-roman;
END
my CSS::Properties $css .= new: :$style;
my $top = 80pt;
my $right = 50pt;
my $bottom = 10pt;
my $left = 10pt;
my CSS::Box $box .= new( :$top, :$left, :$bottom, :$right, :$css );
say $box.padding; # dimensions of padding box;
say $box.margin; # dimensions of margin box;
say $box.border-right; # vertical position of right border
say $box.border-width; # border-right - border-left
say $box.width("border"); # border-width
say $box.height("content"); # height of content box
say $box.font.family; # 'times-roman'
# calculate some relative font lengths
say $box.font-length(1.5em); # 15
say $box.font-length(200%); # 20
say $box.font-length('larger'); # 12
### Box Methods
#### new
my CSS::Box $box .= new( :$top, :$left, :$bottom, :$right, :$css, :$parent );
The box `new` constructor accepts:
- any two of `:top`, `:bottom` or `:height`,
- and any two of `:left`, `:right` or `:width`.
A `:$parent` CSS::Box object is required for correct calculations when any CSS `padding` or
`margin`values are expressed as percentage values.
#### font
say "font-size is {$box.font.em}";
The '.font' accessor returns an object of type `CSS::Properties::Font`, with accessor methods: `em`, `ex`, `weight`, `family`, `style`, `leading`, `find-font`, `fontconfig-pattern` and `measure` methods.
#### measure
This method converts various length units to normalized base units (default 'pt').
use CSS::Units :mm, :in, :pt, :px;
use CSS::Box;
use CSS::Properties;
my CSS::Box $box .= new;
# default base units is points
say [(1mm, 1in, 1pt, 1px).map: {$box.measure($_)}];
# produces: [2.8346pt 72pt 1pt 0.75pt]
# change base units to inches
$box .= new: :units;
say [(1in, 72pt).map: {$box.measure($_)}];
# produces: [1in, 1in]
#### top, right, bottom, left
These methods return measured positions of each of the four corners of the inner content box. They
are rw accessors, e.g.:
$box.top += 5;
Outer boxes will grow and shrink, retaining their original width and height.
#### padding, margin, border
This returns all four corners (measured) of the given box, e.g.:
my Numeric ($top, $right, $bottom, $left) = $box.padding
#### content
This returns all four corners (measured) of the content box, e.g.:
my Numeric ($top, $right, $bottom, $left) = $box.content;
These values are rw. The box can be both moved and resized, by adjusting this array.
$box.content = (10, 50, 35, 10); # 40x25 box, top-left @ 10,10
Outer boxes, will grow or shrink to retain their original widths.
#### [padding|margin|border|content]-[width|height]
say "margin box is size {$box.margin-width} X {$box.margin-height}";
This family of accessors return the measured width, or height of the given box.
#### [padding|margin|border|content]-[top|right|bottom|left]
say "margin left, top is ({$box.margin-left}, {$box.margin-top})";
This family of accessors return the measured x or y position of the given edge
#### translate, move
These methods can be used to adjust the position of the content box.
$box.translate(10, 20); # translate box 10, 20 in X, Y directions
$box.move(40, 50); # move top left corner to (X, Y) = (40, 50)
## Appendix : CSS3 Properties
Name | Default | Inherit | Type | Synopsis
--- | --- | --- | --- | ---
azimuth | center | Yes | | \ \| [[ left-side \| far-left \| left \| center-left \| center \| center-right \| right \| far-right \| right-side ] \|\| behind ] \| leftwards \| rightwards
background | | | hash | ['background-color' \|\| 'background-image' \|\| 'background-repeat' \|\| 'background-attachment' \|\| 'background-position']
background-attachment | scroll | | | scroll \| fixed
background-color | transparent | | | \ \| transparent
background-image | none | | | \ \| none
background-position | 0% 0% | | | [ [ \ \| \ \| left \| center \| right ] [ \ \| \ \| top \| center \| bottom ]? ] \| [ [ left \| center \| right ] \|\| [ top \| center \| bottom ] ]
background-repeat | repeat | | | repeat \| repeat-x \| repeat-y \| no-repeat
border | | | hash,box | [ 'border-width' \|\| 'border-style' \|\| 'border-color' ]
border-bottom | | | hash | [ 'border-bottom-width' \|\| 'border-bottom-style' \|\| 'border-bottom-color' ]
border-bottom-color | the value of the 'color' property | | | \ \| transparent
border-bottom-style | none | | | \
border-bottom-width | medium | | | \
border-collapse | separate | Yes | | collapse \| separate
border-color | | | box | [ \ \| transparent ]{1,4}
border-left | | | hash | [ 'border-left-width' \|\| 'border-left-style' \|\| 'border-left-color' ]
border-left-color | the value of the 'color' property | | | \ \| transparent
border-left-style | none | | | \
border-left-width | medium | | | \
border-right | | | hash | [ 'border-right-width' \|\| 'border-right-style' \|\| 'border-right-color' ]
border-right-color | the value of the 'color' property | | | \ \| transparent
border-right-style | none | | | \
border-right-width | medium | | | \
border-spacing | 0 | Yes | | \ \?
border-style | | | box | \{1,4}
border-top | | | hash | [ 'border-top-width' \|\| 'border-top-style' \|\| 'border-top-color' ]
border-top-color | the value of the 'color' property | | | \ \| transparent
border-top-style | none | | | \
border-top-width | medium | | | \
border-width | | | box | \{1,4}
bottom | auto | | | \ \| \ \| auto
caption-side | top | Yes | | top \| bottom
clear | none | | | none \| left \| right \| both
clip | auto | | | \ \| auto
color | depends on user agent | Yes | | \
content | normal | | | normal \| none \| [ \ \| \ \| \ \| \ \| attr(\) \| open-quote \| close-quote \| no-open-quote \| no-close-quote ]+
counter-increment | none | | | none \| [ \ \? ]+
counter-reset | none | | | none \| [ \ \? ]+
cue | | | hash | [ 'cue-before' \|\| 'cue-after' ]
cue-after | none | | | \ \| none
cue-before | none | | | \ \| none
cursor | auto | Yes | | [ [\ ,]* [ auto \| crosshair \| default \| pointer \| move \| e-resize \| ne-resize \| nw-resize \| n-resize \| se-resize \| sw-resize \| s-resize \| w-resize \| text \| wait \| help \| progress ] ]
direction | ltr | Yes | | ltr \| rtl
display | inline | | | inline \| block \| list-item \| inline-block \| table \| inline-table \| table-row-group \| table-header-group \| table-footer-group \| table-row \| table-column-group \| table-column \| table-cell \| table-caption \| none
elevation | level | Yes | | \ \| below \| level \| above \| higher \| lower
empty-cells | show | Yes | | show \| hide
float | none | | | left \| right \| none
font | | Yes | hash | [ [ \<‘font-style’\> \|\| \ \|\| \<‘font-weight’\> \|\| \<‘font-stretch’\> ]? \<‘font-size’\> [ / \<‘line-height’\> ]? \<‘font-family’\> ] \| caption \| icon \| menu \| message-box \| small-caption \| status-bar
font-family | depends on user agent | Yes | | [ \ \| \ ]\#
font-feature-settings | normal | Yes | | normal \| \\#
font-kerning | auto | Yes | | auto \| normal \| none
font-language-override | normal | Yes | | normal \| \
font-size | medium | Yes | | \ \| \ \| \ \| \
font-size-adjust | none | Yes | | none \| auto \| \
font-stretch | normal | Yes | | normal \| ultra-condensed \| extra-condensed \| condensed \| semi-condensed \| semi-expanded \| expanded \| extra-expanded \| ultra-expanded
font-style | normal | Yes | | normal \| italic \| oblique
font-synthesis | weight style | Yes | | none \| [ weight \|\| style ]
font-variant | normal | Yes | | normal \| none \| [ \ \|\| \ \|\| \ \|\| \ \|\| stylistic(\) \|\| historical-forms \|\| styleset(\ \#) \|\| character-variant(\ \#) \|\| swash(\) \|\| ornaments(\) \|\| annotation(\) \|\| [ small-caps \| all-small-caps \| petite-caps \| all-petite-caps \| unicase \| titling-caps ] \|\| \ \|\| \ \|\| \ \|\| ordinal \|\| slashed-zero \|\| \ \|\| \ \|\| ruby ]
font-variant-alternates | normal | Yes | | normal \| [ stylistic(\) \|\| historical-forms \|\| styleset(\\#) \|\| character-variant(\\#) \|\| swash(\) \|\| ornaments(\) \|\| annotation(\) ]
font-variant-caps | normal | Yes | | normal \| small-caps \| all-small-caps \| petite-caps \| all-petite-caps \| unicase \| titling-caps
font-variant-east-asian | normal | Yes | | normal \| [ \ \|\| \ \|\| ruby ]
font-variant-ligatures | normal | Yes | | normal \| none \| [ \ \|\| \ \|\| \ \|\| \ ]
font-variant-numeric | normal | Yes | | normal \| [ \ \|\| \ \|\| \ \|\| ordinal \|\| slashed-zero ]
font-variant-position | normal | Yes | | normal \| sub \| super
font-weight | normal | Yes | | normal \| bold \| bolder \| lighter \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900
height | auto | | | \ \| \ \| auto
left | auto | | | \ \| \ \| auto
letter-spacing | normal | Yes | | normal \| \
line-height | normal | Yes | | normal \| \ \| \ \| \
list-style | | Yes | hash | [ 'list-style-type' \|\| 'list-style-position' \|\| 'list-style-image' ]
list-style-image | none | Yes | | \ \| none
list-style-position | outside | Yes | | inside \| outside
list-style-type | disc | Yes | | disc \| circle \| square \| decimal \| decimal-leading-zero \| lower-roman \| upper-roman \| lower-greek \| lower-latin \| upper-latin \| armenian \| georgian \| lower-alpha \| upper-alpha \| none
margin | | | box | \{1,4}
margin-bottom | 0 | | | \
margin-left | 0 | | | \
margin-right | 0 | | | \
margin-top | 0 | | | \
max-height | none | | | \ \| \ \| none
max-width | none | | | \ \| \ \| none
min-height | 0 | | | \ \| \
min-width | 0 | | | \ \| \
opacity | 1.0 | | | \
orphans | 2 | Yes | | \
outline | | | hash | [ 'outline-color' \|\| 'outline-style' \|\| 'outline-width' ]
outline-color | invert | | | \ \| invert
outline-style | none | | | [ none \| hidden \| dotted \| dashed \| solid \| double \| groove \| ridge \| inset \| outset ]
outline-width | medium | | | thin \| medium \| thick \| \
overflow | visible | | | visible \| hidden \| scroll \| auto
padding | | | box | \{1,4}
padding-bottom | 0 | | | \
padding-left | 0 | | | \
padding-right | 0 | | | \
padding-top | 0 | | | \
page-break-after | auto | | | auto \| always \| avoid \| left \| right
page-break-before | auto | | | auto \| always \| avoid \| left \| right
page-break-inside | auto | | | avoid \| auto
pause | | | | [ [\