-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
138 lines (116 loc) · 4.48 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const postcss = require('postcss');
const pluginName = 'plumber';
const unitRegExp = /^(\d+(?:\.\d+)?)([a-z]{1,4})$/;
const defaults = {
fontSize: 2,
gridHeight: '1rem',
lineHeight: 3,
leadingTop: 1,
leadingBottom: 2,
useBaselineOrigin: 0
};
// Converts css property names (including custom properties) to their javascript counterparts e.g.
// font-size -> fontSize
// --grid-height -> gridHeight
const toCamelCase = (value) => {
return value.replace(/^-+/, '').replace(/-([a-z])/g, (nothing, match) => match.toUpperCase());
};
// Converts javascript property names to their css counterparts e.g.
// fontSize -> font-size
const toKebabCase = (value) => {
return value.replace(/([A-Z])/g, (match) => '-' + match.toLowerCase());
};
// Round value to the nearest quarter pixel
const round = (value) => Math.round(value * 4) / 4;
const sanitizeParams = (params) => {
Object.keys(params).forEach(prop => {
let value = params[prop];
// separate value and unit
if (prop === 'gridHeight') {
const match = unitRegExp.exec(value);
value = {
gridHeight: Number(match[1]),
unit: match[2]
};
} else {
value = Number(value);
}
params[prop] = value;
});
return params;
};
const generateDeclarations = (values, unit) => {
let declarations = [];
Object.keys(values).forEach(prop => {
// http://stackoverflow.com/a/18358056
const value = Number(Math.round(values[prop] + 'e+6') + 'e-6');
declarations.push(
postcss.decl({
prop: toKebabCase(prop),
value: value + (value === 0 ? '' : unit)
})
);
});
return declarations;
};
const getBaselineCorrection = (lineHeight, fontSize, baseline) => {
// the distance of the original baseline from the bottom
const baselineFromBottom = (lineHeight - fontSize) / 2 + fontSize * baseline;
// the corrected baseline will be on the nearest gridline
const correctedBaseline = Math.round(baselineFromBottom);
// the difference between the original and the corrected baseline
const baselineDifference = correctedBaseline - baselineFromBottom;
return { correctedBaseline, baselineDifference };
};
module.exports = postcss.plugin(pluginName, (options = {}) => {
// merge default and passed options
// todo validate passed options
options = Object.assign(defaults, options);
return function (css) {
css.walkAtRules(pluginName, rule => {
// merge current parameters into options
let params = Object.assign({}, options);
rule.walkDecls(decl => {
// todo validate params
params[toCamelCase(decl.prop)] = decl.value;
});
// sanitize values
params = sanitizeParams(params);
const { baseline } = params;
const { gridHeight, unit } = params.gridHeight;
let { fontSize, lineHeight, leadingTop, leadingBottom } = params;
let marginTop, marginBottom, paddingTop, paddingBottom;
const { correctedBaseline, baselineDifference } = getBaselineCorrection(
lineHeight,
fontSize,
baseline
);
if (params.useBaselineOrigin) {
// substract the distance of the baseline from the edges
leadingTop -= (lineHeight - correctedBaseline);
leadingBottom -= correctedBaseline;
}
const shift = baselineDifference < 0 ? 0 : 1;
fontSize = fontSize * gridHeight;
lineHeight = lineHeight * gridHeight;
marginTop = (leadingTop - shift) * gridHeight;
paddingTop = (shift - baselineDifference) * gridHeight;
paddingBottom = (1 - shift + baselineDifference) * gridHeight;
marginBottom = (leadingBottom + shift - 1) * gridHeight;
let computedValues = {
lineHeight,
marginTop,
paddingTop,
paddingBottom,
marginBottom
};
if (unit === 'px') {
Object.keys(computedValues).forEach(function (prop) {
computedValues[prop] = round(computedValues[prop]);
});
}
Object.assign(computedValues, { fontSize });
rule.replaceWith(generateDeclarations(computedValues, unit));
});
};
});