fix: Handle Joyce Geometry Applet color specifications

Not everything is tested, but tried to capture all of the structure
   of the original, except for pivot points green, since there are no
   pivot points yet. Also many of the color specifications are missing.
   Resolves #8.
This commit is contained in:
Glen Whitney 2023-09-25 13:35:28 -07:00
parent bab48b25ad
commit 147b478a34
5 changed files with 212 additions and 30 deletions

30
etc/colorsea_types.patch Normal file
View File

@ -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

View File

@ -33,12 +33,13 @@
url: 'https://code.studioinfinity.org/glen/archematics.git', url: 'https://code.studioinfinity.org/glen/archematics.git',
}, },
devDependencies: { devDependencies: {
'@danielx/civet': '^0.6.38', '@danielx/civet': '^0.6.39',
'@types/jquery': '^3.5.19', '@types/jquery': '^3.5.19',
'http-server': '^14.1.1', 'http-server': '^14.1.1',
typescript: '^5.2.2', typescript: '^5.2.2',
}, },
dependencies: { dependencies: {
colorsea: '^1.2.1',
vrml1to97: '^0.2.2', vrml1to97: '^0.2.2',
}, },
} }

View File

@ -5,14 +5,17 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: dependencies:
colorsea:
specifier: ^1.2.1
version: 1.2.1
vrml1to97: vrml1to97:
specifier: ^0.2.2 specifier: ^0.2.2
version: 0.2.2 version: 0.2.2
devDependencies: devDependencies:
'@danielx/civet': '@danielx/civet':
specifier: ^0.6.38 specifier: ^0.6.39
version: 0.6.38(typescript@5.2.2) version: 0.6.39(typescript@5.2.2)
'@types/jquery': '@types/jquery':
specifier: ^3.5.19 specifier: ^3.5.19
version: 3.5.19 version: 3.5.19
@ -32,8 +35,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
dev: true dev: true
/@danielx/civet@0.6.38(typescript@5.2.2): /@danielx/civet@0.6.39(typescript@5.2.2):
resolution: {integrity: sha512-R63YGIfvV4DQianNPUfMfBX60ozlv5htnRXI1wK3Pg6+d4NZ2V3RifFRH0NkmXXoFkRcULzJ+9BzVeI2/yX+yA==} resolution: {integrity: sha512-rePpUAVruQAY5QgYp3gWlNoGSnua0TBu1PkhXZtFjbixrvdGiu0umEZ2eeV1DJ6wFpYGxzYMhqq0Ca9sY3jIUw==}
engines: {node: '>=19 || ^18.6.0 || ^16.17.0'} engines: {node: '>=19 || ^18.6.0 || ^16.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -168,6 +171,10 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true dev: true
/colorsea@1.2.1:
resolution: {integrity: sha512-EkYJGtJ7GcXe/aIkeAZDO/4KaKA8yaxdsXXBNR2MHsEZ6JIzBbAzKc3mH2D8VqGqMciqIpJhCZI1QI7nJQ2aDg==}
dev: false
/corser@2.0.1: /corser@2.0.1:
resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==} resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}

View File

