flutter 自定义插件 手写识别字母 MyScript S

2023-10-06  本文已影响0人  卢融霜
WechatIMG224.jpg

dart端关键代码:

part of myscript;

class MyScriptWidget extends StatefulWidget {
  final int width;
  final int height;
  final String bgColor;
  final String penColor;

  const MyScriptWidget(
      {required this.width,
      required this.height,
      super.key,
      this.bgColor = "#000000",
      this.penColor = "#FFFFFF"});

  @override
  State<MyScriptWidget> createState() => _MyScriptWidgetState();
}

const String viewType = "com.example.myscriptwidget.view";

class _MyScriptWidgetState extends State<MyScriptWidget> {
  @override
  Widget build(BuildContext context) {
    return getScriptWidget();
  }

  Widget getScriptWidget() {
    Widget myScriptWidget = const Placeholder();
    ViewParams viewParams = ViewParams(
        width: widget.width,
        height: widget.height,
        bgColor: widget.bgColor,
        penColor: widget.penColor);

    if (Platform.isAndroid) {
      myScriptWidget = AndroidView(
        viewType: viewType,
        creationParams: viewParams.toJson(),
        hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        creationParamsCodec: const StandardMessageCodec(),
      );
    } else if (Platform.isIOS) {
      myScriptWidget = UiKitView(
          viewType: viewType,
          creationParams: viewParams.toJson(),
          hitTestBehavior: PlatformViewHitTestBehavior.opaque,
          creationParamsCodec: const StandardMessageCodec());
    }

    return Container(
        width: widget.width.toDouble(),
        height: widget.height.toDouble(),
        child: myScriptWidget);
  }
}

class ViewParams {
  int width;
  int height;
  String bgColor;
  String penColor;

  ViewParams(
      {required this.width,
      required this.height,
      required this.bgColor,
      required this.penColor});

  Map<String, dynamic> toJson() => <String, dynamic>{
        "width": width,
        "height": height,
        "bgColor": bgColor,
        "penColor": penColor
      };
}

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'my_script_widget_platform_interface.dart';

/// An implementation of [MyscriptwidgetPlatform] that uses method channels.
class MethodChannelMyScriptWidget extends MyScriptWidgetPlatform {
  /// The method channel used to interact with the native platform.
  @visibleForTesting
  final methodChannel = const MethodChannel(
      'com.example.myScriptWidget_example.myScriptWidgetMethodChannel');

  @override
  Future<String?> getPlatformVersion() async {
    final version =
        await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }

  @override
  Future<String?> clear() {
    return methodChannel.invokeMethod("clear");
  }

  @override
  Future<String?> initMyScriptWidget(
      List<int> myCertificate, String configPath) async {
    final res = await methodChannel.invokeMethod(
        "initMyScriptWidget", <String, dynamic>{
      'myCertificate': myCertificate,
      "configPath": configPath
    });
    return res;
  }

  @override
  Future<String?> getChannel() async {
    final res = await methodChannel.invokeMethod("getChannel");
    return res;
  }
}

part of myscript;

class MyScriptWidgetPlugin {
  static const EventChannel eventChannel = EventChannel(
      "com.example.myScriptWidget_example.myScriptWidgetEventChannel");

  static Future<String?> getPlatformVersion() {
    return MyScriptWidgetPlatform.instance.getPlatformVersion();
  }

  static Stream<dynamic> getBroadcastStream() {
    return eventChannel
        .receiveBroadcastStream()
        .map((event) => event.toString())
        .asBroadcastStream();
  }

  static void clear() {
    MyScriptWidgetPlatform.instance.clear();
  }

  static Future<String?> initMyScriptWidget(
      {required List<int> myCertificate, required String configPath}) {
    return MyScriptWidgetPlatform.instance
        .initMyScriptWidget(myCertificate, configPath);
  }

  static Future<String?> getChannel() {
    return MyScriptWidgetPlatform.instance.getChannel();
  }
}

Android端关键代码:

