神马笔记实现共享方程公式

在前面的2个开发阶段,分别实现了『神马笔记』在文章模式和大纲模式下编辑方程公式元素。

这次开发的目标是实现共享方程公式,以实现知识分享。

一、目标

完成共享方程公式,以实现知识分享的目标。
在这里插入图片描述

二、共享方式

序号方式说明
1拷贝到粘贴板仅复制文字内容,包括段落、图片描述、方程描述、……
2保存为图片保存笔记截图,并添加笔名
3保存为纯文本与"拷贝到粘贴板"相同,将内容保存到文本文件
4保存为Markdown以markdown格式输出笔记元素
5保存为Hexo Markdown在Markdown的基础上增加hexo front-matter,
并按照hexo资源方式保存图片。
6发送到第三方应用同时发送笔记截图与文字内容,有第三方应用自行选择内容。

三、实现过程

1. 拷贝到粘贴板

依次将各种类型的DocumentEntity对象转化为ClipData.Item对象。

List<ClipData.Item> list = document.getList().stream()
  .map(e -> {
    ClipData.Item item;

    Class clz = e.getClass();
    if (clz == ParagraphEntity.class) {
      ParagraphEntity entity = (ParagraphEntity)e;

      item = new ClipData.Item(entity.getText());
    } else if (clz == PictureEntity.class) {
      PictureEntity entity = (PictureEntity)e;

      item = new ClipData.Item(entity.getText());
    } else if (clz == FormulaEntity.class) {
      FormulaEntity entity = (FormulaEntity)e;

      StringBuilder sb = new StringBuilder();

      // formula
      if (!TextUtils.isEmpty(entity.getFormula())) {
        sb.append(entity.getFormula());
      } else {
        sb.append(entity.getLatex());
      }

      // description
      if (!TextUtils.isEmpty(entity.getText())) {
        sb.append('\n');
        sb.append(entity.getText());
      }

      item = new ClipData.Item(sb);
    } else {
      item = new ClipData.Item("");
    }

    return item;
  })
  .collect(Collectors.toList());

if/else的结构似乎应该优化一下了,采用类方式会更理想些。

2. 保存为图片

View绘制到离屏Bitmap并保存之。

public Bitmap apply() {
  Mode mode = chooseMode(view);
  if (mode == null) {
    return null;
  }

  Bitmap target = Bitmap.createBitmap(mode.mWidth, mode.mHeight, mode.mConfig);
  Canvas canvas = new Canvas(target);
  if (mode.mWidth != mode.mSourceWidth) {
    float scale = 1.f * mode.mWidth / mode.mSourceWidth;
    canvas.scale(scale, scale);
  }
  view.draw(canvas);

  return target;
}

3. 保存为纯文本

拷贝到粘贴板类似,将各种类型的DocumentEntity对象添加到StringBuilder对象,然后保存之。

StringBuilder sb = new StringBuilder(10 * 1024);

document.getList().stream()
  .forEach(e -> {
    Class clz = e.getClass();

    if (clz == ParagraphEntity.class) {
      ParagraphEntity entity = (ParagraphEntity)e;

      sb.append(entity.getText());
    } else if (clz == PictureEntity.class) {
      PictureEntity entity = (PictureEntity)e;

      sb.append(entity.getText());
    } else if (clz == FormulaEntity.class) {
      FormulaEntity entity = (FormulaEntity)e;

      // formula
      if (!TextUtils.isEmpty(entity.getFormula())) {
        sb.append(entity.getFormula());
      } else {
        sb.append(entity.getLatex());
      }

      // description
      if (!TextUtils.isEmpty(entity.getText())) {
        sb.append('\n');
        sb.append(entity.getText());
      }
    }


    sb.append('\n');
  });

4. 保存为Markdown

保存到纯文本类似,将各种类型的DocumentEntity转换为CharSequence对象,并保存之。

Map<Class<? extends DocumentEntity>, Function<? super DocumentEntity, CharSequence>> createFactory(final boolean preview) {
  HashMap<Class<? extends DocumentEntity>, Function<? super DocumentEntity, CharSequence>> map = new HashMap<>();

  map.put(ParagraphEntity.class, e -> {
    ParagraphEntity entity = (ParagraphEntity)e;
    CharSequence text = entity.getText();
    return text;
  });

  map.put(PictureEntity.class, e -> {
    PictureEntity entity = (PictureEntity)e;
    String name = pictureMap.get(entity);

    StringBuilder sb = new StringBuilder();
    sb.append(String.format("![%1$s]", entity.getText()));
    sb.append(String.format("(%1$s)", name));

    return sb;
  });

  map.put(FormulaEntity.class, e -> {
    FormulaEntity entity = (FormulaEntity)e;

    StringBuilder sb = new StringBuilder();

    if (preview) {
      sb.append("<p>");
    }

    String formula = entity.getFormula();
    formula = (TextUtils.isEmpty(formula))? entity.getLatex(): formula;
    if (MathMLTransformer.isMathML(formula)) {
      sb.append(formula);
    } else {
      sb.append("$$\n");
      sb.append(getLatex(formula, preview));
      sb.append("\n$$");
    }

    if (preview) {
      sb.append("</p>");
    }

    return sb;
  });

  return map;
}

