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 font shorthand property #693

Merged
merged 30 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
757c67d
Add type to turn strings into keywords
Jym77 Feb 5, 2021
03039cf
Support font-stretch longhand
Jym77 Feb 5, 2021
59098fa
Implement font shorthand
Jym77 Feb 8, 2021
9663be6
Actually define the font shorthand
Jym77 Feb 8, 2021
ee83945
Pass input to Parser.tee callback
Jym77 Feb 8, 2021
2b937b7
Allow error callback in Parser.tee
Jym77 Feb 8, 2021
f48cb1e
Rename Parser.mapResult to Parser.andThen to align with Result.andThen
Jym77 Feb 8, 2021
38227f7
Use Result.andThen in Parser.andThen
Jym77 Feb 8, 2021
404ed0f
Remove unused import
Jym77 Feb 8, 2021
d5718c1
Fix whitespacing in parser
Jym77 Feb 8, 2021
968cad3
Restrict font-stretch to <font-stretch-css3> in font shorthand
Jym77 Feb 8, 2021
a500c35
Make whitespace around font-size/line-height separator optional
Jym77 Feb 8, 2021
d65190f
Correct percentages?
Jym77 Feb 8, 2021
fc7c167
Correct Percentage.toString
Jym77 Feb 8, 2021
0fa82d2
Allow angles in font-style
Jym77 Feb 8, 2021
eae986c
Allow whitespace in font-size: oblique 25deg
Jym77 Feb 8, 2021
e15d859
Add test for font shorthand
Jym77 Feb 8, 2021
c362f94
Clean up
Jym77 Feb 8, 2021
8e4b723
Revert Parser.andThen to Parser.mapResult
Jym77 Feb 8, 2021
3aeafc0
Clean up
Jym77 Feb 8, 2021
67fbf06
Merge branch 'master' into support-font-shorthand
Jym77 Feb 8, 2021
05c1d18
Drop support for font-size: oblique <angle>
Jym77 Feb 8, 2021
4a08e6f
Unwind breaking changes
kasperisager Feb 8, 2021
3a5d380
Merge branch 'master' into support-font-shorthand
kasperisager Feb 8, 2021
c958eb0
Remove comment
Jym77 Feb 9, 2021
bcd53fd
Remove partial support of font-variant
Jym77 Feb 9, 2021
d6aba06
Merge branch 'master' into support-font-shorthand
Jym77 Feb 9, 2021
b3f3fb6
Remove Keyword.ToKeyword type
Jym77 Feb 9, 2021
84a44c3
Remove unnecessarily readonly precision
Jym77 Feb 9, 2021
75a1691
Merge branch 'master' into support-font-shorthand
Jym77 Feb 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/alfa-css/src/value/percentage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class Percentage extends Numeric<"percentage"> {
}

public toString(): string {
return `${this._value}%`;
return `${this._value * 100}%`;
Jym77 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/alfa-style/src/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ const Longhands = {
display: Display,
"font-family": Font.Family,
"font-size": Font.Size,
"font-stretch": Font.Stretch,
"font-style": Font.Style,
"font-weight": Font.Weight,
height: Height,
Expand Down Expand Up @@ -258,6 +259,7 @@ const Shorthands = {
background: Background.Shorthand,
"background-repeat": Background.Repeat.Shorthand,
"background-position": Background.Position.Shorthand,
font: Font.Shorthand,
"inset-block": Inset.Block.Shorthand,
"inset-line": Inset.Line.Shorthand,
outline: Outline.Shorthand,
Expand Down
40 changes: 17 additions & 23 deletions packages/alfa-style/src/property/box-insets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Parser } from "@siteimprove/alfa-parser";
import { Property } from "../property";
import { Resolver } from "../resolver";

const { either, map, oneOrMore, separatedList } = Parser;
const { either, map, separatedList } = Parser;

/**
* @see https://drafts.csswg.org/css-position/#insets
Expand Down Expand Up @@ -67,17 +67,14 @@ export namespace Inset {
*/
export const Shorthand = Property.Shorthand.of(
["inset-block-start", "inset-block-end"],
map(
separatedList(parseInset, Token.parseWhitespace),
(values) => {
const [start, end] = [...values];

return [
["inset-block-start", start],
["inset-block-end", end ?? start],
];
}
)
map(separatedList(parseInset, Token.parseWhitespace), (values) => {
const [start, end] = [...values];

return [
["inset-block-start", start],
["inset-block-end", end ?? start],
];
})
);
}

Expand All @@ -91,17 +88,14 @@ export namespace Inset {
*/
export const Shorthand = Property.Shorthand.of(
["inset-line-start", "inset-line-end"],
map(
separatedList(parseInset, Token.parseWhitespace),
(values) => {
const [start, end] = [...values];

return [
["inset-line-start", start],
["inset-line-end", end ?? start],
];
}
)
map(separatedList(parseInset, Token.parseWhitespace), (values) => {
const [start, end] = [...values];

return [
["inset-line-start", start],
["inset-line-end", end ?? start],
];
})
);
}
}
262 changes: 243 additions & 19 deletions packages/alfa-style/src/property/font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,38 @@ import {
Calculation,
String,
Number,
Angle,
} from "@siteimprove/alfa-css";
import { Parser } from "@siteimprove/alfa-parser";
import { Result } from "@siteimprove/alfa-result";
import { Slice } from "@siteimprove/alfa-slice";