package com.example.myscriptwidget;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import com.example.myscriptwidget.engine.MyScriptEngine;
import com.example.myscriptwidget.view.MyScriptViewFactory;
import com.example.myscriptwidget.view.TextUpdate;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

/**
 * MyscriptwidgetPlugin
 */
public class MyscriptwidgetPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, TextUpdate {
    private MethodChannel channel;

    private EventChannel eventChannel;

    private EventChannel.EventSink eventSink;

    private FlutterPluginBinding flutterPluginBinding;

    private MyScriptViewFactory scriptViewFactory;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "com.example.myScriptWidget_example.myScriptWidgetMethodChannel");
        channel.setMethodCallHandler(this);
        eventChannel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), "com.example.myScriptWidget_example.myScriptWidgetEventChannel");
        eventChannel.setStreamHandler(this);

        scriptViewFactory = new MyScriptViewFactory(flutterPluginBinding.getBinaryMessenger(), this);
        //注册组件
        flutterPluginBinding
                .getPlatformViewRegistry()
                .registerViewFactory(
                        MyScriptViewFactory.viewTypeId,
                        scriptViewFactory);
        this.flutterPluginBinding = flutterPluginBinding;

    }


    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("getPlatformVersion")) {
            result.success("Android " + Build.VERSION.RELEASE);
        } else if (call.method.equals("clear")) {
            scriptViewFactory.clear();
            result.success("success");
        } else if (call.method.equals("initMyScriptWidget")) {
            try {
                MyScriptEngine.configPath = call.argument("configPath");
                List<Integer> myArray = call.argument("myCertificate");
                List<Byte> bytes = new ArrayList<>();
                for (int i = 0; i < myArray.size(); i++) {
                    byte b = Byte.valueOf(myArray.get(i).toString());
                    bytes.add(b);
                }
                MyScriptEngine.myCertificate = listTobyte(bytes);
                MyScriptEngine.initEngine();
                result.success("success");
            } catch (Exception e) {
                Log.e("MyscriptwidgetPlugin", e.toString());
                result.error("-1", e.getMessage(), e.fillInStackTrace());
            }
        } else if (call.method.equals("getChannel")) {
            try {
                ApplicationInfo appInfo = flutterPluginBinding.getApplicationContext().getPackageManager().getApplicationInfo(
                        flutterPluginBinding.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
                String channel = appInfo.metaData.getString("CHANNEL_NAME");
                if (channel == null) {
                    channel = "default";
                }
                result.success(channel);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
                result.error(String.valueOf(-1), e.getMessage(), e.fillInStackTrace());
            }

        } else {
            result.notImplemented();
        }
    }

    private byte[] listTobyte(List<Byte> list) {
        if (list == null || list.size() < 0)
            return null;
        byte[] bytes = new byte[list.size()];
        int i = 0;
        Iterator<Byte> iterator = list.iterator();
        while (iterator.hasNext()) {
            bytes[i] = iterator.next();
            i++;
        }
        return bytes;
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
    }

    @Override
    public void onListen(Object arguments, EventChannel.EventSink events) {
        eventSink = events;
    }

    @Override
    public void onCancel(Object arguments) {
        eventSink = null;
    }

    @Override
    public void onEvent(String text) {
        if (eventSink != null) {
            new Handler(Looper.getMainLooper()).post(() -> {
                eventSink.success(text);
            });
        }
    }
}

package com.example.myscriptwidget.engine;

import static io.flutter.util.PathUtils.getCacheDirectory;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.example.myscriptwidget.R;
import com.example.myscriptwidget.bean.EditorViewBean;
import com.myscript.iink.Configuration;
import com.myscript.iink.ContentPackage;
import com.myscript.iink.ContentPart;
import com.myscript.iink.Editor;
import com.myscript.iink.Engine;
import com.myscript.iink.ParameterSet;
import com.myscript.iink.PointerTool;
import com.myscript.iink.Renderer;
import com.myscript.iink.uireferenceimplementation.EditorBinding;
import com.myscript.iink.uireferenceimplementation.EditorData;
import com.myscript.iink.uireferenceimplementation.EditorView;
import com.myscript.iink.uireferenceimplementation.FontUtils;
import com.myscript.iink.uireferenceimplementation.InputController;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