5. 保存为Hexo Markdown

在markdown的基础上,添加hexo front-matter信息。

并按照hexo assets资源方式存储图片。

String createFrontMatter(RecordEntity entity) {
  StringBuilder sb = new StringBuilder(1024);
  sb.append("---\n");

  // uuid
  {
    sb.append("uuid: ");
    sb.append(getUUID(entity));
    sb.append('\n');
  }

  // title
  {
    sb.append("title: ");
    sb.append(entity.getName());
    sb.append('\n');
  }

  // categories
  {
    String value = getCategories(entity);
    if (!TextUtils.isEmpty(value)) {
      sb.append("categories: ");
      sb.append(value);
      sb.append('\n');
    }
  }

  // tags
  {
    String value = getTags(entity);
    if (!TextUtils.isEmpty(value)) {
      sb.append("tags: ");
      sb.append(value);
      sb.append('\n');
    }
  }

  // mathjax
  if (hasFormula(document)) {
    sb.append("mathjax: true\n");
  }

  // date
  {
    sb.append("date: ");
    sb.append(getDate(entity));
    sb.append('\n');
  }

  sb.append("---\n\n");


  String text = sb.toString();
  return text;
}

6. 发送到第三方应用

包含文本和截图2个内容。

Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

boolean hasImage = (uri != null);
String text = getText(document, getFooter()).toString();

// mime-type
{
  intent.setType("image/*");
  intent.setAction(Intent.ACTION_SEND);
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

  if (!hasImage) {
    intent.setType("text/plain");
  } else {
    if (hasImage) {
      hasImage = isSupport(context, resolveInfo, intent);
      if (!hasImage) {
        intent.setType("text/plain");
      }
    }
  }
}

// component
{
  String pkg = resolveInfo.activityInfo.packageName;
  String cls = resolveInfo.activityInfo.name;
  ComponentName cn = new ComponentName(pkg, cls);
  intent.setComponent(cn);
}

// text
if (!TextUtils.isEmpty(text)) {
  intent.putExtra(Intent.EXTRA_TEXT, text);
}

// picture
if (hasImage) {
  if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND)) {

    intent.removeExtra(Intent.EXTRA_STREAM);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
  }

  if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND_MULTIPLE)) {

    ArrayList<Uri> imageUris = new ArrayList();
    imageUris.add(uri); // Add your image URIs here

    intent.removeExtra(Intent.EXTRA_STREAM);
    intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
  }
}

// start
try {
  context.startActivity(intent);
} catch (Exception e) {

}

四、预览Markdown

1. 使用HTML预览

在6中分享方式中,2种Markdown方式无法直接根据输入内容进行预览。

Android平台并没有内置支持Markdown的应用,无法直接预览。

因此需要转换为HTML格式以实现预览。

序号方式预览
1拷贝到粘贴板与正文一致
2保存为图片与正文一致
3保存为纯文本与正文一致
4保存为Markdown使用HTML进行预览
5保存为Hexo Markdown使用HTML进行预览
6发送到第三方应用与正文一致

2. 遇到的问题

在添加方程公式元素之前,一切工作良好。

添加了方程公式元素之后,会发现很多方程公式无法正常预览。

原因在于markedmathjax发生了冲突。

模块说明
marked将markdown文档渲染为html文档
mathjax渲染html文档中的方程公式,支持latex及mathml

在marked渲染markdown文档时,对一些特殊字符进行了转义,同是将latex中的一些字符进行了转义。从而导致latex公式遭到破坏,进而无法正常显示公式。

详细冲突细节,请参考一下文章。

  1. 在Hexo中渲染MathJax数学公式
  2. 【Markdown】Markdown 使用MathJax引擎 书写Latex 数学公式
  3. Hexo下mathjax的转义问题
  4. 让marked与MathJax和谐共存

3. 解决方案

文章中给出了2种解决冲突的方案。

方案问题
修改marked使用在线marked,无法修改
修改markdown渲染引擎为pandoc需要另外安装pandoc进行渲染

因此已有的2种方案都不适合『神马笔记』。

最终的解决方案将LaTex或MathML包含在<p></p>之间。

因为marked不会对内嵌的html代码进行转义,这样保证了原始的内容。

4. 新的问题

将包含<p></p>的方程公式保存为markdown后。

使用Typora无法直接预览方程公式,因为被处理成内嵌的HTML代码。

因此需要分成2份Markdown进行输出。

Markdown输出类型处理方式
正文方程公式不包含<p></p>
预览将方程公式包含在<p></p>

5. 再次遇到问题

使用『神马笔记』导出markdown后,使用Hexo引擎发布到个人博客时,发现很多公式不能正常显示。

因为Hexo使用marked渲染markdown文件,导致LaTeX被错误转义。

修改的方式有2种。

序号方案参考资料
1修改markedHexo下mathjax的转义问题
2修改markdown渲染引擎为pandocHexo下mathjax的转义问题

最终选择pandoc方式。

比较修改引擎的方式,更偏向于替换整个引擎。

五、接下来

神马笔记』在笔记中插入方程公式元素功能开发到此结束。

接下来2件事情要做:

  1. 编写LaTex数学公式简明手册
  2. 发布新版本『神马笔记

六、Finally

~燕雁无心~太湖西畔随云去~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值