From 01d78081a2c2f2a3e8ec83b0b13f1f704458beb8 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 25 Sep 2023 22:44:44 +0000 Subject: [PATCH] fix: Handle Joyce Geometry Applet color specifications (#33) This PR attempts to capture all of the color structure of the original Geometry Applet, except for pivot points defaulting to green, since there are no pivot points yet. Resolves #8. Reviewed-on: https://code.studioinfinity.org/glen/archematics/pulls/33 Co-authored-by: Glen Whitney Co-committed-by: Glen Whitney --- etc/colorsea_types.patch | 30 +++++ package.json5 | 3 +- pnpm-lock.yaml | 15 ++- src/adapptlet.civet | 232 ++++++++++++++++++++++++++++++++++----- tools/copyDeps.bash | 3 + 5 files changed, 250 insertions(+), 33 deletions(-) create mode 100644 etc/colorsea_types.patch diff --git a/etc/colorsea_types.patch b/etc/colorsea_types.patch new file mode 100644 index 0000000..ece4219 --- /dev/null +++ b/etc/colorsea_types.patch @@ -0,0 +1,30 @@ +--- node_modules/colorsea/dist/index.d.ts 2023-09-25 11:41:07.620198667 -0700 ++++ tsbuild/deps/colorsea.d.ts 2023-09-25 12:24:39.126612604 -0700 +@@ -319,6 +319,9 @@ + }; + } + ++type ColorConstructor = typeof Color ++type DeltaEfunc = typeof deltaE ++ + /** + * Create a color instance + * @param colorInput Input your color value 设置颜色值 +@@ -330,7 +333,7 @@ + var config: (config: ColorConfig) => void; + var random: () => Color; + var convertor: typeof __convertor; +- var Color: typeof Color; ++ var Color: ColorConstructor; + var rgb: (r: number, g: number, b: number, alpha?: number | undefined) => Color; + var hsl: (h: number, s: number, l: number, alpha?: number | undefined) => Color; + var hsv: (h: number, s: number, v: number, alpha?: number | undefined) => Color; +@@ -340,7 +343,7 @@ + var lab: (l: number, a: number, b: number, alpha?: number | undefined) => Color; + var lch: (l: number, c: number, h: number, alpha?: number | undefined) => Color; + var mix: (color1: string | CommonColorTuple | CommonColoraTuple | Color, color2: string | CommonColorTuple | CommonColoraTuple | Color, weight: number) => Color; +- var deltaE: typeof deltaE; ++ var deltaE: DeltaEfunc; + var utils: { + /** + * Rounding preserves the specified decimal place diff --git a/package.json5 b/package.json5 index 83e2952..d247c36 100644 --- a/package.json5 +++ b/package.json5 @@ -33,12 +33,13 @@ url: 'https://code.studioinfinity.org/glen/archematics.git', }, devDependencies: { - '@danielx/civet': '^0.6.38', + '@danielx/civet': '^0.6.39', '@types/jquery': '^3.5.19', 'http-server': '^14.1.1', typescript: '^5.2.2', }, dependencies: { + colorsea: '^1.2.1', vrml1to97: '^0.2.2', }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76fd228..4f7938d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,14 +5,17 @@ settings: excludeLinksFromLockfile: false dependencies: + colorsea: + specifier: ^1.2.1 + version: 1.2.1 vrml1to97: specifier: ^0.2.2 version: 0.2.2 devDependencies: '@danielx/civet': - specifier: ^0.6.38 - version: 0.6.38(typescript@5.2.2) + specifier: ^0.6.39 + version: 0.6.39(typescript@5.2.2) '@types/jquery': specifier: ^3.5.19 version: 3.5.19 @@ -32,8 +35,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@danielx/civet@0.6.38(typescript@5.2.2): - resolution: {integrity: sha512-R63YGIfvV4DQianNPUfMfBX60ozlv5htnRXI1wK3Pg6+d4NZ2V3RifFRH0NkmXXoFkRcULzJ+9BzVeI2/yX+yA==} + /@danielx/civet@0.6.39(typescript@5.2.2): + resolution: {integrity: sha512-rePpUAVruQAY5QgYp3gWlNoGSnua0TBu1PkhXZtFjbixrvdGiu0umEZ2eeV1DJ6wFpYGxzYMhqq0Ca9sY3jIUw==} engines: {node: '>=19 || ^18.6.0 || ^16.17.0'} hasBin: true peerDependencies: @@ -168,6 +171,10 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /colorsea@1.2.1: + resolution: {integrity: sha512-EkYJGtJ7GcXe/aIkeAZDO/4KaKA8yaxdsXXBNR2MHsEZ6JIzBbAzKc3mH2D8VqGqMciqIpJhCZI1QI7nJQ2aDg==} + dev: false + /corser@2.0.1: resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} engines: {node: '>= 0.4.0'} diff --git a/src/adapptlet.civet b/src/adapptlet.civet index 0cd01e4..60177c5 100644 --- a/src/adapptlet.civet +++ b/src/adapptlet.civet @@ -1,5 +1,6 @@ import https://code.jquery.com/jquery-3.7.1.js import type {AppletObject} from ./deps/geogebra/api.ts +colorsea from ./deps/colorsea.js type AppletDescription html: string @@ -44,13 +45,18 @@ jQuery.getScript 'https://www.geogebra.org/apps/deployggb.js', => for each jApp of joyceApplets params := { appName: 'classic', + -enableRightClick, + // +showMenuBar, jApp.width, jApp.height, appletOnLoad: (api: AppletObject) => elements: JoyceElements := {} + backgroundRGB := [255, 255, 255] as RGB for child of jApp.children - dispatchJcommand api, child, elements - api.setCoordSystem(-10, 10 + jApp.width, -10, 10 + jApp.height) + dispatchJcommand api, child, elements, backgroundRGB + api.setCoordSystem -10, 10 + jApp.width, -10, 10 + jApp.height + api.setAxesVisible false, false + api.setGridVisible false } as const geoApp := new GGBApplet params geoApp.inject jApp.id @@ -76,27 +82,35 @@ function freshCommander(): Commander type JoyceArguments = Partial & {scalar: number[]}> - type ClassHandler = ( name: GeoName, m: string, args: JoyceArguments, index: number) => Commander +type RGB = [number, number, number] +type XYZ = RGB // Executes the command corresponding to param against the GeoGebra applet // api, consulting and extending by side effect the elements that are // present in that applet function dispatchJcommand( - api: AppletObject, param: Element, elements: JoyceElements): void + api: AppletObject, + param: Element, + elements: JoyceElements + backgroundRGB: RGB): void val := param.getAttribute 'value' unless val return attr := param.getAttribute 'name' switch attr 'background' - api.setGraphicsOptions 1, bgColor: `#${val}` + backgroundHex := `#${val}` + api.setGraphicsOptions 1, bgColor: backgroundHex + newback := colorsea(backgroundHex).rgb() + for i of [0..2] + backgroundRGB[i] = newback[i] 'title' api.evalCommand `TitlePoint = Corner(1,1) Text("${val}", TitlePoint + (2,5))` /e\[\d+\]/ num := parseInt(attr.slice(2)) - {commands, callbacks, parts} := jToG val, elements, num + {commands, callbacks, parts} := jToG val, elements, num, backgroundRGB if commands.length lastTried .= 0 if commands.filter((&)).every (cmd) => @@ -117,8 +131,12 @@ function dispatchJcommand( // Parses a Joyce element-creating command, extending the elements // by side effect: -function jToG(jCom: string, elements: JoyceElements, index: number): Commander - [jname, klass, method, data, ...colors] := jCom.split(';') +function jToG( + jCom: string, + elements: JoyceElements, + index: number, + backgroundRGB: RGB): Commander + [jname, klass, method, data, ...colors] := jCom.split ';' cmdr .= freshCommander() unless klass in classHandler console.log `Unknown entity class ${klass}` @@ -144,32 +162,191 @@ function jToG(jCom: string, elements: JoyceElements, index: number): Commander (args.subpoints ?= []).push ...ends ?? [] cmdr = classHandler[klass] name, method, args, index unless name is jname then cmdr.callbacks.push (api: AppletObject) => - api.setCaption(name, jname) - api.setLabelStyle(name, 3) // style CAPTION = 3 + api.setCaption name, jname + api.setLabelStyle name, 3 // style CAPTION = 3 if cmdr.auxiliaries.length cmdr.callbacks.push (api: AppletObject) => for each aux of cmdr.auxiliaries - api.setAuxiliary(aux, true) - api.setVisible(aux,false) + api.setAuxiliary aux, true + api.setVisible aux,false // Create callback to assign colors - if colors.length is 4 and colors.every (color) => - false and color is '0' or color is 'none' - cmdr.callbacks.push (api: AppletObject) => - api.setVisible(name, false) + if colors.length is 4 and colors.every (color) => invisible color + cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false + else // we have to decorate + dimension .= cmdr.parts.findLastIndex .includes name + cmdr.callbacks.push (api: AppletObject, parts: DimParts) => + trace := false // e.g., klass is 'polygon' + // Operate in order faces, lines, point, caption so that + // we can adjust components after setting overall color, etc. + + // Color the "Faces"; they default to 'brighter': + if invisible colors[3] + for each face of parts[2] + if face is name + console.log 'Fading out interior of', face if trace + // hide the interior by making it transparent + api.setFilling face, 0 + else if face not in elements + console.log 'Hiding face', face if trace + api.setVisible face, false + else + faceRGB := joyce2rgb(colors[3] or 'brighter', backgroundRGB) + deep := ['circle', 'polygon', 'sector'] + filling := deep.includes(klass) ? 0.7 : 0.2 + for each face of parts[2] + console.log 'Coloring face', face, 'to', colors[3] if trace + api.setVisible face, true + api.setFilling face, filling + api.setColor face, ...faceRGB + + // Lines default to black: + if invisible colors[2] + for each line of parts[1] + unless line in elements + console.log 'Hiding line', line if trace + api.setVisible line, false + else + lineRGB := joyce2rgb(colors[2] or 'black', backgroundRGB) + for each line of parts[1] + console.log 'Coloring line', line, 'to', colors[2] if trace + api.setVisible line, true + api.setColor line, ...lineRGB + + // Now color the points: + if invisible colors[1] + // Hide all the dim-0 elements that are not their own independent + // items: + for each point of parts[0] + unless point in elements + console.log 'Hiding point', point if trace + api.setVisible point, false + else if dimension is 0 or colors[1] // Need to color the points + ptRGB := colors[1] ? joyce2rgb colors[1], backgroundRGB + : pointDefaultRGB name, method + for each point of parts[0] + console.log 'Coloring point', point, 'to', colors[1] if trace + api.setVisible point, true + api.setColor point, ...ptRGB + + // Make the caption the correct color + if invisible colors[0] + console.log 'Hiding label', name if trace + api.setLabelVisible name, false + else if colors[dimension] and colors[dimension] is not colors[0] + // Have to make a text to provide the caption, since GeoGebra + // doesn't allow caption different color from entity. + textName := 'GeoText' + index + locationExpr := switch klass + when 'point' then `1.02${name})` + when 'line' + (`Midpoint(${name}) + ` + + `Rotate(Direction(${name})*Length(${name})*0.02, pi/2)`) + when 'circle' + `Center(${name}) + Radius(${name})*Vector((12/13,5/13))*1.03` + when 'polygon' then `Centroid(${name})` + when 'sector' + `(5*Center(${name}) - ${cmdr.ends?[0]} - ${cmdr.ends?[1]})/3` + when 'plane' + `Intersect(${name}, PerpendicularLine((0, 0, 0), ${name}))` + when 'sphere' + `Center(${name})+Radius(${name})*Vector((12/13,0,5/13))*1.03` + when 'polyhedron' + // The "ends" are faces or vertices roughly opposite + // from each other + [ex1, ex2] .= cmdr.ends ?? ['', ''] + unless parts[0].includes ex1 then ex1 = `Centroid(${ex1})` + unless parts[0].includes ex2 then ex2 = `Centroid(${ex2})` + `(4*${ex1}+${ex2})/5` + api.evalCommand `${textName} = Text("${jname}", ${locationExpr})` + api.setColor textName, ...joyce2rgb colors[0], backgroundRGB + // and hide the underlying GeoGebra label + api.setLabelVisible name, false + else if colors[0] + // Label gets the correct color from element + // but we had better make sure it is visible: + console.log 'Showing label', name if trace + api.setLabelVisible name, true + else + // label color is defaulting. Same as element for points, invisible + // otherwise: + show := klass is 'point' + console.log 'Setting label vis of', name, 'to', show if trace + api.setLabelVisible name, show + // window[hideListener] = (arg) => -// console.log('Hello', arg, 'disappearing', name) -// api.setVisible(name, false) - api.registerObjectUpdateListener(name, hideListener) - if cmdr.ends or klass is 'line' +// api.setVisible name, false +// api.registerObjectUpdateListener name, hideListener + if cmdr.ends // line or sector elements[jname] = - {otherName: name, usesCaptions, klass: 'line', cmdr.ends} + {otherName: name, usesCaptions, klass, cmdr.ends} elements[name] = - {otherName: jname, usesCaptions, klass: 'line', cmdr.ends} + {otherName: jname, usesCaptions, klass, cmdr.ends} else // any other geometry elements[jname] = {otherName: name, usesCaptions, klass} elements[name] = {otherName: jname, usesCaptions, klass} cmdr +function invisible(cname: string): boolean + cname is '0' or cname is 'none' + +function joyce2rgb(cname: string, backgroundRGB?: RGB): RGB + whiteRGB: RGB := [255, 255, 255] + bg: RGB := backgroundRGB or whiteRGB + switch cname + // RGB values from Duck Duck Go search on "[COLOR] rgb" + /black/i + [0,0,0] + /blue/i + [0,0,255] + /cyan/i + [0,255,255] + /darkgray/i + [128,128,128] + /^gray/i + [169,169,169] + /green/i + [0,255,0] + /lightgray/i + [211,211,211] + /magenta/i + [255,0,255] + /orange/i + [255,165,0] + /pink/i + [255,192,203] + /red/i + [255,0,0] + /white/i + [255,255,255] + /yellow/i + [255,255,0] + /random/i + colorsea.random().lighten(40).rgb() + /background/i + bg + /brighter/i + colorsea(bg).lighten(30).rgb() + /darker/i + colorsea(bg).darken(20).rgb() + /^[0-9A-F]{6}$/i + colorsea(`#${cname}`).rgb() + /^\d+,\d+,\d+$/ + // HSB specification + [H,S,B] := cname.split(',').map (s) => parseInt s + colorsea.hsv(H, S, B).rgb() + else + console.log 'Could not parse color:', cname + [128, 128, 128] + +function pointDefaultRGB(name: string, method: string): RGB + // Need to short-circuit with green for pivot point, once that is implemented + switch method + 'free' + joyce2rgb 'red' + /.*[Ss]lider$/ + joyce2rgb 'orange' + else joyce2rgb 'black' + function geoname(jname: JoyceName, elements: JoyceElements): GeoName numCode .= 0n numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname @@ -188,7 +365,7 @@ classHandler: Record := /free|fixed/ commands.push `${name} = (${args.scalar?.join ','})` if method is 'fixed' - callbacks.push (api: AppletObject) => api.setFixed(name, true) + callbacks.push (api: AppletObject) => api.setFixed name, true 'perpendicular' // Note only the two-point option implemented so far unless args.subpoints return @@ -230,8 +407,7 @@ classHandler: Record := commands.push `${name} = Point(${segment})` if args.scalar and args.scalar.length callbacks.push (api: AppletObject) => - api.setCoords name, - ...args.scalar as [number, number, number] + api.setCoords name, ...args.scalar as XYZ 'first' unless args.subpoints then return commands.push `${name} = ${args.subpoints[0]}` @@ -310,7 +486,7 @@ classHandler: Record := unless madeSegment commands.push `${name} = Segment(${ends[0]},${ends[1]})` callbacks.push (api: AppletObject) => - api.setLabelVisible(name, true) + api.setLabelVisible name, true parts[0].push ...ends circle: (name, method, args) => @@ -323,7 +499,7 @@ classHandler: Record := unless args.subpoints then return [center, point] := args.subpoints commands.push `${name} = Circle(${center}, ${point})` - callbacks.push (api: AppletObject) => api.setLabelVisible(name, true) + callbacks.push (api: AppletObject) => api.setLabelVisible name, true polygon: (name, method, args, index) => return := freshCommander() @@ -349,7 +525,7 @@ classHandler: Record := parts[1].push newObj 'point' parts[0].push newObj - api.setVisible(newObj, false) + api.setVisible newObj, false /triangle|quadrilateral/ pt := args.subpoints unless pt then return diff --git a/tools/copyDeps.bash b/tools/copyDeps.bash index 66c2e44..87b3aac 100644 --- a/tools/copyDeps.bash +++ b/tools/copyDeps.bash @@ -1,3 +1,6 @@ # Takes one parameter, the destination directory mkdir -p $1 cp -rL node_modules/vrml1to97/{deps,vrml1to97,streamToString.js} $1 +cp -rL node_modules/colorsea/dist/index.esm.js $1/colorsea.js +cp -rL node_modules/colorsea/dist/index.d.ts $1/colorsea.d.ts +patch -u $1/colorsea.d.ts -i etc/colorsea_types.patch