public class MyScriptEngine {
    private static final String TAG = "MyScriptEngine";

    private static Engine engine;

    private static final Map<Integer, EditorViewBean> viewMap = new HashMap<>();

    public static byte[] myCertificate = {};
    public static String configPath = "";

    public static EditorViewBean getEditorView(Context applicationContext, int viewId, Map<String, Object> args) {
        initEditorView(applicationContext, viewId, args);
        return viewMap.get(viewId);
    }


    public static Engine getEngine() {

        return engine;
    }

    /**
     * 初始化Engine
     */
    public static void initEngine() {
        if (engine == null || engine.isClosed()) {
            initCreateEngine();
        }
    }

    private static synchronized void initCreateEngine() {
//        engine = Engine.create(MyCertificate.getBytes());
        try {
            engine = Engine.create(myCertificate);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }

    }


    public static EditorViewBean getEditorViewBean(int id) {
        return viewMap.get(id);

    }

    private static View initEditorView(Context applicationContext, int viewId, Map<String, Object> args) {
        Editor editor;
        View editorViewInflate;
        EditorView editorView;
        ContentPackage contentPackage = null;
        EditorData editorData;
        ContentPart contentPart = null;
        ParameterSet exportParams;
        FrameLayout flView;

        // configure recognition
        Configuration conf = engine.getConfiguration();
//        String confDir = "zip://" + applicationContext.getPackageCodePath() + "!/assets/conf";
        String confDir = configPath;
        conf.setStringArray("configuration-manager.search-path", new String[]{confDir});
        String tempDir = getCacheDirectory(applicationContext) + File.separator + "tmp";
        conf.setString("content-package.temp-folder", tempDir);

        LayoutInflater inflater = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        editorViewInflate = inflater.inflate(R.layout.activity_main, null);
        editorView = editorViewInflate.findViewById(R.id.editor_view);
        flView = editorViewInflate.findViewById(R.id.flView);
        // load fonts
        AssetManager assetManager = applicationContext.getAssets();
        Map<String, Typeface> typefaceMap = FontUtils.loadFontsFromAssets(assetManager);
        editorView.setTypefaces(typefaceMap);
        EditorBinding editorBinding = new EditorBinding(engine, typefaceMap);
        editorData = editorBinding.openEditor(editorView);
        editor = editorData.getEditor();
        assert editor != null;
        setMargins(editor, applicationContext);

        assert editorData.getInputController() != null;
        editorData.getInputController().setInputMode(InputController.INPUT_MODE_FORCE_PEN);


        String packageName = "File1_" + viewId + ".iink";
        File file = new File(applicationContext.getFilesDir(), packageName);
        try {
            contentPackage = engine.createPackage(file);
            // Choose type of content (possible values are: "Text Document", "Text", "Diagram", "Math", "Drawing" and "Raw Content")
            contentPart = contentPackage.createPart("Text");
        } catch (IOException | IllegalArgumentException e) {
            Log.e(TAG, "Failed to open package \"" + packageName + "\"", e);
        }


        // wait for view size initialization before setting part
        editorView.post(() -> {
            Renderer renderer = editorView.getRenderer();
            if (renderer != null) {
                renderer.setViewOffset(0, 0);
                editorView.getRenderer().setViewScale(1);
                editorView.setVisibility(View.VISIBLE);
                editor.setPart(getEditorViewBean(viewId).contentPart);
            }
        });
        exportParams = engine.createParameterSet();
        exportParams.setBoolean("export.jiix.text.words", true);

        viewMap.put(viewId, new EditorViewBean(editor, editorViewInflate, editorView, contentPackage, editorData, contentPart, exportParams));
        try {
            String bgColor = (String) args.get("bgColor");
            editorView.setBackgroundColor(Color.parseColor(bgColor));
            flView.setBackgroundColor(Color.parseColor(bgColor));
        } catch (Exception e) {
            Log.e(TAG, Objects.requireNonNull(e.getMessage()));
        }
        try {
            String penColor = (String) args.get("penColor");
            editor.getToolController().setToolStyle(PointerTool.PEN, "color: " + penColor + ";");
        } catch (Exception e) {
            Log.e(TAG, Objects.requireNonNull(e.getMessage()));
        }
        return editorViewInflate;
    }