import { Property } from "../property";
import { Resolver } from "../resolver";

const { map, filter, either, option, delimited, separatedList } = Parser;
import { Line } from "./line";

const {
delimited,
either,
filter,
map,
option,
pair,
right,
separatedList,
} = Parser;

export namespace Font {
export type Family = Family.Specified;

export namespace Family {
export type Generic = Keyword<
"serif" | "sans-serif" | "cursive" | "fantasy" | "monospace"
>;
export type Generic =
| Keyword<"serif">
| Keyword<"sans-serif">
| Keyword<"cursive">
| Keyword<"fantasy">
| Keyword<"monospace">;

export type Specified = Array<Generic | String>;
}
Expand Down Expand Up @@ -56,18 +72,17 @@ export namespace Font {
export type Size = Size.Specified | Size.Computed;

export namespace Size {
export type Absolute = Keyword<
| "xx-small"
| "x-small"
| "small"
| "medium"
| "large"
| "x-large"
| "xx-large"
| "xxx-large"
>;

export type Relative = Keyword<"larger" | "smaller">;
export type Absolute =
| Keyword<"xx-small">
| Keyword<"x-small">
| Keyword<"small">
| Keyword<"medium">
| Keyword<"large">
| Keyword<"x-large">
| Keyword<"xx-large">
| Keyword<"xxx-large">;

export type Relative = Keyword<"larger"> | Keyword<"smaller">;

export type Specified =
| Absolute
Expand Down Expand Up @@ -198,10 +213,84 @@ export namespace Font {
}
);

export type Style = Keyword<"normal" | "italic" | "oblique">;
export namespace Stretch {
export type Absolute =
| Keyword<"ultra-condensed">
| Keyword<"extra-condensed">
| Keyword<"condensed">
| Keyword<"semi-condensed">
| Keyword<"normal">
| Keyword<"semi-expanded">
| Keyword<"expanded">
| Keyword<"extra-expanded">
| Keyword<"ultra-expanded">;

export type Specified = Absolute | Percentage;

export type Computed = Percentage;
}

/**
* @see https://drafts.csswg.org/css-fonts/#font-stretch-prop
*/
export const Stretch: Property<
Stretch.Specified,
Stretch.Computed
> = Property.of(
Percentage.of(1),
either(
Percentage.parse,
Keyword.parse(
"ultra-condensed",
"extra-condensed",
"condensed",
"semi-condensed",
"normal",
"semi-expanded",
"expanded",
"extra-expanded",
"ultra-expanded"
)
),
(style) =>
style.specified("font-stretch").map((stretch) => {
if (stretch.type === "percentage") {
return stretch;
}

switch (stretch.value) {
case "ultra-condensed":
return Percentage.of(0.5);
case "extra-condensed":
return Percentage.of(0.625);
case "condensed":
return Percentage.of(0.75);
case "semi-condensed":
return Percentage.of(0.875);
case "normal":
return Percentage.of(1);
case "semi-expanded":
return Percentage.of(1.125);
case "expanded":
return Percentage.of(1.25);
case "extra-expanded":
return Percentage.of(1.5);
case "ultra-expanded":
return Percentage.of(2);
}
}),
{ inherits: true }
);

export type Style =
| Keyword<"normal">
| Keyword<"italic">
| Keyword<"oblique">;

/**
* @see https://drafts.csswg.org/css-fonts/#font-style-prop
*
* oblique accepting an angle has poor browser support and shouldn't affect Alfa currently.
*/
export const Style: Property<Style> = Property.of(
Keyword.of("normal"),
Expand All @@ -215,9 +304,9 @@ export namespace Font {
export type Weight = Weight.Specified | Weight.Computed;

export namespace Weight {
export type Absolute = Keyword<"normal" | "bold">;
export type Absolute = Keyword<"normal"> | Keyword<"bold">;

export type Relative = Keyword<"bolder" | "lighter">;
export type Relative = Keyword<"bolder"> | Keyword<"lighter">;

export type Specified = Absolute | Relative | Number;

Expand Down Expand Up @@ -287,4 +376,139 @@ export namespace Font {
inherits: true,
}
);

/**
* Parses the "combinator any" part of the font shorthand.
*/
const parseFontAny: Parser<
Slice<Token>,
[
["font-stretch", Stretch.Specified],
["font-style", Style],
["font-weight", Weight.Specified]
],
string
> = (input) => {
const normal = Keyword.of("normal");

// "normal" can be a keyword for each of these four longhands, making parsing annoying…
// Fortunately, "normal" happens to also be the initial value of these longhands.
// So, we can have them default to "normal", and try to overwrite them as long as they still are "normal";
// when "normal" keyword is encountered, it will be affected to the first still "normal" longhand (for no effect)
// and the end result is OK (only the longhands with a non-"normal" specified value are changed).
//
// This approach will stop working if CSS ever decides that the initial value of these longhands is not "normal"
// or that setting the shorthand does not reset all the longhands. Both seem to be unlikely changes.
// It is nonetheless hacky to hardcode the initial value instead of using Keyword.of("initial") in the end…
let stretch: Stretch.Specified = normal;
let style: Style = normal;
let variant: Keyword<"normal"> | Keyword<"small-caps"> = normal;
let weight: Weight.Specified = normal;

while (true) {
for (const [remainder] of Token.parseWhitespace(input)) {
input = remainder;
}

if (normal.equals(stretch)) {
// only keyword stretch are allowed in the shorthand
const result = Keyword.parse(
"ultra-condensed",
"extra-condensed",
"condensed",
"semi-condensed",
"normal",
"semi-expanded",
"expanded",
"extra-expanded",
"ultra-expanded"
)(input);

if (result.isOk()) {
[input, stretch] = result.get();
continue;
}
}

if (normal.equals(style)) {
const result = Style.parse(input);

if (result.isOk()) {
[input, style] = result.get();
continue;
}
}

if (normal.equals(variant)) {
const result = Keyword.parse("normal", "small-caps")(input);

if (result.isOk()) {
[input, variant] = result.get();
continue;
}
}

if (normal.equals(weight)) {
const result = Weight.parse(input);

if (result.isOk()) {
[input, weight] = result.get();
continue;
}
}

break;
}

return Result.of([
input,
[
["font-stretch", stretch],
["font-style", style],
// we currently do not support font-variant and just ditch it.
["font-weight", weight],
],
]);
};

/**
* Alfa is not really equipped to deal with system fonts right now.
* The resulting family and size depends both on the OS and on the browser.
*/
export const Shorthand = Property.Shorthand.of(
[
"font-family",
"font-size",
"font-stretch",
"font-style",
"font-weight",
"line-height",
],
map(
pair(
parseFontAny,
pair(
right(option(Token.parseWhitespace), Size.parse),
pair(
option(
right(
pair(
pair(option(Token.parseWhitespace), Token.parseDelim("/")),
option(Token.parseWhitespace)
),
Line.Height.parse
)
),
right(Token.parseWhitespace, Family.parse)
)
)
),
([fontAny, [size, [lineHeight, family]]]) => [
...fontAny,
["font-size", size],
["line-height", lineHeight.getOr(Keyword.of("initial"))],
["font-family", family],
]
)
);
}
Loading