@ -1,5 +1,6 @@
import https://code.jquery.com/jquery-3.7.1.js import https://code.jquery.com/jquery-3.7.1.js
import type {AppletObject} from ./deps/geogebra/api.ts import type {AppletObject} from ./deps/geogebra/api.ts
colorsea from ./deps/colorsea.js
type AppletDescription type AppletDescription
html: string html: string
@ -44,13 +45,17 @@ jQuery.getScript 'https://www.geogebra.org/apps/deployggb.js', =>
for each jApp of joyceApplets for each jApp of joyceApplets
params := { params := {
appName: 'classic', appName: 'classic',
-enableRightClick,
// +showMenuBar,
jApp.width, jApp.width,
jApp.height, jApp.height,
appletOnLoad: (api: AppletObject) => appletOnLoad: (api: AppletObject) =>
elements: JoyceElements := {} elements: JoyceElements := {}
for child of jApp.children for child of jApp.children
dispatchJcommand api, child, elements dispatchJcommand api, child, elements
api.setCoordSystem(-10, 10 + jApp.width, -10, 10 + jApp.height) api.setCoordSystem -10, 10 + jApp.width, -10, 10 + jApp.height
api.setAxesVisible false, false
api.setGridVisible false
} as const } as const
geoApp := new GGBApplet params geoApp := new GGBApplet params
geoApp.inject jApp.id geoApp.inject jApp.id
@ -76,9 +81,10 @@ function freshCommander(): Commander
type JoyceArguments = type JoyceArguments =
Partial<Record<JoyceClass|'subpoints', GeoName[]> & {scalar: number[]}> Partial<Record<JoyceClass|'subpoints', GeoName[]> & {scalar: number[]}>
type ClassHandler = ( type ClassHandler = (
name: GeoName, m: string, args: JoyceArguments, index: number) => Commander 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 // Executes the command corresponding to param against the GeoGebra applet
// api, consulting and extending by side effect the elements that are // api, consulting and extending by side effect the elements that are
@ -88,15 +94,17 @@ function dispatchJcommand(
val := param.getAttribute 'value' val := param.getAttribute 'value'
unless val return unless val return
attr := param.getAttribute 'name' attr := param.getAttribute 'name'
backgroundHex .= '#FFFFFF'
switch attr switch attr
'background' 'background'
api.setGraphicsOptions 1, bgColor: `#${val}` backgroundHex = `#${val}`
api.setGraphicsOptions 1, bgColor: backgroundHex
'title' 'title'
api.evalCommand `TitlePoint = Corner(1,1) api.evalCommand `TitlePoint = Corner(1,1)
Text("${val}", TitlePoint + (2,5))` Text("${val}", TitlePoint + (2,5))`
/e\[\d+\]/ /e\[\d+\]/
num := parseInt(attr.slice(2)) num := parseInt(attr.slice(2))
{commands, callbacks, parts} := jToG val, elements, num {commands, callbacks, parts} := jToG val, elements, num, backgroundHex
if commands.length if commands.length
lastTried .= 0 lastTried .= 0
if commands.filter((&)).every (cmd) => if commands.filter((&)).every (cmd) =>
@ -117,8 +125,12 @@ function dispatchJcommand(
// Parses a Joyce element-creating command, extending the elements // Parses a Joyce element-creating command, extending the elements
// by side effect: // by side effect:
function jToG(jCom: string, elements: JoyceElements, index: number): Commander function jToG(
[jname, klass, method, data, ...colors] := jCom.split(';') jCom: string,
elements: JoyceElements,
index: number,
backgroundHex: string): Commander
[jname, klass, method, data, ...colors] := jCom.split ';'
cmdr .= freshCommander() cmdr .= freshCommander()
unless klass in classHandler unless klass in classHandler
console.log `Unknown entity class ${klass}` console.log `Unknown entity class ${klass}`
@ -144,32 +156,162 @@ function jToG(jCom: string, elements: JoyceElements, index: number): Commander
(args.subpoints ?= []).push ...ends ?? [] (args.subpoints ?= []).push ...ends ?? []
cmdr = classHandler[klass] name, method, args, index cmdr = classHandler[klass] name, method, args, index
unless name is jname then cmdr.callbacks.push (api: AppletObject) => unless name is jname then cmdr.callbacks.push (api: AppletObject) =>
api.setCaption(name, jname) api.setCaption name, jname
api.setLabelStyle(name, 3) // style CAPTION = 3 api.setLabelStyle name, 3 // style CAPTION = 3
if cmdr.auxiliaries.length if cmdr.auxiliaries.length
cmdr.callbacks.push (api: AppletObject) => cmdr.callbacks.push (api: AppletObject) =>
for each aux of cmdr.auxiliaries for each aux of cmdr.auxiliaries
api.setAuxiliary(aux, true) api.setAuxiliary aux, true
api.setVisible(aux,false) api.setVisible aux,false
// Create callback to assign colors // Create callback to assign colors
if colors.length is 4 and colors.every (color) => if colors.length is 4 and colors.every (color) => invisible color
false and color is '0' or color is 'none' cmdr.callbacks.push (api: AppletObject) => api.setVisible name, false
cmdr.callbacks.push (api: AppletObject) => else // we have to decorate
api.setVisible(name, false) dimension .= cmdr.parts.findLastIndex .includes name
cmdr.callbacks.push (api: AppletObject, parts: DimParts) =>
trace := 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', backgroundHex)
for each face of parts[2]
console.log 'Coloring face', face, 'to', colors[3] if trace
api.setVisible face, true
api.setFilling face, 0.3
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
black: RGB := [0, 0, 0]
lineRGB := colors[2] ? joyce2rgb colors[2], backgroundHex : black
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], backgroundHex
: 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], backgroundHex
// 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) => // window[hideListener] = (arg) =>
// console.log('Hello', arg, 'disappearing', name) // console.log('Hello', arg, 'disappearing', name)
// api.setVisible(name, false) // api.setVisible name, false
api.registerObjectUpdateListener(name, hideListener) // api.registerObjectUpdateListener name, hideListener
if cmdr.ends or klass is 'line' if cmdr.ends // line or sector
elements[jname] = elements[jname] =
{otherName: name, usesCaptions, klass: 'line', cmdr.ends} {otherName: name, usesCaptions, klass, cmdr.ends}
elements[name] = elements[name] =
{otherName: jname, usesCaptions, klass: 'line', cmdr.ends} {otherName: jname, usesCaptions, klass, cmdr.ends}
else // any other geometry else // any other geometry
elements[jname] = {otherName: name, usesCaptions, klass} elements[jname] = {otherName: name, usesCaptions, klass}
elements[name] = {otherName: jname, usesCaptions, klass} elements[name] = {otherName: jname, usesCaptions, klass}
cmdr cmdr
function invisible(cname: string): boolean
cname is '0' or cname is 'none'
function joyce2rgb(cname: string, backgroundHex: string): RGB
switch cname
/black/i
[0,0,0]
/cyan/i
[0,255,255]
/lightgray/i
[211,211,211]
/pink/i
[255,192,203]
/red/i
[255,0,0]
/brighter/i
colorsea(backgroundHex).lighten(20).rgb()
/darker/i
colorsea(backgroundHex).darken(20).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'
[255, 0, 0]
/.*[Ss]lider$/
[255, 165, 0]
else [0,0,0]
function geoname(jname: JoyceName, elements: JoyceElements): GeoName function geoname(jname: JoyceName, elements: JoyceElements): GeoName
numCode .= 0n numCode .= 0n
numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname numCode = numCode*128n + BigInt ch.codePointAt(0) ?? 1 for each ch of jname
@ -188,7 +330,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
/free|fixed/ /free|fixed/
commands.push `${name} = (${args.scalar?.join ','})` commands.push `${name} = (${args.scalar?.join ','})`
if method is 'fixed' if method is 'fixed'
callbacks.push (api: AppletObject) => api.setFixed(name, true) callbacks.push (api: AppletObject) => api.setFixed name, true
'perpendicular' 'perpendicular'
// Note only the two-point option implemented so far // Note only the two-point option implemented so far
unless args.subpoints return unless args.subpoints return
@ -230,8 +372,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
commands.push `${name} = Point(${segment})` commands.push `${name} = Point(${segment})`
if args.scalar and args.scalar.length if args.scalar and args.scalar.length
callbacks.push (api: AppletObject) => callbacks.push (api: AppletObject) =>
api.setCoords name, api.setCoords name, ...args.scalar as XYZ
...args.scalar as [number, number, number]
'first' 'first'
unless args.subpoints then return unless args.subpoints then return
commands.push `${name} = ${args.subpoints[0]}` commands.push `${name} = ${args.subpoints[0]}`
@ -310,7 +451,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
unless madeSegment unless madeSegment
commands.push `${name} = Segment(${ends[0]},${ends[1]})` commands.push `${name} = Segment(${ends[0]},${ends[1]})`
callbacks.push (api: AppletObject) => callbacks.push (api: AppletObject) =>
api.setLabelVisible(name, true) api.setLabelVisible name, true
parts[0].push ...ends parts[0].push ...ends
circle: (name, method, args) => circle: (name, method, args) =>
@ -323,7 +464,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
unless args.subpoints then return unless args.subpoints then return
[center, point] := args.subpoints [center, point] := args.subpoints
commands.push `${name} = Circle(${center}, ${point})` 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) => polygon: (name, method, args, index) =>
return := freshCommander() return := freshCommander()
@ -349,7 +490,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
parts[1].push newObj parts[1].push newObj
'point' 'point'
parts[0].push newObj parts[0].push newObj
api.setVisible(newObj, false) api.setVisible newObj, false
/triangle|quadrilateral/ /triangle|quadrilateral/
pt := args.subpoints pt := args.subpoints
unless pt then return unless pt then return

View File

@ -1,3 +1,6 @@
# Takes one parameter, the destination directory # Takes one parameter, the destination directory
mkdir -p $1 mkdir -p $1
cp -rL node_modules/vrml1to97/{deps,vrml1to97,streamToString.js} $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