    public static void disposeView() {
        for (Map.Entry<Integer, EditorViewBean> editorViewBeanEntry : viewMap.entrySet()) {
            EditorViewBean editorViewBean = editorViewBeanEntry.getValue();

            EditorView editorView = editorViewBean.editorView;
            if (editorView != null) {
                editorView.setEditor(null);
            }


            ContentPart contentPart = editorViewBean.contentPart;
            if (contentPart != null && !contentPart.isClosed()) {
                contentPart.getPackage().close();
                contentPart.close();
            }

            Editor editor = editorViewBean.editor;
            if (editor != null && !editor.isClosed()) {
                editor.getRenderer().close();
                editor.close();
            }

            ContentPackage contentPackage = editorViewBean.contentPackage;
            if (contentPackage != null && !contentPackage.isClosed()) {
                contentPackage.close();
            }
        }
        viewMap.clear();
//        engine.close();
    }

    private static void setMargins(Editor editor, Context applicationContext) {
        DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics();
        Configuration conf = editor.getConfiguration();
        float verticalMarginPX = 2;
        float verticalMarginMM = 25.4f * verticalMarginPX / displayMetrics.ydpi;
        float horizontalMarginPX = 10;
        float horizontalMarginMM = 25.4f * horizontalMarginPX / displayMetrics.xdpi;
        conf.setNumber("text.margin.top", verticalMarginMM);
        conf.setNumber("text.margin.left", horizontalMarginMM);
        conf.setNumber("text.margin.right", horizontalMarginMM);

        conf.setNumber("math.margin.top", verticalMarginMM);
        conf.setNumber("math.margin.bottom", verticalMarginMM);
        conf.setNumber("math.margin.left", horizontalMarginMM);
        conf.setNumber("math.margin.right", horizontalMarginMM);
        conf.setBoolean("text.guides.enable", false);
    }
}
package com.example.myscriptwidget.view;

import android.content.Context;
import android.os.Build;
import android.view.Gravity;
import android.view.View;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.example.myscriptwidget.R;
import com.example.myscriptwidget.bean.EditorViewBean;
import com.example.myscriptwidget.bean.JSONData;
import com.example.myscriptwidget.engine.MyScriptEngine;
import com.google.gson.Gson;
import com.myscript.iink.ContentBlock;
import com.myscript.iink.ContentPart;
import com.myscript.iink.Editor;
import com.myscript.iink.EditorError;
import com.myscript.iink.IEditorListener;
import com.myscript.iink.MimeType;

import java.io.IOException;
import java.util.Map;

import io.flutter.Log;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.platform.PlatformView;

public class MyScriptView implements PlatformView {
    private final Context context;
    private BinaryMessenger messenger;
    private final int viewId;
    private Object args;

    private View myView;

    private final String TAG = "MyScriptView";

    private TextUpdate textUpdate;

    public MyScriptView(Context context, BinaryMessenger messenger, int viewId, Object args, TextUpdate textUpdate) {
        this.context = context;
        this.messenger = messenger;
        this.viewId = viewId;
        this.args = args;
        this.textUpdate = textUpdate;
    }

