Files
MapLibreParticleLab/platform/darwin/scripts/generate-style-code.js
Minh Nguyễn a648f42bc7 [ios, macos] More specific default value types
The documentation for runtime style API properties now spells out the value of the default type, to help developers infer the type of the property itself.
2016-08-26 22:27:51 -07:00

339 lines
14 KiB
JavaScript

'use strict';
const fs = require('fs');
const ejs = require('ejs');
const spec = require('mapbox-gl-style-spec').latest;
const prefix = 'MGL';
const suffix = 'StyleLayer';
global.camelize = function (str) {
return str.replace(/(?:^|-)(.)/g, function (_, x) {
return x.toUpperCase();
});
};
global.camelizeWithLeadingLowercase = function (str) {
return str.replace(/-(.)/g, function (_, x) {
return x.toUpperCase();
});
};
global.objCName = function (property) { return camelizeWithLeadingLowercase(property.name); }
global.arrayType = function (property) {
return property.type === 'array' ? property.name.split('-').pop() : false;
};
global.testImplementation = function (property, layerType) {
switch (property.type) {
case 'boolean':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testBool;`;
case 'number':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testNumber;`;
case 'string':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testString;`;
case 'enum':
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
let objCEnum = `${objCType}${camelize(property.values[property.values.length-1])}`;
return `layer.${objCName(property)} = [MGLRuntimeStylingHelper testEnum:${objCEnum} type:@encode(${objCType})];`;
case 'color':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testColor;`;
case 'array':
return testArrayImplementation(property);
default: throw new Error(`unknown type for ${property.name}`);
}
}
global.testGetterImplementation = function (property, layerType) {
switch (property.type) {
case 'boolean':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testBool);`;
case 'number':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testNumber);`;
case 'string':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testString);`;
case 'enum':
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
let objCEnum = `${objCType}${camelize(property.values[property.values.length-1])}`;
return `XCTAssert([(NSValue *)gLayer.${objCName(property)} objCType] == [[MGLRuntimeStylingHelper testEnum:${objCEnum} type:@encode(${objCType})] objCType]);`;
case 'color':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testColor);`;
case 'array':
return testGetterArrayImplementation(property);
default: throw new Error(`unknown type for ${property.name}`);
}
}
global.testGetterArrayImplementation = function (property) {
switch (arrayType(property)) {
case 'dasharray':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testDashArray);`;
case 'font':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testFont);`;
case 'padding':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testPadding);`;
case 'offset':
case 'translate':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testOffset);`; // Default offset (dx, dy)
default:
throw new Error(`unknown array type for ${property.name}`);
}
};
global.testArrayImplementation = function (property) {
switch (arrayType(property)) {
case 'dasharray':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testDashArray;`;
case 'font':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testFont;`;
case 'padding':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testPadding;`;
case 'offset':
case 'translate':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testOffset;`; // Default offset (dx, dy)
default:
throw new Error(`unknown array type for ${property.name}`);
}
};
global.propertyDoc = function (property, layerType) {
let doc = property.doc.replace(/`(.+?)`/g, function (m, symbol, offset, str) {
if ('values' in property && property.values.indexOf(symbol) !== -1) {
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
return '`' + `${objCType}${camelize(symbol)}` + '`';
}
if (str.substr(offset - 4, 3) !== 'CSS') {
symbol = camelizeWithLeadingLowercase(symbol);
}
return '`' + symbol + '`';
});
if ('units' in property) {
if (!property.units.match(/s$/)) {
property.units += 's';
}
doc += `
This property is measured in ${property.units}.`;
}
return doc.replace(/(p)ixel/gi, '$1oint').replace(/(\d)px\b/g, '$1pt');
};
global.propertyReqs = function (property, layoutPropertiesByName, type) {
return 'This property is only applied to the style if ' + property.requires.map(function (req) {
if (typeof req === 'string') {
return '`' + camelizeWithLeadingLowercase(req) + '` is non-`nil`';
} else if ('!' in req) {
return '`' + camelizeWithLeadingLowercase(req['!']) + '` is set to `nil`';
} else {
let name = Object.keys(req)[0];
return '`' + camelizeWithLeadingLowercase(name) + '` is set to ' + describeValue(req[Object.keys(req)[0]], layoutPropertiesByName[name], type);
}
}).join(', and ') + '. Otherwise, it is ignored.';
};
global.parseColor = function (str) {
let m = str.match(/^#(\d\d)(\d\d)(\d\d)$/);
if (m) {
return {
r: parseInt(m[1], 16) / 255,
g: parseInt(m[2], 16) / 255,
b: parseInt(m[3], 16) / 255,
a: 1.0,
};
}
m = str.match(/^rgba\(\s*([-\d.]+),\s*([-\d.]+),\s*([-\d.]+),\s*([\d.]+)\s*\)$/);
if (m) {
return {
r: parseFloat(m[1]) / 255,
g: parseFloat(m[2]) / 255,
b: parseFloat(m[3]) / 255,
a: parseFloat(m[4]),
};
}
};
global.describeValue = function (value, property, layerType) {
switch (property.type) {
case 'boolean':
return 'an `NSNumber` object containing ' + (value ? '`YES`' : '`NO`');
case 'number':
return 'an `NSNumber` object containing the float `' + value + '`';
case 'string':
return 'the string `' + value + '`';
case 'enum':
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
return 'an `NSValue` object containing `' + `${objCType}${camelize(value)}` + '`';
case 'color':
let color = parseColor(value);
if (!color) {
throw new Error(`unrecognized color format in default value of ${property.name}`);
}
if (color.r === 0 && color.g === 0 && color.b === 0 && color.a === 0) {
return '`NSColor.clearColor` or `UIColor.clearColor`';
}
if (color.r === 0 && color.g === 0 && color.b === 0 && color.a === 1) {
return '`NSColor.blackColor` or `UIColor.blackColor`';
}
if (color.r === 1 && color.g === 1 && color.b === 1 && color.a === 1) {
return '`NSColor.whiteColor` or `UIColor.whiteColor`';
}
return 'an `NSColor` or `UIColor`' + ` object whose RGB value is ${color.r}, ${color.g}, ${color.b} and whose alpha value is ${color.a}`;
case 'array':
let units = property.units || '';
if (units) {
units = ` ${units}`.replace(/pixel/, 'point');
}
if (property.name.indexOf('padding') !== -1) {
if (value[0] === 0 && value[1] === 0 && value[2] === 0 && value[3] === 0) {
return 'an `NSValue` object containing `NSEdgeInsetsZero` or `UIEdgeInsetsZero`';
}
return 'an `NSValue` object containing an `NSEdgeInsets` or `UIEdgeInsets` struct set to' + ` ${value[0]}${units} on the top, ${value[3]}${units} on the left, ${value[2]}${units} on the bottom, and ${value[1]}${units} on the right`;
}
if (property.name.indexOf('offset') !== -1 || property.name.indexOf('translate') !== -1) {
return 'an `NSValue` object containing a `CGVector` struct set to' + ` ${value[0]}${units} from the left and ${value[1]}${units} from the top`;
}
return 'the array `' + value.join('`, `') + '`';
default:
throw new Error(`unknown type for ${property.name}`);
}
};
global.propertyDefault = function (property, layerType) {
return describeValue(property.default, property, layerType);
};
global.propertyType = function (property, _private) {
return _private ? `id <MGLStyleAttributeValue, MGLStyleAttributeValue_Private>` : `id <MGLStyleAttributeValue>`;
};
global.initLayer = function (layerType) {
if (layerType == "background") {
return `_layer = new mbgl::style::${camelize(layerType)}Layer(layerIdentifier.UTF8String);`
} else {
return `_layer = new mbgl::style::${camelize(layerType)}Layer(layerIdentifier.UTF8String, sourceIdentifier.UTF8String);`
}
}
global.setterImplementation = function(property, layerType) {
let implementation = '';
switch (property.type) {
case 'boolean':
implementation = `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_boolPropertyValue);`;
break;
case 'number':
implementation = `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_floatPropertyValue);`;
break;
case 'string':
implementation = `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_stringPropertyValue);`;
break;
case 'enum':
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
implementation = `MGLSetEnumProperty(${objCName(property)}, ${camelize(property.name)}, ${mbglType(property)}, ${objCType});`;
break;
case 'color':
implementation = `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_colorPropertyValue);`;
break;
case 'array':
implementation = arraySetterImplementation(property);
break;
default: throw new Error(`unknown type for ${property.name}`)
}
implementation += "\n [self update];"
return implementation;
}
global.mbglType = function(property) {
let mbglType = camelize(property.name) + 'Type';
if (/-translate-anchor$/.test(property.name)) {
mbglType = 'TranslateAnchorType';
}
if (/-(rotation|pitch)-alignment$/.test(property.name)) {
mbglType = 'AlignmentType';
}
return mbglType;
}
global.arraySetterImplementation = function(property) {
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_${convertedType(property)}PropertyValue);`;
}
global.styleAttributeFactory = function (property, layerType) {
switch (property.type) {
case 'boolean':
return 'mbgl_boolWithPropertyValueBool';
case 'number':
return 'mbgl_numberWithPropertyValueNumber';
case 'string':
return 'mbgl_stringWithPropertyValueString';
case 'enum':
throw new Error('Use MGLGetEnumProperty() for enums.');
case 'color':
return 'mbgl_colorWithPropertyValueColor';
case 'array':
return `mbgl_${convertedType(property)}WithPropertyValue${camelize(convertedType(property))}`;
default:
throw new Error(`unknown type for ${property.name}`);
}
};
global.getterImplementation = function(property, layerType) {
if (property.type === 'enum') {
let objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
return `MGLGetEnumProperty(${camelize(property.name)}, ${mbglType(property)}, ${objCType});`;
}
let rawValue = `self.layer->get${camelize(property.name)}() ?: self.layer->getDefault${camelize(property.name)}()`;
return `return [MGLStyleAttribute ${styleAttributeFactory(property, layerType)}:${rawValue}];`;
}
global.convertedType = function(property) {
switch (arrayType(property)) {
case 'dasharray':
return 'numberArray';
case 'font':
return 'stringArray';
case 'padding':
return 'padding';
case 'offset':
case 'translate':
return 'offset';
default:
throw new Error(`unknown array type for ${property.name}`);
}
}
const layerH = ejs.compile(fs.readFileSync('platform/darwin/src/MGLStyleLayer.h.ejs', 'utf8'), { strict: true });
const layerM = ejs.compile(fs.readFileSync('platform/darwin/src/MGLStyleLayer.mm.ejs', 'utf8'), { strict: true});
const testLayers = ejs.compile(fs.readFileSync('platform/darwin/src/MGLRuntimeStylingTests.m.ejs', 'utf8'), { strict: true});
const layers = spec.layer.type.values.map((type) => {
const layoutProperties = Object.keys(spec[`layout_${type}`]).reduce((memo, name) => {
if (name !== 'visibility') {
spec[`layout_${type}`][name].name = name;
memo.push(spec[`layout_${type}`][name]);
}
return memo;
}, []);
const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => {
spec[`paint_${type}`][name].name = name;
memo.push(spec[`paint_${type}`][name]);
return memo;
}, []);
return {
type: type,
layoutProperties: layoutProperties,
paintProperties: paintProperties,
layoutPropertiesByName: spec[`layout_${type}`],
paintPropertiesByName: spec[`paint_${type}`],
};
});
for (const layer of layers) {
fs.writeFileSync(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.h`, layerH(layer));
fs.writeFileSync(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.mm`, layerM(layer));
fs.writeFileSync(`platform/darwin/test/${prefix}${camelize(layer.type)}${suffix}Tests.m`, testLayers(layer));
}