Files
MapLibreParticleLab/scripts/generate-style-code.js
Chloe Krawczyk 77d0d5e5cf Add bindings for circle-sort-key (#322)
* Update generate-style-code.js

* add jni bindings for circle-sort-key

* update mapbox-gl-native to latest

* Revert "add jni bindings for circle-sort-key"

This reverts commit cdd6049d

Co-authored-by: kevin <kevin.li@mapbox.com>
2020-04-15 17:56:54 +08:00

410 lines
14 KiB
JavaScript
Executable File

#!/usr/bin/env node
'use strict';
const fs = require('fs');
const ejs = require('ejs');
const spec = require('../vendor/mapbox-gl-native/scripts/style-spec');
const _ = require('lodash');
// FIXME: https://github.com/mapbox/mapbox-gl-native/issues/15008
//delete spec.layout_circle["circle-sort-key"]
//delete spec.layout_line["line-sort-key"]
//delete spec.layout_fill["fill-sort-key"]
require('../vendor/mapbox-gl-native/scripts/style-code');
// Specification parsing //
const lightProperties = Object.keys(spec[`light`]).reduce((memo, name) => {
var property = spec[`light`][name];
property.name = name;
property['light-property'] = true;
property.doc = property.doc.replace(/°/g,'&#xB0;');
memo.push(property);
return memo;
}, []);
// Collect layer types from spec
var layers = Object.keys(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,
doc: spec.layer.type.values[type].doc,
layoutProperties: layoutProperties,
paintProperties: paintProperties,
properties: layoutProperties.concat(paintProperties)
};
});
// Process all layer properties
const uniqueArrayEnum = (prop, enums) => {
if (prop.value !== 'enum') return false;
const enumsEqual = (val1, val2) => val1.length === val1.length && val1.every((val, i) => val === val2[i]);
return enums.filter(e => enumsEqual(Object.keys(prop.values).sort(), Object.keys(e.values).sort())).length == 0;
};
const layoutProperties = _(layers).map('layoutProperties').flatten().value();
const paintProperties = _(layers).map('paintProperties').flatten().value();
const allProperties = _(layoutProperties).union(paintProperties).union(lightProperties).value();
let allEnumProperties = _(allProperties).filter({'type': 'enum'}).value();
const uniqueArrayEnumProperties = _(allProperties).filter({'type': 'array'}).filter(prop => uniqueArrayEnum(prop, allEnumProperties)).value();
const enumProperties = _(allEnumProperties).union(uniqueArrayEnumProperties).value();
global.propertyType = function propertyType(property) {
switch (property.type) {
case 'boolean':
return 'Boolean';
case 'number':
return 'Float';
case 'formatted':
return 'Formatted';
case 'string':
case 'resolvedImage':
return 'String';
case 'enum':
return 'String';
case 'color':
return 'String';
case 'array':
return `${propertyType({type:property.value})}[]`;
default:
throw new Error(`unknown type for ${property.name}`);
}
}
global.propertyJavaType = function propertyType(property) {
switch (property.type) {
case 'boolean':
return 'boolean';
case 'number':
return 'float';
case 'formatted':
return 'Formatted';
case 'string':
case 'resolvedImage':
return 'String';
case 'enum':
return 'String';
case 'color':
return 'String';
case 'array':
return `${propertyJavaType({type:property.value})}[]`;
default:
throw new Error(`unknown type for ${property.name}`);
}
}
global.propertyJNIType = function propertyJNIType(property) {
switch (property.type) {
case 'boolean':
return 'jboolean';
case 'number':
return 'jfloat';
case 'String':
return 'String';
case 'enum':
return 'String';
case 'color':
return 'String';
case 'array':
return `jarray<${propertyType({type:property.value})}[]>`;
default:
return 'jobject*';
}
}
global.propertyNativeType = function (property) {
if (/-translate-anchor$/.test(property.name)) {
return 'TranslateAnchorType';
}
if (/-(rotation|pitch|illumination)-alignment$/.test(property.name)) {
return 'AlignmentType';
}
if (/^(text|icon)-anchor$/.test(property.name)) {
return 'SymbolAnchorType';
}
switch (property.type) {
case 'boolean':
return 'bool';
case 'number':
return 'float';
case 'formatted':
case 'string':
case 'resolvedImage': // TODO: replace once we implement image expressions
return 'std::string';
case 'enum':
if(property['light-property']){
return `Light${camelize(property.name)}Type`;
}
return `${camelize(property.name)}Type`;
case 'color':
return `Color`;
case 'array':
if (property.length) {
return `std::array<${propertyType({type: property.value})}, ${property.length}>`;
} else {
return `std::vector<${propertyType({type: property.value})}>`;
}
default: throw new Error(`unknown type for ${property.name}`)
}
}
global.propertyTypeAnnotation = function propertyTypeAnnotation(property) {
switch (property.type) {
case 'enum':
return `@Property.${snakeCaseUpper(property.name)}`;
default:
return "";
}
};
global.defaultExpressionJava = function(property) {
switch (property.type) {
case 'boolean':
return 'boolean';
case 'number':
return 'number';
case 'formatted':
return 'format';
case 'resolvedImage':
return "image";
case 'string':
case 'image':
return "string";
case 'enum':
return "string";
case 'color':
return 'toColor';
case 'array':
return "array";
default: return "string";
}
}
global.defaultValueJava = function(property) {
if(property.name.endsWith("-pattern")) {
return '"pedestrian-polygon"';
}
if(property.name.endsWith("-font")) {
return 'new String[]{"Open Sans Regular", "Arial Unicode MS Regular"}';
}
switch (property.type) {
case 'boolean':
return 'true';
case 'number':
return '0.3f';
case 'formatted':
return 'new Formatted(new FormattedSection("default"))'
case 'string':
case 'resolvedImage':
return '"' + property['default'] + '"';
case 'enum':
return snakeCaseUpper(property.name) + "_" + snakeCaseUpper(Object.keys(property.values)[0]);
case 'color':
return '"rgba(255,128,0,0.7)"';
case 'array':
switch (property.value) {
case 'string':
case 'enum':
if (property['default'] !== undefined) {
return '[' + property['default'] + ']';
} else {
return 'new String[0]';
}
case 'number':
var result ='new Float[] {';
for (var i = 0; i < property.length; i++) {
result += "0f";
if (i +1 != property.length) {
result += ", ";
}
}
return result + "}";
}
default: throw new Error(`unknown type for ${property.name}`)
}
}
/**
* Produces documentation for property factory methods
*/
global.propertyFactoryMethodDoc = function (property) {
var replaceIfPixels = function (doc) {
return doc.replace('pixels', 'density-independent pixels')
}
let doc = replaceIfPixels(property.doc);
// Match other items in back ticks
doc = doc.replace(/`(.+?)`/g, function (m, symbol, offset, str) {
if (str.substr(offset - 4, 3) !== 'CSS' && symbol[0].toUpperCase() != symbol[0] && _(enumProperties).filter({'name': symbol}).value().length > 0) {
// Property 'enums'
symbol = snakeCaseUpper(symbol);
return '{@link Property.' + symbol + '}';
} else if( _(allProperties).filter({'name': symbol}).value().length > 0) {
// Other properties
return '{@link PropertyFactory#' + camelizeWithLeadingLowercase(symbol) + '}';
} else {
// Left overs
return '`' + symbol + '`';
}
});
return doc;
};
/**
* Produces documentation for property value constants
*/
global.propertyValueDoc = function (property, value) {
// Match references to other property names & values.
// Requires the format 'When `foo` is set to `bar`,'.
let doc = property.values[value].doc.replace(/When `(.+?)` is set to `(.+?)`(?: or `([^`]+?)`)?,/g, function (m, peerPropertyName, propertyValue, secondPropertyValue, offset, str) {
let otherProperty = snakeCaseUpper(peerPropertyName);
let otherValue = snakeCaseUpper(peerPropertyName) + '_' + snakeCaseUpper(propertyValue);
const firstPropertyValue = 'When {@link ' + `${otherProperty}` + '} is set to {@link Property#' + `${otherValue}` + '}';
if (secondPropertyValue) {
return firstPropertyValue + ` or {@link Property#${snakeCaseUpper(peerPropertyName) + '_' + snakeCaseUpper(secondPropertyValue)}},`;
} else {
return firstPropertyValue + ',';
}
});
// Match references to our own property values.
// Requires the format 'is equivalent to `bar`'.
doc = doc.replace(/is equivalent to `(.+?)`/g, function(m, propertyValue, offset, str) {
propertyValue = snakeCaseUpper(property.name) + '_' + snakeCaseUpper(propertyValue);
return 'is equivalent to {@link Property#' + propertyValue + '}';
});
// Match other items in back ticks
doc = doc.replace(/`(.+?)`/g, function (m, symbol, offset, str) {
if ('values' in property && Object.keys(property.values).indexOf(symbol) !== -1) {
// Property values
propertyValue = snakeCaseUpper(property.name) + '_' + snakeCaseUpper(symbol);
console.log("Transforming", symbol, propertyValue);
return '{@link Property#' + `${propertyValue}` + '}';
} else if (str.substr(offset - 4, 3) !== 'CSS' && symbol[0].toUpperCase() != symbol[0]) {
// Property 'enums'
if (symbol === 'symbol-sort-key') {
return 'symbol sort key';
}
symbol = snakeCaseUpper(symbol);
return '{@link ' + symbol + '}';
} else {
// Left overs
return symbol
}
});
return doc;
};
global.isLightProperty = function (property) {
return property['light-property'] === true;
};
global.propertyValueType = function (property) {
switch (property['property-type']) {
default:
return `PropertyValue<${evaluatedType(property)}>`;
}
};
global.evaluatedType = function (property) {
if (/-translate-anchor$/.test(property.name)) {
return 'TranslateAnchorType';
}
if (/-(rotation|pitch|illumination)-alignment$/.test(property.name)) {
return 'AlignmentType';
}
if (/^(text|icon)-anchor$/.test(property.name)) {
return 'SymbolAnchorType';
}
if (/position/.test(property.name)) {
return 'Position';
}
switch (property.type) {
case 'boolean':
return 'bool';
case 'number':
return 'float';
case 'formatted':
case 'string':
case 'image':
return 'std::string';
case 'enum':
return (isLightProperty(property) ? 'Light' : '') + `${camelize(property.name)}Type`;
case 'color':
return `Color`;
case 'array':
if (property.length) {
return `std::array<${evaluatedType({type: property.value})}, ${property.length}>`;
} else {
return `std::vector<${evaluatedType({type: property.value})}>`;
}
default: throw new Error(`unknown type for ${property.name}`)
}
};
global.supportsZoomFunction = function (property) {
return property.expression && property.expression.parameters.indexOf('zoom') > -1;
};
global.supportsPropertyFunction = function (property) {
return property['property-type'] === 'data-driven' || property['property-type'] === 'cross-faded-data-driven';
};
// Template processing //
// Java + JNI Light (Peer model)
const lightJava = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/light.java.ejs', 'utf8'), {strict: true});
const lightJavaUnitTests = ejs.compile(fs.readFileSync('MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/light.junit.ejs', 'utf8'), {strict: true});
writeIfModified(`MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/light/Light.java`, lightJava({properties: lightProperties}));
writeIfModified(`MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LightTest.java`, lightJavaUnitTests({properties: lightProperties}));
// Java
const layerJava = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs', 'utf8'), {strict: true});
const layerJavaUnitTests = ejs.compile(fs.readFileSync('MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs', 'utf8'), {strict: true});
for (const layer of layers) {
writeIfModified(`MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/${camelize(layer.type)}Layer.java`, layerJava(layer));
writeIfModified(`MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/${camelize(layer.type)}LayerTest.java`, layerJavaUnitTests(layer));
}
// Jni
const layerHpp = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/cpp/style/layers/layer.hpp.ejs', 'utf8'), {strict: true});
const layerCpp = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/cpp/style/layers/layer.cpp.ejs', 'utf8'), {strict: true});
for (const layer of layers) {
const layerFileName = layer.type.replace('-', '_');
writeIfModified(`MapboxGLAndroidSDK/src/cpp/style/layers/${layerFileName}_layer.hpp`, layerHpp(layer));
writeIfModified(`MapboxGLAndroidSDK/src/cpp/style/layers/${layerFileName}_layer.cpp`, layerCpp(layer));
}
// Java PropertyFactory
const propertiesTemplate = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/property_factory.java.ejs', 'utf8'), {strict: true});
writeIfModified(
`MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java`,
propertiesTemplate({layoutProperties: layoutProperties, paintProperties: paintProperties})
);
// Java Property
const enumPropertyJavaTemplate = ejs.compile(fs.readFileSync('MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/property.java.ejs', 'utf8'), {strict: true});
writeIfModified(
`MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java`,
enumPropertyJavaTemplate({properties: enumProperties})
);