    @Nullable
    @Override
    public View getView() {
        ///返回原生视图对象。当Flutter需要显示PlatformView时,会调用此方法来获取原生视图,然后将其嵌入到Flutter界面中。
        if (myView != null) {
            return myView;
        }
        if (MyScriptEngine.getEngine() == null) {
            TextView textView = new TextView(context);
            textView.setText("初始化失败 engine is null");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                textView.setTextColor(context.getColor(R.color.control_gray));
            }
            textView.setGravity(Gravity.CENTER);
            return textView;
        }
        EditorViewBean editorViewBean = MyScriptEngine.getEditorView(context, viewId, (Map<String, Object>) args);
        myView = editorViewBean.editorViewInflate;
        Editor editor = editorViewBean.editor;
        editor.addListener(new IEditorListener() {
            @Override
            public void partChanging(@NonNull Editor editor, ContentPart oldPart, ContentPart newPart) {
                // no-op
                android.util.Log.e("MyScriptView", "partChanging");
            }

            @Override
            public void partChanged(@NonNull Editor editor) {
                android.util.Log.e("MyScriptView", "partChanged");
            }

            @Override
            public void contentChanged(@NonNull Editor editor, @NonNull String[] blockIds) {
                for (String blockId : blockIds) {
                    String label = "";
                    ContentBlock contentBlock = editor.getBlockById(blockId);
                    try {
                        String jiixString = editor.export_(contentBlock, MimeType.JIIX, MyScriptEngine.getEditorViewBean(viewId).exportParams);
                        Gson gson = new Gson();
                        JSONData jsonElement = gson.fromJson(jiixString, JSONData.class);
                        label = jsonElement.getLabel();
                        android.util.Log.w(TAG, label);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    if (textUpdate != null) {
                        textUpdate.onEvent(label);
                    }
                    Log.w("editor", label);
                }


            }

            @Override
            public void onError(@NonNull Editor editor, @NonNull String blockId, @NonNull EditorError error, @NonNull String message) {
                android.util.Log.e(TAG, "Failed to edit block \"" + blockId + "\"" + message);
            }

            @Override
            public void selectionChanged(@NonNull Editor editor) {
                // no-op
                android.util.Log.e("MyScriptView", "selectionChanged");
            }

            @Override
            public void activeBlockChanged(@NonNull Editor editor, @NonNull String blockId) {
                // no-op
                android.util.Log.e("MyScriptView", "activeBlockChanged");
            }
        });

        return myView;
    }

    @Override
    public void onFlutterViewAttached(@NonNull View flutterView) {
        ///当PlatformView被附加到Flutter视图层级时,即将显示在Flutter界面上时,
        // 会调用此方法。您可以在此方法中执行一些初始化操作,或者监听与Flutter视图的交互事件。
        PlatformView.super.onFlutterViewAttached(flutterView);
    }

    @Override
    public void onFlutterViewDetached() {
        //当PlatformView从Flutter视图层级中分离(不再显示)时,
        //会调用此方法。您可以在此方法中执行一些清理操作,停止监听与Flutter视图的交互事件。
        PlatformView.super.onFlutterViewDetached();
    }

    @Override
    public void dispose() {
        ///当PlatformView需要被销毁时,会调用此方法。
        // 您应该在这里释放与PlatformView相关的资源,确保不会出现内存泄漏
        Log.e("MyScriptView", "dispose");
        MyScriptEngine.disposeView();
    }

    @Override
    public void onInputConnectionLocked() {
        //当Flutter视图需要锁定输入连接时,会调用此方法。
        // 这可以发生在需要处理输入事件时,例如文本输入。您可以在此方法中实现处理输入事件所需的逻辑。
        PlatformView.super.onInputConnectionLocked();
    }

    @Override
    public void onInputConnectionUnlocked() {
        ///当Flutter视图不再需要锁定输入连接时,会调用此方法。您可以在此方法中实现停止处理输入事件所需的逻辑。
        //这些方法允许您与Flutter视图进行交互并管理PlatformView的生命周期。您可以根据需要在这些方法中添加逻辑,
        // 以确保PlatformView在Flutter中正确地显示和交互。
        PlatformView.super.onInputConnectionUnlocked();
    }
}

ios端关键代码:

import Flutter
import UIKit
@available(iOS 13.0, *)
public class MyScriptWidgetPlugin: NSObject, FlutterPlugin, FlutterStreamHandler ,TextUpdate{
   
