From fb2137306f6f8057fa684b9fcece2f23d65fb548 Mon Sep 17 00:00:00 2001 From: Songchen Tan Date: Mon, 15 Jan 2024 11:06:07 +0800 Subject: [PATCH] Finish e2e test --- spec/degenerator.spec.ts | 5 +--- spec/mock.ts | 14 ++++++---- spec/selector.spec.ts | 14 ++++++++-- spec/topology.spec.ts | 2 +- src/components/CharacterQuery.tsx | 2 +- src/lib/affine.ts | 24 ++++++++++++---- src/lib/component.ts | 28 +++++++++++-------- src/lib/config.ts | 2 -- src/lib/degenerator.ts | 5 ++-- src/lib/element.ts | 4 +-- src/lib/repertoire.ts | 36 +++++++++++++----------- src/lib/selector.ts | 11 ++++---- src/lib/templates.ts | 16 +++++------ src/pages/[id]/assembly.tsx | 35 +++++++++++------------ src/pages/[id]/index.tsx | 46 +++++++++++-------------------- 15 files changed, 128 insertions(+), 116 deletions(-) diff --git a/spec/degenerator.spec.ts b/spec/degenerator.spec.ts index 3be749b..ab800a9 100644 --- a/spec/degenerator.spec.ts +++ b/spec/degenerator.spec.ts @@ -6,11 +6,8 @@ import _degenerate, { } from "~/lib/degenerator"; import { describe, it, expect } from "vitest"; import { create, all } from "mathjs"; -import type { SVGGlyph } from "~/lib/data"; -import { computedGlyphs, rendered } from "./mock"; +import { computedGlyphs2 as computedGlyphs } from "./mock"; import { RenderedGlyph } from "~/lib/topology"; -import { defaultKeyboard } from "~/lib/templates"; -import { computeComponent } from "~/lib/component"; const { randomInt } = create(all, { randomSeed: "a", diff --git a/spec/mock.ts b/spec/mock.ts index 315ede3..c8e5caf 100644 --- a/spec/mock.ts +++ b/spec/mock.ts @@ -8,13 +8,17 @@ import type { import { determine } from "~/lib/repertoire"; import { computeComponent } from "~/lib/component"; -export const repertoire: PrimitiveRepertoire = listToObject(rawrepertoire); -export const rendered = determine(repertoire); +export const repertoire = determine(listToObject(rawrepertoire)); export const computedGlyphs = Object.fromEntries( - Object.entries(rendered) - .filter(([k, v]) => v.glyph?.type === "component") + Object.entries(repertoire) + .filter(([k, v]) => v.glyph?.type === "basic_component") .map(([k, v]) => { const glyph = (v.glyph as BasicComponent).strokes; - return [k, computeComponent(k, glyph).glyph]; + return [k, computeComponent(k, glyph)]; }), ); +export const computedGlyphs2 = Object.fromEntries( + Object.entries(computedGlyphs).map(([k, v]) => { + return [k, v.glyph]; + }), +); diff --git a/spec/selector.spec.ts b/spec/selector.spec.ts index 48bfc8d..707f8cb 100644 --- a/spec/selector.spec.ts +++ b/spec/selector.spec.ts @@ -8,12 +8,20 @@ import { Scheme, } from "~/lib/selector"; import select from "~/lib/selector"; -import { rendered } from "./mock"; +import { computedGlyphs, repertoire } from "./mock"; import { defaultKeyboard } from "~/lib/templates"; +import { Config } from "~/lib/config"; -const config = defaultKeyboard; +const config: Config = { + source: null, + form: defaultKeyboard, + encoder: { + sources: {}, + conditions: {}, + }, +}; -const { 天 } = rendered; +const { 天 } = computedGlyphs; const rootMap = new Map(); diff --git a/spec/topology.spec.ts b/spec/topology.spec.ts index b8f2c31..9ddb57a 100644 --- a/spec/topology.spec.ts +++ b/spec/topology.spec.ts @@ -3,7 +3,7 @@ import type { StrokeRelation } from "~/lib/topology"; import findTopology, { curveRelation, renderSVGGlyph } from "~/lib/topology"; import { CubicCurve, LinearCurve, area, render } from "~/lib/bezier"; import type { Draw, Point } from "~/lib/data"; -import { computedGlyphs } from "./mock"; +import { computedGlyphs2 as computedGlyphs } from "./mock"; import { getIntervalPosition, makeCurve } from "~/lib/bezier"; describe("interval position", () => { diff --git a/src/components/CharacterQuery.tsx b/src/components/CharacterQuery.tsx index 293a210..af2d06f 100644 --- a/src/components/CharacterQuery.tsx +++ b/src/components/CharacterQuery.tsx @@ -80,7 +80,7 @@ export default function ({ setFilter }: StrokeSearchProps) { const tags = useAtomValue(tagsAtom); return ( - onValuesChange={async (values) => setFilter(values)} + onValuesChange={async (_, values) => setFilter(values)} labelWidth="auto" submitter={false} style={{ maxWidth: 1080 }} diff --git a/src/lib/affine.ts b/src/lib/affine.ts index 4432f18..e488245 100644 --- a/src/lib/affine.ts +++ b/src/lib/affine.ts @@ -1,5 +1,5 @@ import { add } from "mathjs"; -import { Draw, Operator, Point, SVGGlyph, SVGStroke } from "./data"; +import { Compound, Draw, Operator, Point, SVGGlyph, SVGStroke } from "./data"; import { deepcopy } from "./utils"; class Affine { @@ -79,10 +79,24 @@ const affineMap: Record = { "⿻": [id, id], }; -export function affineMerge(operator: Operator, glyphList: SVGGlyph[]) { - const result: SVGGlyph[] = []; +export function affineMerge(compound: Compound, glyphList: SVGGlyph[]) { + const { operator, order } = compound; + const transformedGlyphs: SVGGlyph[] = []; for (const [index, affine] of affineMap[operator].entries()) { - result.push(affine.transformSVGGlyph(glyphList[index]!)); + const transformed = affine.transformSVGGlyph(glyphList[index]!); + transformedGlyphs.push(transformed); } - return result.flat(); + if (order === undefined) return transformedGlyphs.flat(); + const result: SVGGlyph = []; + for (const { index, strokes } of order) { + const glyph = transformedGlyphs[index]; + if (glyph === undefined) continue; + if (strokes === 0) { + result.push(...glyph); + } else { + result.push(...glyph.slice(0, strokes)); + transformedGlyphs[index] = glyph.slice(strokes); + } + } + return result; } diff --git a/src/lib/component.ts b/src/lib/component.ts index 2f977b3..f281ef0 100644 --- a/src/lib/component.ts +++ b/src/lib/component.ts @@ -1,4 +1,4 @@ -import type { KeyboardConfig, SieveName } from "./config"; +import type { Config, KeyboardConfig, SieveName } from "./config"; import type { DerivedComponent, Compound, @@ -15,7 +15,6 @@ import type { RenderedGlyph, Topology } from "./topology"; import findTopology, { renderSVGGlyph } from "./topology"; import type { Classifier } from "./classifier"; import { isValidCJKChar, isValidChar } from "./utils"; -import defaultClassifier from "./classifier"; import { affineMerge } from "./affine"; class InvalidGlyphError extends Error {} @@ -54,10 +53,10 @@ export class MultipleSchemeError extends Error {} const getComponentScheme = function ( component: ComputedComponent, rootData: ComputedComponent[], - config: KeyboardConfig, + config: Config, classifier: Classifier, ): ComponentResult | NoSchemeError | MultipleSchemeError { - const { mapping } = config; + const { mapping } = config.form; if (mapping[component.name]) return { sequence: [component.name], @@ -176,7 +175,7 @@ export const recursiveRenderCompound = function ( glyphCache.set(char, rendered); } } - return affineMerge(compound.operator, glyphs); + return affineMerge(compound, glyphs); }; export const computeComponent = (name: string, glyph: SVGGlyph) => { @@ -190,17 +189,22 @@ export const computeComponent = (name: string, glyph: SVGGlyph) => { return cache; }; -export const renderRootList = (data: Repertoire, config: KeyboardConfig) => { - const { mapping, grouping } = config; - const glyphCache = new Map(); +export const renderRootList = (repertoire: Repertoire, config: Config) => { + const { mapping, grouping } = config.form; const roots = [...Object.keys(mapping), ...Object.keys(grouping)].filter( - (x) => data[x] !== undefined, + (x) => repertoire[x] !== undefined, ); const rootList: ComputedComponent[] = []; for (const root of roots) { - const glyph = data[root]?.glyph; - if (glyph?.type === "basic_component") { + const glyph = repertoire[root]?.glyph; + if (glyph === undefined) continue; + if (glyph.type === "basic_component") { rootList.push(computeComponent(root, glyph.strokes)); + } else { + const rendered = recursiveRenderCompound(glyph, repertoire); + if (rendered instanceof Error) continue; + const cache = computeComponent(root, rendered); + rootList.push(cache); } } return rootList; @@ -208,7 +212,7 @@ export const renderRootList = (data: Repertoire, config: KeyboardConfig) => { export const disassembleComponents = function ( data: Repertoire, - config: KeyboardConfig, + config: Config, classifier: Classifier, ): [ComponentCache, string[]] { const rootList = renderRootList(data, config); diff --git a/src/lib/config.ts b/src/lib/config.ts index 052a848..0262b4a 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -24,8 +24,6 @@ export type SieveName = export type Selector = SieveName[]; -export type PartialClassifier = Partial; - export type Element = string; export type Key = string | { element: string; index: number }; diff --git a/src/lib/degenerator.ts b/src/lib/degenerator.ts index c8453b5..5699505 100644 --- a/src/lib/degenerator.ts +++ b/src/lib/degenerator.ts @@ -7,7 +7,7 @@ import { isCollinear, sortTwoNumbers, } from "./bezier"; -import { Degenerator, FormConfig } from "./config"; +import { Config, Degenerator, KeyboardConfig } from "./config"; import { Feature } from "./classifier"; import { ComputedComponent } from "./component"; @@ -26,6 +26,7 @@ export const binaryToIndices = (n: number) => (binary: number) => { export const defaultDegenerator: Degenerator = { feature: { + 提: "横", 捺: "点", } as Record, no_cross: false, @@ -89,7 +90,7 @@ const verifySpecialRoots = ( }; export const generateSliceBinaries = ( - config: FormConfig, + config: Config, component: ComputedComponent, root: ComputedComponent, ) => { diff --git a/src/lib/element.ts b/src/lib/element.ts index cf48e2d..cf3cbc3 100644 --- a/src/lib/element.ts +++ b/src/lib/element.ts @@ -2,7 +2,7 @@ import type { Config, Rule } from "./config"; import type { TotalResult } from "./encoder"; export interface Extra { - rootSequence: Record; + rootSequence: Map; } interface Base { @@ -214,7 +214,7 @@ export const findElement = ( case "二笔": { root = getindex(sequence, object.rootIndex); if (root === undefined) return undefined; - strokes = extra.rootSequence[root]; + strokes = extra.rootSequence.get(root); if (strokes === undefined) { if (Math.abs(object.strokeIndex) === 1) return root; return undefined; diff --git a/src/lib/repertoire.ts b/src/lib/repertoire.ts index 6930e7f..cdaae54 100644 --- a/src/lib/repertoire.ts +++ b/src/lib/repertoire.ts @@ -4,6 +4,7 @@ import { ComponentResult, disassembleComponents, recursiveRenderComponent, + recursiveRenderCompound, } from "./component"; import { disassembleCompounds } from "./compound"; import { Config, CustomGlyph, KeyboardConfig } from "./config"; @@ -63,7 +64,7 @@ export const determine = ( export const getAnalysisCore = (data: Repertoire, config: Config) => { const [componentCache, componentError] = disassembleComponents( data, - config.form, + config, mergeClassifier(config.analysis?.classifier), ); const customizations: ComponentCache = new Map( @@ -90,27 +91,30 @@ export const getAnalysisCore = (data: Repertoire, config: Config) => { }; }; -const getExtra = function (data: Repertoire, config: Config): Extra { +const getExtra = function (repertoire: Repertoire, config: Config): Extra { const { mapping, grouping } = config.form; - const roots = Object.keys(mapping).concat(Object.keys(grouping)); + const classifier = mergeClassifier(config.analysis?.classifier); const findSequence = (x: string) => { - if (data[x] === undefined) { - // 单笔画 - return [Number(x)]; + if (x.match(/[0-9]+/)) { + return [...x].map(Number); } - try { - const sequence = [1]; - if (sequence instanceof Error) { - return []; - } - return sequence; - } catch { + const glyph = repertoire[x]?.glyph; + if (glyph === undefined) { return []; } + if (glyph.type === "basic_component") { + return glyph.strokes.map((s) => classifier[s.feature]); + } else { + const sequence = recursiveRenderCompound(glyph, repertoire); + if (sequence instanceof Error) return []; + return sequence.map((s) => classifier[s.feature]); + } }; - const rootSequence = Object.fromEntries( - roots.map((x) => [x, findSequence(x)]), - ); + const rootSequence = new Map(); + const roots = Object.keys(mapping).concat(Object.keys(grouping)); + for (const root of roots) { + rootSequence.set(root, findSequence(root)); + } return { rootSequence, }; diff --git a/src/lib/selector.ts b/src/lib/selector.ts index c4dc063..185c3ea 100644 --- a/src/lib/selector.ts +++ b/src/lib/selector.ts @@ -3,13 +3,14 @@ import { type ComputedComponent, NoSchemeError, } from "./component"; -import type { FormConfig, SieveName } from "./config"; +import type { Config, SieveName } from "./config"; import { binaryToIndices } from "./degenerator"; import { type CurveRelation } from "./topology"; import { isEqual } from "lodash-es"; import { sortTwoNumbers } from "./bezier"; export const defaultSelector: SieveName[] = [ + "结构完整", "根少优先", "能连不交", "能散不连", @@ -26,7 +27,7 @@ interface Sieve { key: ( scheme: Scheme, component: ComputedComponent, - config: FormConfig, + config: Config, rootMap: Map, ) => T; display?: (data: T) => string; @@ -121,7 +122,7 @@ export const similar: Sieve = { title: "非形近根", key: (scheme, _, config, rootMap) => { const roots = scheme.map((x) => rootMap.get(x)!); - return roots.filter((x) => config.grouping[x] !== undefined).length; + return roots.filter((x) => config.form.grouping[x] !== undefined).length; }, }; @@ -267,10 +268,8 @@ export const sieveMap = new Map | Sieve>( ].map((x) => [x.title, x]), ); -type Evaluation = Map; - const select = ( - config: FormConfig, + config: Config, component: ComputedComponent, schemeList: Scheme[], rootMap: Map, diff --git a/src/lib/templates.ts b/src/lib/templates.ts index 71fd86e..16d7cc2 100644 --- a/src/lib/templates.ts +++ b/src/lib/templates.ts @@ -1,9 +1,5 @@ -import type { - Analysis, - Config, - KeyboardConfig, - PartialClassifier, -} from "./config"; +import { Classifier } from "./classifier"; +import type { Analysis, Config, KeyboardConfig } from "./config"; import { defaultDegenerator } from "./degenerator"; import { examples } from "./example"; import { defaultSelector } from "./selector"; @@ -23,8 +19,8 @@ export const classifierTypes = [ "郑码七分类", ] as const; export type ClassifierType = (typeof classifierTypes)[number]; -const classifierMap: Record = { - 国标五分类: {}, +const classifierMap: Record = { + 国标五分类: {} as Classifier, 表形码六分类: examples.mswb.analysis!.classifier!, 郑码七分类: examples.zhengma.analysis!.classifier!, }; @@ -72,6 +68,10 @@ export const createConfig = function (starter: StarterType): Config { version: APP_VERSION, source: null, info: getInfo(starter.name), + analysis: { + ...defaultAnalysis, + classifier: classifierMap[starter.data]!, + }, form: keyboardMap[starter.keyboard], encoder: encoderMap[starter.encoder], }; diff --git a/src/pages/[id]/assembly.tsx b/src/pages/[id]/assembly.tsx index 81a259d..7e21059 100644 --- a/src/pages/[id]/assembly.tsx +++ b/src/pages/[id]/assembly.tsx @@ -15,20 +15,10 @@ import { configAtom, displayAtom, determinedRepertoireAtom, + sequenceAtom, } from "~/atoms"; - -import type { - CharsetFilter, - EncoderResult, - IndexedElement, -} from "~/lib/encoder"; -import encode, { - autoSplit, - collect, - filtermap, - filtervalues, - uniquify, -} from "~/lib/encoder"; +import type { EncoderResult, IndexedElement } from "~/lib/encoder"; +import encode, { autoSplit, collect, uniquify } from "~/lib/encoder"; import type { ColumnsType } from "antd/es/table"; import Table from "antd/es/table"; import { @@ -44,6 +34,10 @@ import { ReactFlowProvider } from "reactflow"; import { useChaifenTitle } from "~/lib/hooks"; import type { ColumnType } from "antd/es/table/interface"; import ElementSelect from "~/components/ElementSelect"; +import CharacterQuery, { + CharacterFilter, + makeCharacterFilter, +} from "~/components/CharacterQuery"; interface EncodeResultTable { char: string; @@ -58,13 +52,14 @@ type ElementFilter = { const Encoder = () => { useChaifenTitle("编码"); - const data = useAtomValue(determinedRepertoireAtom); + const repertoire = useAtomValue(determinedRepertoireAtom); + const sequence = useAtomValue(sequenceAtom); const config = useAtomValue(configAtom); const display = useAtomValue(displayAtom); - const [gb2312, setGB2312] = useState("未定义"); - const [tygf, setTYGF] = useState("未定义"); const [result, setResult] = useState(new Map()); - const list = Object.entries(data) + const [filter, setFilter] = useState({}); + const filterFn = makeCharacterFilter(filter, repertoire, sequence); + const list = Object.entries(repertoire) .filter(([, v]) => v.gb2312 && v.tygf > 0) .map(([x]) => x); @@ -92,6 +87,7 @@ const Encoder = () => { let dataSource = [...result] .filter(([, v]) => v.code.length > 0) + .filter(([x]) => filterFn(x)) .map(([char, code]) => { return { key: char, @@ -252,6 +248,7 @@ const Encoder = () => { closable /> ) : null} + {/* 字集过滤 @@ -281,7 +278,7 @@ const Encoder = () => { - - + + + + + ); }