    static var myScriptViewFac : MyScriptViewFactory?;
 @available(iOS 13.0, *)
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "com.example.myScriptWidget_example.myScriptWidgetMethodChannel", binaryMessenger: registrar.messenger())
    let instance = MyScriptWidgetPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
      
    let eventChannel = FlutterEventChannel(name: "com.example.myScriptWidget_example.myScriptWidgetEventChannel", binaryMessenger: registrar.messenger())
     eventChannel.setStreamHandler(instance)
      myScriptViewFac =  MyScriptViewFactory(messenger: registrar.messenger(),textUpdate: instance);
      registrar.register(myScriptViewFac!, withId: "com.example.myscriptwidget.view")
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "getPlatformVersion":
        result("iOS " + UIDevice.current.systemVersion)
        break;
    case "clear":
        MyScriptWidgetPlugin.myScriptViewFac?.clear();
        result("success");
        break;
    case "initMyScriptWidget":
        if let dictValue = call.arguments as? [String: Any] {
            let arr =  (dictValue["myCertificate"] as? [CChar]) ?? [];
            print(arr);
            EngineProvider.myCertificate_BYTES = arr;
            EngineProvider.configPath = (dictValue["configPath"] as? String) ?? "";
        }
        result("success");
        break;
    case "getChannel":
        result("appStore");
        break;
    default:
      result(FlutterMethodNotImplemented)
    }
  }
    
    var eventSinkIt : FlutterEventSink? = nil;
    public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        eventSinkIt = events;
        return nil;
    }
    
    public func onCancel(withArguments arguments: Any?) -> FlutterError? {
        eventSinkIt = nil;
        return nil;
    }
    public func sendEvent(event:Any) {
        eventSinkIt?(event)
    }
    
    
    func onEvent(text: String) {
        sendEvent(event: text);
    }

}

//
//  MyscriptView.swift
//  Pods
//
//  Created by qqss on 2023/9/15.
//

import Foundation

import UIKit
import Flutter
@available(iOS 13.0, *)
public class MyscriptView : NSObject,FlutterPlatformView, IINKEditorDelegate{
   
    
    var editor: IINKEditor?;
    var framSize :CGRect;
    var args :Any?;
    var textUpdate:TextUpdate?;
    var viewID: Int64;
    init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger,textUpdate:TextUpdate) {
        self.framSize = frame;
        self.viewID = viewID;
        super.init();
        self.args = args;
        self.textUpdate =  textUpdate;
       var _ =  self.initEditorView();
        createDefaultPackage(packageName: "\(viewID)_new", packageType:"Text", engineProvider: EngineProvider.sharedInstance)
    }
    public func view() -> UIView {
        if(EngineProvider.sharedInstance.engine == nil){
            let lable : UILabel =  UILabel();
            lable.text = "初始化失败 engine is null";
            lable.textAlignment =  NSTextAlignment.center;
            return lable;
        }
        return initEditorView();
    }

    var editorContainerView : UIView?;
    var viewModel :EditorViewModel?;
    var controller:EditorViewController?;
    
    var  iinkParamterSet : IINKParameterSet = IINKParameterSet.init();
    
    public func initEditorView()-> UIView {
        
        var width : CGFloat = 350.0;
        var height : CGFloat  = 350.0;
        var bgColor : String = "#FFFFFF";
        if let dictValue = args as? [String: Any] {
            width = dictValue["width"] as! CGFloat;
            height = dictValue["height"] as! CGFloat;
            bgColor = dictValue["bgColor"] as! String;
        }
        let size : CGSize =  CGSize(width: width, height: height);
        let frame: CGRect  = CGRect(origin: CGPoint.init(x: 0, y: 0), size:size);
        if(editorContainerView == nil ){
            editorContainerView = UIView.init(frame: frame);
        }else{
            if(viewModel != nil){
                viewModel!.setEditorViewSize(size: size)
                viewModel!.configureEditorUI(with:  size)
                controller!.view.layoutIfNeeded();
            }
            return editorContainerView!;
        }
        
        if(EngineProvider.sharedInstance.engine == nil){
            let label = UILabel();
            label.text = EngineProvider.sharedInstance.engineErrorMessage;
            return label;
        }
        editorContainerView!.frame = frame;
        viewModel  = EditorViewModel(engine: EngineProvider.sharedInstance.engine!, inputMode: InputMode.forcePen, editorDelegate: self, smartGuideDelegate: nil);
        controller = EditorViewController(viewModel: viewModel!);

       

        controller!.view.frame = frame;
        controller!.view.backgroundColor = bgColor.uicolor();

        editorContainerView!.addSubview(controller!.view);
        controller!.view.layoutIfNeeded();
        iinkParamterSet =  EngineProvider.sharedInstance.engine!.createParameterSet() ?? IINKParameterSet.init();
        try? iinkParamterSet.set(boolean: true, forKey: "export.jiix.text.words")
        return editorContainerView!;
    }
    
 
    
    private func createDefaultPackage(packageName: String, packageType: String, engineProvider: EngineProvider) {
        guard let engine = engineProvider.engine else {
            createAlert(title: "Certificate error", message: engineProvider.engineErrorMessage)
            return
        }
        do {
            var resultPackage: IINKContentPackage?
            let fullPath = FileManager.default.pathForFileInDocumentDirectory(fileName: packageName) + ".iink"
            resultPackage = try engine.createPackage(fullPath.decomposedStringWithCanonicalMapping)
            // Add a blank page type Text Document
            try resultPackage?.createPart(with: packageType)
            if let package = resultPackage {
                try self.editor?.part = package.part(at: 0)
            }
        } catch {
            createAlert(title: "Error", message: "An error occured during the page creation")
            print("Error while creating package : " + error.localizedDescription)
        }
    }
 
    private func createAlert(title: String, message: String) {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
            exit(1)
        }))
    }
    
   public func editUpdate(){
       let ed = EditorViewBean(editor: editor, iinkParamterSet: iinkParamterSet, editView: editorContainerView!);
       MyScriptEngine.addEditView(viewID:  viewID, eb: ed);
       var penColor : String  =  "#000000";
       if let dictValue = args as? [String: Any] {
           penColor = dictValue["penColor"] as! String;
           try? self.editor?.toolController.set(style: "color: " + penColor + ";", forTool: IINKPointerTool.toolPen);
       }
       
   }
    
}
@available(iOS 13.0, *)
extension MyscriptView: EditorDelegate {
    
 
    public func partChanged(_ editor: IINKEditor) {
        print("");
    }
    
    public func contentChanged(_ editor: IINKEditor, blockIds: [String]) {
        for blockId in blockIds {
            let blockEn : IINKContentBlock?  =  editor.getBlockById(blockId);
            var jiixString :String = "";
            let jsonStr :String? = try? editor.export(selection: blockEn, mimeType: IINKMimeType.JIIX, overrideConfiguration: iinkParamterSet);
            if(jsonStr != nil){
                jiixString = jsonStr!;
                let jsonData : Data? = jiixString.data(using: .utf8)
                let json  = try? JSONSerialization.jsonObject(with: jsonData!, options: [])
                    if let dict = json as? [String: Any] {
                        if let text = dict["label"] as? String {
                            self.textUpdate?.onEvent(text: text);
                        }
                       
                    }
//                print(jiixString);
            }
        }
    }
    
    public func onError(_ editor: IINKEditor, blockId: String, message: String) {
        print("");
    }
    

    func didCreateEditor(editor: IINKEditor?) {
        self.editor = editor;
        editor?.addDelegate(self)
        self.editUpdate();
    }
}

    
    
protocol  TextUpdate {
    func onEvent(text : String)-> Void
}

extension String {
    /// 十六进制字符串颜色转为UIColor
    /// - Parameter alpha: 透明度
    func uicolor(alpha: CGFloat = 1.0) -> UIColor {
        // 存储转换后的数值
        var red: UInt64 = 0, green: UInt64 = 0, blue: UInt64 = 0
        var hex = self
        // 如果传入的十六进制颜色有前缀,去掉前缀
        if hex.hasPrefix("0x") || hex.hasPrefix("0X") {
            hex = String(hex[hex.index(hex.startIndex, offsetBy: 2)...])
        } else if hex.hasPrefix("#") {
            hex = String(hex[hex.index(hex.startIndex, offsetBy: 1)...])
        }
        // 如果传入的字符数量不足6位按照后边都为0处理,当然你也可以进行其它操作
        if hex.count < 6 {
            for _ in 0..<6 - hex.count {
                hex += "0"
            }
        }

        // 分别进行转换
        // 红
        Scanner(string: String(hex[..<hex.index(hex.startIndex, offsetBy: 2)])).scanHexInt64(&red)
        // 绿
        Scanner(string: String(hex[hex.index(hex.startIndex, offsetBy: 2)..<hex.index(hex.startIndex, offsetBy: 4)])).scanHexInt64(&green)
        // 蓝
        Scanner(string: String(hex[hex.index(startIndex, offsetBy: 4)...])).scanHexInt64(&blue)

        return UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: alpha)
    }
}

//
//  MyScriptEngine.swift
//  Pods
//
//  Created by qqss on 2023/9/19.
//

import Foundation
@available(iOS 13.0, *)
public class MyScriptEngine{
     
//    public init() {
//        }
    
    static var editMap = [Int64:EditorViewBean]();
    
    static func getEditView(viewId : Int64) ->EditorViewBean?{
        return editMap[viewId];
    }
    
    static func addEditView(viewID: Int64,eb :EditorViewBean){
        editMap[viewID] = eb;
    }
    
    static func removeAll(){
        for (_, em) in editMap {
            em.editor?.clear();
        }
        editMap.removeAll();
    }
    
static func initEditorView(viewID: Int64,args :Any?,editorDelegate :EditorDelegate,editor : IINKEditor?)-> UIView {
    
    let editView =  getEditView(viewId: viewID);
    if(editView != nil){
        return editView!.editView;
    }
    var editorContainerView : UIView?;
    var viewModel :EditorViewModel?;
    var controller:EditorViewController?;
    var  iinkParamterSet : IINKParameterSet = IINKParameterSet.init();
    
    var width : CGFloat = 350.0;
    var height : CGFloat  = 350.0;
    if let dictValue = args as? [String: Any] {
        width = dictValue["width"] as! CGFloat;
        height = dictValue["height"] as! CGFloat;
    }
    let size : CGSize =  CGSize(width: width, height: height);
    let frame: CGRect  = CGRect(origin: CGPoint.init(x: 0, y: 0), size:size);
    if(editorContainerView == nil ){
        editorContainerView = UIView.init(frame: frame);
    }else{
        viewModel!.setEditorViewSize(size: size)
        viewModel!.configureEditorUI(with:  size)
        controller!.view.layoutIfNeeded();
        return editorContainerView!;
    }

    editorContainerView!.frame = frame;
    viewModel  = EditorViewModel(engine: EngineProvider.sharedInstance.engine!, inputMode: InputMode.forcePen, editorDelegate: editorDelegate, smartGuideDelegate: nil);
    controller = EditorViewController(viewModel: viewModel!);

    controller!.view.frame = frame;
    controller!.view.backgroundColor = UIColor.red;

    editorContainerView!.addSubview(controller!.view);
    controller!.view.layoutIfNeeded();
    iinkParamterSet =  EngineProvider.sharedInstance.engine!.createParameterSet() ?? IINKParameterSet.init();
    addEditView(viewID: viewID, eb: EditorViewBean(editor: editor, iinkParamterSet: iinkParamterSet, editView: editorContainerView!))
    return editorContainerView!;
    }
}

上一篇 下一篇

猜你喜欢

热点阅读